// Copyright (C) 2008 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: BasicBuiltins.cs 431 2011-11-06 09:43:30Z darai $

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

namespace Protra.Lib.Lang
{
    /// <summary>
    /// 基本的な組み込み関数を実行するクラス。実行に必要なコンテキストも保持する。
    /// </summary>
    public class BasicBuiltins : MathBuiltins
    {
        /// <summary>
        /// 実行対象の価格データを取得または設定する。
        /// </summary>
        public List<Price> Prices { set; get; }
        /// <summary>
        /// 期間モード（0:日足、1:週足）を取得または設定する。
        /// </summary>
        public int Mode { get; set; }
        /// <summary>
        /// インデックスを設定または取得する。
        /// </summary>
        public int Index { get; set; }
        /// <summary>
        /// 右端のインデックスを取得または設定する。
        /// </summary>
        public int RightIndex { get; set; }
        /// <summary>
        /// 銘柄データを取得する。
        /// </summary>
        public Brand Brand
        {
            get
            {
                return GlobalEnv.BrandData[Prices[0].Code];
            }
        }

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

        /// <summary>
        /// 銘柄コード、インデックス差分テーブル
        /// 他銘柄の価格を参照する場合に使用する
        /// </summary>
        private Dictionary<string, int> codeIdxdiffTable = 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)
            {
                if (ats == null)
                {
                    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);
                    }
                }
                else
                {
                    // 他銘柄の価格リスト読み出し
                    if (!codePricesTable.ContainsKey(ats))
                        if (Mode == 0)
                            codePricesTable[ats] = PriceData.Prices(ats);
                        else
                            codePricesTable[ats] = PriceData.WeeklyPrices(ats, false);
                    var prices = codePricesTable[ats];

                    // 他銘柄の価格リストと、実行対象銘柄のインデックス差分を算出
                    int index;
                    if (codeIdxdiffTable.ContainsKey(ats))
                    {
                        index = Index + codeIdxdiffTable[ats];
                        if (index < 0 || index >= prices.Count || prices[index].Date != Prices[Index].Date)
                            // キャッシュしたインデックス差分が有効でないので、再算出対象にする
                            codeIdxdiffTable.Remove(ats);
                    }

                    if (!codeIdxdiffTable.ContainsKey(ats))
                    {
                        int l, m, r;
                        l = 0;
                        r = prices.Count - 1;
                        while (l <= r)
                        {
                            m = (l + r) / 2;
                            if (prices[m].Date == Prices[Index].Date)
                            {
                                codeIdxdiffTable[ats] = m - Index;
                                break;
                            }
                            else if (prices[m].Date < Prices[Index].Date)
                                l = m + 1;
                            else
                                r = m - 1;
                        }
                    }
                    if (!codeIdxdiffTable.ContainsKey(ats))
                        return null;

                    index = Index + codeIdxdiffTable[ats];

                    switch (name)
                    {
                        case "Index":
                            return new Value(index + at);
                        case "RightIndex":
                            return new Value(RightIndex + codeIdxdiffTable[ats]);
                        case "Code":
                            return new Value(ats);
                        case "Market":
                            return new Value(GlobalEnv.BrandData[ats].Code);
                        case "Unit":
                            return new Value(GlobalEnv.BrandData[ats].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);
        }
    }
}
