// Copyright (C) 2003, 2004 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2005, 2007, 2008, 2010, 2013 panacoran <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$

using System;
using System.ComponentModel;
using System.IO;
using Protra.Lib;
using Protra.Lib.Config;
using Protra.Lib.Data;
using Protra.Lib.Lang;
using Protra.Lib.Lang.Builtins;

namespace PtSim
{
    /// <summary>
    /// ループの種類を指定する列挙体
    /// </summary>
    public enum LoopType
    {
        /// <summary>
        /// 銘柄と日付
        /// </summary>
        BrandAndDate,

        /// <summary>
        /// 日付のみ
        /// </summary>
        DateOnly
    }

    /// <summary>
    /// トレーディングシステムを実行するクラス
    /// </summary>
    public class SystemExecutor
    {
        private readonly string _name;
        private readonly BrandList _brandList;
        private readonly TimeFrame _priceType;
        private readonly LoopType _loopType;
        private readonly Interpreter _interpreter;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="name">システムの名前</param>
        /// <param name="priceType">株価データの種類</param>
        /// <param name="brandList">使用する銘柄リスト</param>
        /// <param name="loopType">繰り返しの種類</param>
        public SystemExecutor(string name, BrandList brandList, TimeFrame priceType, LoopType loopType)
        {
            _name = name;
            _brandList = brandList;
            _priceType = priceType;
            _loopType = loopType;
            _interpreter = new Interpreter(Path.Combine(Global.DirSystem, name));
        }

        /// <summary>
        /// トレーディングシステムを実行する。
        /// </summary>
        /// <param name="worker">Executeを実行するBackgroundWorker</param>
        /// <param name="appendText">TextBoxのAppendText</param>
        public void Execute(BackgroundWorker worker, AppendTextDelegate appendText)
        {
            if (_loopType == LoopType.BrandAndDate)
                LoopBrandAndDate(worker, appendText);
            else
                LoopDateOnly(worker, appendText);
        }

        private void LoopBrandAndDate(BackgroundWorker worker, AppendTextDelegate appendText)
        {
            var count = 0;
            foreach (var code in _brandList.List)
            {
                if (worker.CancellationPending)
                    break;
                var prices = PriceData.GetPrices(code, _priceType);
                if (prices == null)
                    throw new Exception(string.Format("{0} 株価データがありません。\n", GlobalEnv.BrandData[code]));
                var builtins = new SimulateBuiltins
                    {
                        Index = 0,
                        System = _name,
                        BrandList = _brandList,
                        AppendText = appendText,
                        Prices = prices,
                        RightIndex = prices.Count - 1,
                    };
                _interpreter.Builtins = builtins;
                _interpreter.GlobalVariableTable.Clear();
                GlobalData.Load(_interpreter);
                if (CheckSplit(code, builtins.Index, prices))
                {
                    var msg = string.Format(
                        "{0} {1} 分割調整のためログを削除して再実行\r\n",
                        code, builtins.Brand.Name);
                    appendText(msg);
                    LogData.Delete(_name, _priceType, code);
                    GlobalData.Delete(_name, _priceType, code);
                    builtins.Index = 0;
                    _interpreter.GlobalVariableTable.Clear();
                }
                for (; builtins.Index < prices.Count; builtins.Index++)
                {
                    if (worker.CancellationPending)
                        break;
                    _interpreter.Execute();
                }
                GlobalData.Save(_interpreter);
                worker.ReportProgress(100 * ++count / _brandList.List.Count);
            }
        }

        private void LoopDateOnly(BackgroundWorker worker, AppendTextDelegate appendText)
        {
            // 日経平均のデータを読み出し、価格をゼロにする
            var prices = PriceData.GetPrices("1001", _priceType);
            if (prices == null)
                throw new Exception(string.Format("{0} 株価データがありません。\n", GlobalEnv.BrandData["1001"]));
            foreach (var p in prices)
            {
                p.Code = "0000";
                p.Market = "不明";
                p.Open = p.High = p.Low = p.Close = 0;
                p.Volume = 0;
            }
            var builtins = new SimulateBuiltins
                {
                    Index = 0,
                    System = _name,
                    BrandList = _brandList,
                    AppendText = appendText,
                    Prices = prices,
                    RightIndex = prices.Count - 1,
                };
            _interpreter.Builtins = builtins;
            GlobalData.Load(_interpreter);

            // 銘柄リスト内の分割調整の有無を調べる
            foreach (var code in _brandList.List)
                if (CheckSplit(code, builtins.Index, prices))
                {
                    appendText("分割調整のためログを削除して再実行\r\n");
                    LogData.Delete(_name, _priceType);
                    GlobalData.Delete(_name, _priceType, "0000");
                    builtins.Index = 0;
                    _interpreter.GlobalVariableTable.Clear();
                    break;
                }
            var total = prices.Count - builtins.Index;
            var count = 0;
            for (; builtins.Index < prices.Count; builtins.Index++)
            {
                if (worker.CancellationPending)
                    break;
                _interpreter.Execute();
                worker.ReportProgress(100 * ++count / total);
            }
            GlobalData.Save(_interpreter);
        }

        /// <summary>
        /// 指定した銘柄の株式分割が前回実行した日からあったかどうかを返す。
        /// </summary>
        /// <param name="code">銘柄の証券コード</param>
        /// <param name="index">実行対象の株価データの開始位置</param>
        /// <param name="prices">日付を得るための株価データ</param>
        /// <returns></returns>
        private static bool CheckSplit(string code, int index, PriceList prices)
        {
            var split = GlobalEnv.BrandData[code].Split;
            if (index == 0 || split.Count == 0)
                return false;
            var latest = split[split.Count - 1].Date;
            return ( // 前回実行した日と比較しないと週足では飛び越す可能性がある。
                       prices[index - 1].Date < latest &&
                       // 株価データよりも未来の株式分割は除く。
                       latest <= prices[prices.Count - 1].Date);
        }
    }
}