// Copyright(C) 2008, 2010, 2011, 2013 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: KdbComUpdator.cs 492 2013-09-14 15:24:10Z panacoran $

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using Protra.Lib.Data;

namespace Protra.Lib.Update
{
    /// <summary>
    /// 株価データダウンロードサイトを利用して株価データの更新を行うクラス。
    /// </summary>
    public class KdbComUpdator : PriceDataUpdator
    {
        /// <summary>
        /// 東証一部の出来高を読んでから株価データを更新する。
        /// </summary>
        /// <param name="worker">BackgroundWorker</param>
        /// <param name="e">DoWorkイベントの引数</param>
        protected override void UpdatePrice(BackgroundWorker worker, DoWorkEventArgs e)
        {
            GetMarketVolume();
            base.UpdatePrice(worker, e);
        }

        /// <summary>
        /// データが存在する最初の日付を取得する。
        /// </summary>
        public override DateTime DataSince
        {
            get
            {
                var result = DateTime.Now;
                var date = result;
                for (var i = 0; i < 500; date = date.AddDays(-1))
                {
                    if (!Calendar.IsMarketOpen(date))
                        continue;
                    result = date;
                    i++;
                }
                return new DateTime(result.Year, result.Month, result.Day);
            }
        }

        /// <summary>
        /// 新しいデータが置かれる時刻に達しているか。
        /// </summary>
        /// <param name="time">時刻</param>
        /// <returns></returns>
        protected override bool IsDataAvailable(DateTime time)
        {
            return time.Hour >= 16;
        }

        /// <summary>
        /// データのURLを取得する。
        /// </summary>
        /// <returns>URL</returns>
        protected override string DownloadUrl
        {
            get { return "http://k-db.com/site/download.aspx?p=all&download=csv&date=" + Date.ToString("yyyy-MM-dd"); }
        }

        private Dictionary<DateTime, double> _indexVolume;

        private void GetMarketVolume()
        {
            _indexVolume = new Dictionary<DateTime, double>();
            var dl = new DownloadUtil("http://k-db.com/site/toukei.aspx?market=T1&download=csv");
            var stream = dl.GetResponse();
            if (stream == null)
                throw new ApplicationException("市場統計データの読み込みに失敗しました。");
            using (var reader = new StreamReader(stream, Encoding.GetEncoding("shift_jis")))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    var tokens = line.Split(',');
                    switch (tokens[0])
                    {
                        case "東証1部":
                        case "日付":
                            continue;
                    }
                    var date = DateTime.Parse(tokens[0]);
                    var volume = Math.Round(double.Parse(tokens[1]) / 1000); // 無尽蔵に合わせるため丸める。
                    _indexVolume[date] = volume;
                }
            }
        }

        /// <summary>
        /// 文字列に含まれるデータを格納したオブジェクトを返す。
        /// </summary>
        /// <param name="line">文字列を指定する。</param>
        /// <returns>オブジェクト</returns>
        protected override Price ParseLine(string line)
        {
            var tokens = line.Split(',');
            var r = new Price();
            try
            {
                if (tokens[0] == "10" || tokens[0] == "67")
                {
                    r.Code = tokens[0] == "67" ? "1001" : "1002";
                    r.Market = "T1";
                    r.Volume = _indexVolume[Date];
                }
                else if (tokens[0].Length == 6)
                {
                    r.Code = tokens[0].Substring(0, 4);
                    switch (tokens[2])
                    {
                        case "東証":
                        case "東証1部":
                            r.Market = "T1";
                            break;
                        case "東証2部":
                            r.Market = "T2";
                            break;
                        case "東証マザーズ":
                        case "東証マザーズ外国":
                            r.Market = "M";
                            break;
                        case "東証TPM":
                            return null;
                        case "東証1部外国":
                            r.Market = "T1";
                            break;
                        case "大証":
                        case "大証1部":
                            r.Market = "O1";
                            break;
                        case "大証2部":
                            r.Market = "O2";
                            break;
                        case "東証JQグロース":
                        case "東証JQスタンダード":
                        case "東証JQスタンダード外国":
                        case "JQ":
                        case "JQスタンダード":
                        case "JQスタンダード外国":
                        case "JQグロース":
                        case "JQNEO":
                            r.Market = "J";
                            break;
                        case "HCスタンダード":
                        case "HCスタンダード外国":
                        case "HCグロース":
                            r.Market = "H";
                            break;
                        case "福証":
                        case "福証QBoard":
                        case "札証":
                        case "札証アンビシャス":
                        case "名証":
                            return null;
                        default:
                            throw new ApplicationException(tokens[2] + ": 不明な市場名です。");
                    }
                    r.Volume = ParseField(tokens[8]) / 1000;
                }
                else
                    return null;
                r.Date = Date;
                r.Open = (int)ParseField(tokens[4]);
                r.High = (int)ParseField(tokens[5]);
                r.Low = (int)ParseField(tokens[6]);
                r.Close = (int)ParseField(tokens[7]);
            }
            catch (FormatException)
            {
                throw new ApplicationException("データ形式に異常があります。");
            }
            return r;
        }

        private static double ParseField(string s)
        {
            // 無効なフィールドを示す"-"を0として扱う。
            if (s == "-")
                return 0;
            // 指数の値に小数が含まれているのでdouble.Parseを利用する。
            return double.Parse(s);
        }
    }
}