// Copyright (C) 2008, 2010, 2011 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: YahooFinanceUpdator.cs 442 2013-03-04 11:56:25Z panacoran $

using System;
using System.ComponentModel;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;
using System.IO;
using System.Net;
using Protra.Lib.Data;

namespace Protra.Lib.Update
{
	/// <summary>
	/// Yahoo!ファイナンスを利用して株価データを更新するクラス。
	/// </summary>
    public class YahooFinanceUpdator: PriceDataUpdator
    {
        readonly Regex regex;

        /// <summary>
        /// データが存在する最初の日付を取得する。
        /// </summary>
        public override DateTime DataSince
        {
            get { return new DateTime(1991, 1, 4); }
        }

        /// <summary>
        /// コンストラクタ。
        /// </summary>
        public YahooFinanceUpdator()
        {
            regex = new Regex("<b class=\"yjXL\">(?<name>[^<]+)</b><span class=\"yjM\">" +
                "【(?<market>.*): (?<code>[0-9]+)(?:\\.[TOQNJ])?】.*" +
                "<td><small>(?<year>\\d{4})年(?<month>1?\\d)月(?<day>\\d?\\d)日</small></td>\\n" +
                "<td><small>(?<open>[0-9,.]+)</small></td>\\n" +
                "<td><small>(?<high>[0-9,.]+)</small></td>\\n" +
                "<td><small>(?<low>[0-9,.]+)</small></td>\\n" +
                "<td><small><b>(?<close>[0-9,.]+)</b></small></td>\\n" +
                "(?:<td><small>(?<volume>[0-9,]+)</small></td>)?",
                RegexOptions.Compiled | RegexOptions.Singleline);
        }

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

            for (Start(begin, today); Date <= EndDate; NextDate())
            {
                TotalRecords = 9999 - 1300 + 2;
                UpdateProgress(worker);
                for (NumRecords = 0; NumRecords < TotalRecords; NumRecords++, DoneRecords++, UpdateProgress(worker))
                {
                    if (worker.CancellationPending)
                    {
                        e.Cancel = true;
                        PriceData.CloseAll();
                        return;
                    }
                    int code;
                    if (NumRecords == 0)
                        code = 998407; // 日経225
                    else if (NumRecords == 1)
                        code = 998405; // TOPIX
                    else
                        code = NumRecords - 2 + 1301;
                    Price r = ParsePage(GetPage(code));
                    if (r == null)
                    {
                        if (NumRecords == 0) // 日経平均がない。
                            break;
                        continue;
                    }
                    DateTime latest = PriceData.MaxDateByCode(r.Code);
                    if (latest != DateTime.MinValue)
                    {
                        var dummy = new Price {Code = r.Code, Open = 0, High = 0, Low = 0, Close = 0};
                        for (DateTime d = latest.AddDays(1); d < r.Date; d = d.AddDays(1))
                        {
                            if (!Utils.IsMarketOpen(d))
                                continue;
                            dummy.Date = d;
                            PriceData.Add(dummy, false);
                        }
                    }
                    PriceData.Add(r, Date == EndDate);
                }
                if (NumRecords == TotalRecords)
                {
                    PriceData.MaxDate = Date;
                    NumDays++;
                }
                else
                    TotalDays--;
            }
            PriceData.CloseAll();
        }

        private string GetPage(int code)
        {
            var dl = new DownloadUtil(
                "http://table.yahoo.co.jp/t?c=" + Date.Year + "&a=" + Date.Month + "&b=" + Date.Day +
                "&f=" + Date.Year + "&d=" + Date.Month + "&e=" + Date.Day + "&g=d" + "&s=" + code +
                "&y=0" + "&z=" + code);
            for (int i = 0; i < 10; i++)
            {
                try
                {
                    var stream = dl.GetResponse();
                    if (stream == null)
                        return null;
                    var reader = new StreamReader(stream, Encoding.GetEncoding("euc-jp"));
                    string buf = reader.ReadToEnd();
                    reader.Close();
                    return buf;
                }
                catch (WebException e)
                {
                    switch (e.Status)
                    {
                        case WebExceptionStatus.Timeout:
                        case WebExceptionStatus.ConnectionClosed:
                        case WebExceptionStatus.ReceiveFailure:
                        case WebExceptionStatus.ConnectFailure:
                            break;
                        default:
                            throw;
                    }
                }
            }
            return null;
        }

        private Price ParsePage(string buf)
        {
            if (buf == null)
                return null;
            var m = regex.Match(buf);
            if (!m.Success)
                return null;
            var r = new Price();
            try
            {
                r.Date = new DateTime(int.Parse(m.Groups["year"].Value),
                    int.Parse(m.Groups["month"].Value),
                    int.Parse(m.Groups["day"].Value));
                r.Code = m.Groups["code"].Value;
                const NumberStyles s = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands;
                r.Open = (int)double.Parse(m.Groups["open"].Value, s);
                r.High = (int)double.Parse(m.Groups["high"].Value, s);
                r.Low = (int)double.Parse(m.Groups["low"].Value, s);
                r.Close = (int)double.Parse(m.Groups["close"].Value, s);
                if (m.Groups["volume"].Value != "")
                    r.Volume = double.Parse(m.Groups["volume"].Value, s) / 1000;
                switch (m.Groups["market"].Value)
                {
                    case "東証1部":
                    case "東証":
                    case "---":
                        r.Market = "T1";
                        break;
                    case "東証2部":
                        r.Market = "T2";
                        break;
                    case "マザーズ":
                        r.Market = "M";
                        break;
                    case "東証外国":
                        r.Market = "T1";
                        break;
                    case "大証":
                    case "大証1部":
                        r.Market = "O1";
                        break;
                    case "大証2部":
                        r.Market = "O2";
                        break;
                    case "ヘラクレス":
                        r.Market = "H";
                        break;
                    case "JASDAQ":
                        r.Market = "J";
                        break;
                    default:
                        return null;
                }
            }
            catch (FormatException)
            {
                return null;
            }
            // 無尽蔵に合わせる。
            if (r.Code == "998407")
            {
                r.Code = "1001";
                r.Market = "T1";
            }
            else if (r.Code == "998405")
                r.Code = "1002";
            return r;
        }

        /// <summary>
        /// 使わない
        /// </summary>
        /// <param name="time">使わない</param>
        /// <returns>使わない</returns>
        protected override bool IsDataAvailable(DateTime time)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// 使わない
        /// </summary>
        /// <returns>使わない</returns>
        protected override string DownloadUrl
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// 使わない
        /// </summary>
        /// <param name="line">使わない</param>
        /// <returns>使わない</returns>
        protected override Price ParseLine(string line)
        {
            throw new NotImplementedException();
        }
    }
}
