// Copyright(C) 2008, 2010 panacorn <panacoran@users.sourceforge.jp>
// 
// This program is part of Protra.
//
// Protra is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <http://www.gnu.org/licenses/>.
// 
// $Id: MujinzouUpdator.cs 306 2010-03-19 13:21:22Z panacoran $

using System;
using System.Collections;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Protra.Lib.Db;

namespace Protra.Lib.Update
{
	/// <summary>
	/// 無尽蔵を利用して株価データの更新を行うクラス。
	/// </summary>
	public class MujinzouUpdator: PriceDataUpdator
	{
        /// <summary>
        /// データが存在する最初の日付を取得する。
        /// </summary>
        public override DateTime DataSince
        {
            get { return new DateTime(1996, 1, 4); }
        }

        string urlHead;
        string urlTemplate;

        static string BackslashEscape(string s)
        {
            var r = "";
            foreach (var c in s)
                r += @"\" + c;
            return r;
        }

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        /// <param name="url">データのURL。</param>
        public MujinzouUpdator(string url)
        {
            var r = new Regex(@"^(?<head>.*)\d\d(?<yy>\d\d)(?<s1>.*)\k<yy>_(?<MM>\d\d)(?<s2>.*)T\k<yy>\k<MM>\d\d.lzh$");
            var m = r.Match(url);
            if (!m.Success)
                return;
            urlHead = m.Groups["head"].Value;
            urlTemplate = "yyyy" + BackslashEscape(m.Groups["s1"].Value) + "yy_MM" +
                BackslashEscape(m.Groups["s2"].Value) + "TyyMMdd";
        }

        /// <summary>
		/// 株価データを更新する。
		/// </summary>
		/// <param name="worker">BackgroundWorker</param>
        /// <param name="e">DoWorkイベントの引数</param>
		public override void Update(BackgroundWorker worker, DoWorkEventArgs e)
		{
            DateTime begin = (DateTime)e.Argument;
            if (begin < DataSince)
                begin = DataSince;
			DateTime today = DateTime.Now;
			// 新しいデータが置かれるのは早くても午後5時以降
			if (today.Hour < 17)
				today = today.AddDays(-1);

            for (Start((DateTime)e.Argument, today);
				 ShouldContinue(); NextDate())
			{
				UpdateProgress(worker, e);

                StartDownload();
                if (urlHead == null)
                    throw new Exception("URLが正しくありません。");
                DownloadUtil.Url = urlHead + Date.ToString(urlTemplate) + ".lzh";
                Stream stream = DownloadUtil.DownloadAndExtract();
                if (stream == null)
                {
                    DecrementTotalDays();
                    continue;
                }
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    stream.Close();
                    return;
                }
                StreamReader reader = new StreamReader(stream, Encoding.GetEncoding("shift_jis"));
                ArrayList dataList = new ArrayList();
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    PriceData tmp = ParseLine(line);
                    if (tmp != null)
                        dataList.Add(tmp);
                }
                reader.Close();
                TotalRecords = dataList.Count;
                EndDownload();

                for (; NumRecords < TotalRecords; IncrementRecords(), UpdateProgress(worker, e))
                {
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        PriceTable.Delete(Date);
                        return;
                    }
                    
                    PriceData data = (PriceData)dataList[NumRecords];
                    Brand brand = BrandTable.GetRecordOrCreate(data.MarketId, data.Code, data.Name);
                    PriceTable.Add(brand.Id, data.Date, data.Open, data.High, data.Low, data.Close, data.Volume);
                }
                IncrementDays();
            }

			UpdateSplitData(worker, e);
            worker.ReportProgress(100, "");
		}

		/// <summary>
		/// 文字列に含まれるデータを格納したオブジェクトを返す。
		/// </summary>
		/// <param name="line">文字列を指定する。</param>
		/// <returns>オブジェクト</returns>
		private PriceData ParseLine(string line)
		{
			string[] tokens = line.Split(new char [] {'\t', ','});
			PriceData r = new PriceData();
			try
			{
				r.Date = DateTime.Parse(tokens[0]);
				r.Code = int.Parse(tokens[1]);
				r.Name = tokens[3];
				int index = r.Name.IndexOf(' ');
				if (index > 0)
					r.Name = r.Name.Remove(0, index + 1);
				// 指数の値に小数が含まれているのでdouble.Parseを利用する。
				r.Open = (int)double.Parse(tokens[4]);
				r.High = (int)double.Parse(tokens[5]);
				r.Low = (int)double.Parse(tokens[6]);
				r.Close = (int)double.Parse(tokens[7]);
				r.Volume = double.Parse(tokens[8]) / 1000;
				switch (tokens[2])
				{
				case "11":
					r.MarketId = Db.MarketId.Tokyo1;
					break;
				case "12":
					r.MarketId = Db.MarketId.Tokyo2;
					break;
				case "13": 				
					r.MarketId = Db.MarketId.Mothers;
					break;
				case "14":
					r.MarketId = Db.MarketId.TokyoForeign;
					break;
				case "20"://この大証は１部、２部混在しているので１部にする
				case "21":
					r.MarketId = Db.MarketId.Osaka1;
					break;
				case "22":
					r.MarketId = Db.MarketId.Osaka2;
					break;
				case "23":
				case "24": 
					r.MarketId = Db.MarketId.Hercules;
					break;
				case "30"://この名証は１部、２部混在しているので１部にする
				case "31":
					r.MarketId = Db.MarketId.Nagoya1;
					break;
				case "32":
					r.MarketId = Db.MarketId.Nagoya2;
					break;
				case "91":
					r.MarketId = Db.MarketId.Jasdaq;
					break;
				default:
					return null;
				}
			}
			catch (FormatException)
			{
				// フォーマットエラーは全部無視する。
				return null;
			}
			return r;
		}
	}
}
