// Copyright (C) 2008, 2013 panacoran <panacoran@users.sourceforge.jp>
// Copyright (C) 2011 Daisuke Arai <darai@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: BasicBuiltins.cs 460 2013-06-16 05:38:46Z panacoran $

using System.Collections.Generic;
using Protra.Lib.Data;

namespace Protra.Lib.Lang.Builtins
{
    /// <summary>
    /// 基本的な組み込み関数を実行するクラス。実行に必要なコンテキストも保持する。
    /// </summary>
    public class BasicBuiltins : MathBuiltins
    {
        private PriceList _prices;

        /// <summary>
        /// 実行対象の価格データを取得または設定する。
        /// </summary>
        public PriceList Prices
        {
            get { return _prices; }
            set
            {
                _prices = value;
                Brand = GlobalEnv.BrandData[_prices.First.Code];
            }
        }

        /// <summary>
        /// インデックスを設定または取得する。
        /// </summary>
        public int Index { get; set; }

        /// <summary>
        /// 右端のインデックスを取得または設定する。
        /// </summary>
        public int RightIndex { get; set; }

        /// <summary>
        /// 銘柄データを取得または設定する。
        /// </summary>
        public Brand Brand { get; private set; }

        /// <summary>
        /// 銘柄コード、価格リストテーブル
        /// 他銘柄の価格を参照する場合に使用する
        /// </summary>
        private readonly Dictionary<string, PriceList> _priceDataCache = new Dictionary<string, PriceList>();

        /// <summary>
        /// 銘柄コード、インデックス差分テーブル
        /// 他銘柄の価格を参照する場合に使用する
        /// </summary>
        private readonly Dictionary<string, int> _indexAdjustmentCache = new Dictionary<string, int>();

        /// <summary>
        /// 組み込み関数を実行する。
        /// </summary>
        /// <param name="name">名前</param>
        /// <param name="args">引数</param>
        /// <param name="at">int型@作用素</param>
        /// <param name="ats">string型@作用素</param>
        /// <returns></returns>
        public override Value Invoke(string name, Value[] args, int at, string ats)
        {
            if (args.Length != 0)
                return base.Invoke(name, args, at, ats);
            var prices = Prices;
            var index = Index;
            var rightIndex = RightIndex;
            var brand = Brand;
            if (ats != null)
            {
                // 他銘柄の価格リスト読み出し
                if (!_priceDataCache.ContainsKey(ats))
                    _priceDataCache[ats] = PriceData.GetPrices(ats, prices.TimeFrame);
                prices = _priceDataCache[ats];
                if (prices == null)
                    return null;

                // 他銘柄の価格リストと、実行対象銘柄のインデックス差分を算出
                if (_indexAdjustmentCache.ContainsKey(ats))
                {
                    index = Index + _indexAdjustmentCache[ats];
                    if (index < 0 || index >= prices.Count || prices[index].Date != Prices[Index].Date)
                        // キャッシュしたインデックス差分が有効でないので、再算出対象にする
                        _indexAdjustmentCache.Remove(ats);
                }
                if (!_indexAdjustmentCache.ContainsKey(ats))
                {
                    index = prices.SearchByDate(Prices[Index].Date);
                    if (index < 0)
                        return null;
                    _indexAdjustmentCache[ats] = index - Index;
                }
                rightIndex = RightIndex + index - Index;
                brand = GlobalEnv.BrandData[ats];
            }
            switch (name)
            {
                case "Index":
                    return new Value(index + at);
                case "RightIndex":
                    return new Value(rightIndex);
                case "Code":
                    return new Value(brand.Code);
                case "Market":
                    return new Value(brand.Market);
                case "Unit":
                    return new Value(brand.Unit);
            }
            if (index + at < 0 || index + at >= prices.Count)
                return null;
            var price = prices[index + at];
            switch (name)
            {
                case "Year":
                    return new Value(price.Date.Year);
                case "Month":
                    return new Value(price.Date.Month);
                case "Day":
                    return new Value(price.Date.Day);
                case "DayOfWeek":
                    return new Value((int)price.Date.DayOfWeek);
                case "Open":
                    return new Value(price.Open);
                case "High":
                    return new Value(price.High);
                case "Low":
                    return new Value(price.Low);
                case "Close":
                    return new Value(price.Close);
                case "Volume":
                    return new Value(price.Volume);
            }
            return base.Invoke(name, args, at, ats);
        }
    }
}