// Copyright (c) 2014 panacoran <panacoran@users.sourceforge.jp>
// This program is part of OmegaChart.
// OmegaChart is licensed under the Apache License, Version 2.0.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using Zanetti.Data;

namespace Zanetti.DataSource.Specialized
{
    internal class KdbComDataSource : DailyDataSource
    {
        public KdbComDataSource(int[] dates) : base(dates)
        {
        }

        public override void Run()
        {
            GetIndices(); // 先に指数のデータを読む。
            var newdata = new Dictionary<int, Dictionary<int, NewDailyData>>();
            foreach (var date in _dates)
            {
                newdata[date] = FillData(date);
                SendMessage(AsyncConst.WM_ASYNCPROCESS, date | DATE_MASK, AsyncConst.LPARAM_PROGRESS_SUCCESSFUL);
            }
            foreach (AbstractBrand br in Env.BrandCollection.Values)
            {
                if (br.Market == MarketType.B && !IsSupportedIndex(br.Code) || br.Market == MarketType.Custom)
                    continue;
                using (var farm = (DailyDataFarm)br.CreateDailyFarm(_dates.Length))
                {
                    var traceFlag = false;
                    foreach (var date in _dates)
                    {
                        NewDailyData td;
                        if (newdata[date].TryGetValue(br.Code, out td))
                        {
                            farm.UpdateDataFarm(date, td);                            
                        }
                        else
                        {
                            if (traceFlag)
                                continue;
                            traceFlag = true;
                            Debug.WriteLine("Data not found(k-db.com) : code=" + br.Code + " market=" + br.Market);
                        }
                    }
                    farm.Save(Util.GetDailyDataFileName(br.Code));
                    SendMessage(AsyncConst.WM_ASYNCPROCESS, br.Code, AsyncConst.LPARAM_PROGRESS_SUCCESSFUL);
                }
            }
        }

        private readonly Dictionary<DateTime, int> _marketVolume = new Dictionary<DateTime, int>();
        private readonly Dictionary<DateTime, NewDailyData> _nikkei225 = new Dictionary<DateTime, NewDailyData>();
        private readonly Dictionary<DateTime, NewDailyData> _topix = new Dictionary<DateTime, NewDailyData>();

        private void GetIndices()
        {
            GetMarketVolume();
            GetIndexValues();

        }

        private void GetMarketVolume()
        {
            const string url = "http://k-db.com/statistics/T1?download=csv";
            using (var reader = new StreamReader(Util.HttpDownload(url), 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 = double.Parse(tokens[1]);
                    _marketVolume[date] = (int)(volume * 0.001);
                }
            }
        }

        private void GetIndexValues()
        {
            foreach (var kdbCode in new[] {"I101", "I102"})
            {
                var url = string.Format("http://k-db.com/indices/{0}?download=csv", kdbCode);
                var prices = kdbCode == "I101" ? _nikkei225 : _topix;
                using (var reader = new StreamReader(Util.HttpDownload(url), Encoding.GetEncoding("shift_jis")))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        var tokens = line.Split(',');
                        if (tokens.Length != 5)
                            continue;
                        if (tokens[0] == "日付")
                            continue;
                        var date = DateTime.Parse(tokens[0]);
                        prices[date] = new NewDailyData
                        {
                            open = (int)(double.Parse(tokens[1]) * 100),
                            high = (int)(double.Parse(tokens[2]) * 100),
                            low = (int)(double.Parse(tokens[3]) * 100),
                            close = (int)(double.Parse(tokens[4]) * 100),
                            volume = _marketVolume[date]
                        };
                    }
                }
            }
        }

        private void InsertIndices(DateTime date, Dictionary<int, NewDailyData> result)
        {

            result[(int)BuiltInIndex.Nikkei225] = _nikkei225[date];
            result[(int)BuiltInIndex.TOPIX] = _topix[date];
        }

        private Dictionary<int, NewDailyData> FillData(int date)
        {
            var result = new Dictionary<int, NewDailyData>();
            var d2 = Util.IntToDate(date);
            var url = string.Format("http://k-db.com/stocks/{0:yyyy-MM-dd}?download=csv", d2);
            using (var reader = new StreamReader(Util.HttpDownload(url), Encoding.GetEncoding("shift_jis")))
            {
                InsertIndices(d2, result);
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    var tokens = line.Split(',');
                    if (tokens.Length != 10 || tokens[0].Length != 6)
                        continue;
                    var code = int.Parse(tokens[0].Substring(0, 4));
                    var br = Env.BrandCollection.FindBrand(code) as BasicBrand;
                    if (br == null || !CheckMarket(br.Market, tokens[1]))
                            continue;
                    var volume = (int)ParseField(tokens[8]);
                    const int shift = 10; // 株価は10倍で記録
                    var td = new NewDailyData
                    {
                        volume = volume,
                        open = (int)(ParseField(tokens[4]) * shift),
                        high = (int)(ParseField(tokens[5]) * shift),
                        low = (int)(ParseField(tokens[6]) * shift),
                        close = (int)(ParseField(tokens[7]) * shift)
                    };
                    result[code] = td;
                }
            }
            return result;
        }

        private bool CheckMarket(MarketType market, string name)
        {
            switch (name)
            {
                case "東証":
                case "東証1部":
                    return market == MarketType.T1;
                case "東証2部":
                    return market == MarketType.T2;
                case "東証マザーズ":
                case "東証マザーズ外国":
                case "東証マザーズ審査": //20150119 k-db仕様変更にて追加
                    return market == MarketType.M;
                case "東証TPM":
                    return market == MarketType.Custom;
                case "東証1部外国":
                    return market == MarketType.T1;
                case "東証2部外国":
                    return market == MarketType.T2;
                case "大証":
                case "大証1部":
                    return market == MarketType.O1;
                case "大証2部":
                    return market == MarketType.O2;
                case "東証JQグロース":
                case "東証JQスタンダード":
                case "東証JQスタンダード外国":
                case "JQ":
                case "JQスタンダード":
                case "JQスタンダード外国":
                case "JQグロース":
                case "JQNEO":
                    return market == MarketType.J;
                case "HCスタンダード":
                case "HCスタンダード外国":
                case "HCグロース":
                    return market == MarketType.H;
                case "福証":
                case "福証Q-Board":
                case "札証":
                case "札証アンビシャス":
                case "名証":
                    return false;
            }
            return false;
        }

        private bool IsSupportedIndex(int code)
        {
            return code == (int)BuiltInIndex.Nikkei225 ||
                   code == (int)BuiltInIndex.TOPIX;
        }

        private double ParseField(string s)
        {
            // 無効なフィールドを示す"-"を0として扱う。
            return s == "-" ? 0 : double.Parse(s);
        }
    }
}