// 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 _timeFrame;
        private readonly Interpreter _interpreter;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="name">システムの名前</param>
        /// <param name="timeFrame">タイムフレーム</param>
        /// <param name="brandList">使用する銘柄リスト</param>
        public SystemExecutor(string name, BrandList brandList, TimeFrame timeFrame)
        {
            _name = name;
            _brandList = brandList;
            _timeFrame = timeFrame;
            _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 (_interpreter.GetMagicCommentValue("loop-type").Equals("date-only"))
                LoopDateOnly(worker, appendText);

            else
                LoopBrandAndDate(worker, appendText);
        }

        private void LoopBrandAndDate(BackgroundWorker worker, AppendTextDelegate appendText)
        {
            var count = 0;
            using (var logData = new LogData(_name, _timeFrame))
            {
                if (PriceData.MaxDate == DateTime.MinValue)
                    throw new Exception("株価データがありません。");
                foreach (var code in _brandList.List)
                {
                    if (worker.CancellationPending)
                        break;
                    var prices = PriceData.GetPrices(code, _timeFrame);
                    if (prices == null)
                        continue;
                    var builtins = new SimulateBuiltins
                        {
                            Index = 0,
                            System = _name,
                            BrandList = _brandList,
                            LogData = logData,
                            AppendText = appendText,
                            Prices = prices,
                            RightIndex = prices.Count - 1,
                        };
                    _interpreter.Builtins = builtins;
                    _interpreter.GlobalVariableTable.Clear();
                    GlobalData.Load(_interpreter);
                    if (_interpreter.Builtins.Index > prices.Count)
                        throw new Exception("前回の実行時より株価データが古いです。");

                    if (CheckSplit(code, builtins.Index, prices))
                    {
                        var msg = string.Format(
                            "{0} {1} 分割調整のためログを削除して再実行\r\n",
                            code, builtins.Brand.Name);
                        appendText(msg);
                        logData.Remove(code);
                        GlobalData.Delete(_name, _timeFrame, 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", _timeFrame);
            if (prices == null)
                throw new Exception("株価データがありません。");
            foreach (var p in prices)
            {
                p.Code = "0000";
                p.Market = "不明";
                p.Open = p.High = p.Low = p.Close = 0;
                p.Volume = 0;
            }
            using (var logData = new LogData(_name, _timeFrame))
            {
                var builtins = new SimulateBuiltins
                    {
                        Index = 0,
                        System = _name,
                        BrandList = _brandList,
                        LogData = logData,
                        AppendText = appendText,
                        Prices = prices,
                        RightIndex = prices.Count - 1,
                    };
                _interpreter.Builtins = builtins;
                GlobalData.Load(_interpreter);
                if (_interpreter.Builtins.Index > prices.Count)
                    throw new Exception("前回の実行時より株価データが古いです。");

                // 銘柄リスト内の分割調整の有無を調べる
                foreach (var code in _brandList.List)
                    if (CheckSplit(code, builtins.Index, prices))
                    {
                        appendText("分割調整のためログを削除して再実行\r\n");
                        logData.Clear();
                        GlobalData.Delete(_name, _timeFrame, "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);
        }
    }
}