/*
 * Copyright (c) Daisuke OKAJIMA    All rights reserved.
 * 
 * $Id$
 */
// 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.IO;
using System.Collections;
using System.Diagnostics;
using System.Text;

using Zanetti.Arithmetic;
using Zanetti.Arithmetic.Series;
using Zanetti.Indicators;

namespace Zanetti.Data
{

    //primitive indicatorをサポートするためのもの
    internal class DataFarmPrimitiveAccess
    {
        //delegateの引数になるためにこの形でないとだめ
        internal static double GetDate(TradeData tr)
        {
            return (double)tr.Farm.GetInt(tr.Offset);
        }
        internal static double GetOpen(TradeData tr)
        {
            return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.OPEN_OFFSET), tr);
        }
        internal static double GetHigh(TradeData tr)
        {
            return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.HIGH_OFFSET), tr);
        }
        internal static double GetLow(TradeData tr)
        {
            return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.LOW_OFFSET), tr);
        }
        internal static double GetClose(TradeData tr)
        {
            return AdjustPrice((double)tr.Farm.GetInt(tr.Offset + DataFarm.CLOSE_OFFSET), tr);
        }
        internal static double GetVolume(TradeData tr)
        {
            //オーバーフロー対策の一時しのぎ
            return AdjustVolume((double)(uint)tr.Farm.GetInt(tr.Offset + DataFarm.VOLUME_OFFSET), tr);
        }
        internal static double GetCreditLong(TradeData tr)
        {
            return AdjustVolume((double)tr.Farm.GetInt(tr.Offset + DataFarm.CREDITLONG_OFFSET), tr);
        }
        internal static double GetCreditShort(TradeData tr)
        {
            return AdjustVolume((double)tr.Farm.GetInt(tr.Offset + DataFarm.CREDITSHORT_OFFSET), tr);
        }

        private static double AdjustPrice(double value, TradeData tr)
        {
            double split = Env.Preference.AdjustSplit ? tr.Farm.CalcSplitRatio(tr.Date) : 1;
            if (value == 0 && GetVolume(tr) == 0)
            { //出来高がない日は価格が0と記入されているので前日の終値で代用
                TradeData pr = tr.Prev;
                return pr == null ? 0 : GetClose(tr.Prev);
            }
            else
                return tr.Farm.Brand.PriceScale * value / split;
        }
        private static double AdjustVolume(double value, TradeData tr)
        {
            double split = Env.Preference.AdjustSplit ? tr.Farm.CalcSplitRatio(tr.Date) : 1;
            return value * split;
        }
    }

    internal class NewDailyData
    {
        public int open;
        public int high;
        public int low;
        public int close;
        public int volume;
    }

    internal abstract class DataFarm : IDisposable
    {
        public const int RECORD_LENGTH = 32;

        public const int OPEN_OFFSET = 4;
        public const int HIGH_OFFSET = 8;
        public const int LOW_OFFSET = 12;
        public const int CLOSE_OFFSET = 16;
        public const int VOLUME_OFFSET = 20;
        public const int CREDITSHORT_OFFSET = 24;
        public const int CREDITLONG_OFFSET = 28;

        protected bool _isEmpty; //エラーなどで利用不能なことを示すフラグ
        protected AbstractBrand _brand;

        protected byte[] _farm;      //一次データ。同一のDataFarmオブジェクトを他の銘柄に使いまわすときもあるので、必要以上の長さが確保されることもある
        protected int _byteLength;   //_farmの論理的な長さ
        protected TradeData[] _data; //必要に応じて生成されるTradeDataの列。一目など未来の日付のデータがあると配列の長さは_farmに対応する分より大きいこともある
        protected int _filledLength;   //最新日付までの長さ

        public DataFarm()
        {
        }
        public abstract void LoadFor(AbstractBrand br);

        public AbstractBrand Brand
        {
            get
            {
                return _brand;
            }
        }

        public int TotalLength
        {
            get
            {
                return _data.Length;
            }
        }
        public int FilledLength
        {
            get
            {
                return _filledLength;
            }
        }
        public bool IsEmpty
        {
            get
            {
                return _isEmpty;
            }
        }

        public byte[] RawDataImage
        {
            get
            {
                return _farm;
            }
        }

        internal int GetInt(int offset)
        {
            if (offset >= _byteLength)
                throw new IndexOutOfRangeException();
            unsafe
            {
                fixed (byte* p = &_farm[0])
                {
                    return *(int*)(p + offset);
                }
            }
        }

        public TradeData GetByIndex(int index)
        {
            Debug.Assert(_data != null);
            if (index < 0 || index >= _data.Length)
                throw new TradeDataOverflowException(index.ToString() + " is out of range");
            TradeData td = _data[index];
            if (td != null) return td; //cache hit

            td = new TradeData(this, index, index * RECORD_LENGTH);
            _data[index] = td;
            return td;
        }

        public abstract int LastDate { get; }
        public abstract int FirstDate { get; }

        public int DateToIndex(int date)
        {
            return DateToIndex(0, _filledLength, date);
        }
        private int DateToIndex(int begin, int end, int date)
        {
            //binary search
            if (end - begin <= 1)
                return begin;
            else
            {
                int h = (begin + end) / 2;
                int t = GetByIndex(h).Date;
                if (date < t)
                    return DateToIndex(begin, h, date);
                else
                    return DateToIndex(h, end, date);
            }
        }

        //分割比率の取得
        public double CalcSplitRatio(int date)
        {
            return _brand.CalcSplitRatio(date, this.LastDate);

        }

        public void Dispose()
        {
            _farm = null;
            _data = null;
        }

        internal static int GetInt(byte[] rawdata, int offset)
        {
            Debug.Assert(rawdata.Length > 0);
            unsafe
            {
                fixed (byte* p = &rawdata[0])
                {
                    return *(int*)(p + offset);
                }
            }
        }
        internal static void SetInt(byte[] rawdata, int offset, int value)
        {
            unsafe
            {
                fixed (byte* p = &rawdata[0])
                {
                    *(int*)(p + offset) = value;
                }
            }
        }
        internal static void SetUInt(byte[] rawdata, int offset, uint value)
        {
            unsafe
            {
                fixed (byte* p = &rawdata[0])
                {
                    *(uint*)(p + offset) = value;
                }
            }
        }

        protected static int AdjustPrice(int raw, double ratio)
        {
            return (int)((double)raw / ratio);
        }
        protected static int AdjustVolume(int raw, double ratio)
        {
            return (int)((double)raw * ratio);
        }
    }

    internal class DailyDataFarm : DataFarm
    {

        protected int _extraDataOffset; //1日単位でデータの追加をしたときのために

        public DailyDataFarm()
            : base()
        {
        }
        public override void LoadFor(AbstractBrand br)
        {
            _brand = br;
            Construct(Util.GetDailyDataFileName(br.Code), 0);
        }
        public void LoadFor(AbstractBrand br, int extra_dates)
        {
            _brand = br;
            Construct(Util.GetDailyDataFileName(br.Code), extra_dates);
        }
        private void Construct(string filename, int extra_dates)
        {
            _isEmpty = true;
#if DOJIMA
			Dojima.DojimaUtil.HalfDailyDataFarmCache.Clear(_brand);
#endif
            if (File.Exists(filename))
            {
                int length = (int)new FileInfo(filename).Length;
                if (length == 0) return;

                if (_farm == null || _farm.Length < length + extra_dates * RECORD_LENGTH)
                    _farm = new byte[length + extra_dates * RECORD_LENGTH];
                int future_length = Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Daily);
                _filledLength = length / RECORD_LENGTH;
                _data = new TradeData[_filledLength + future_length];
                _byteLength = length;
                _extraDataOffset = 0;
                _isEmpty = false;

                FileStream s = null;
                try
                {
                    s = new FileStream(filename, FileMode.Open);
                    s.Read(_farm, 0, length);
                }
                finally
                {
                    if (s != null) s.Close();
                }
                // 個別銘柄の株価データの先頭にある出来高0のデータを取り除く
                var basic = _brand as BasicBrand;
                if (basic == null || basic.Market == MarketType.B || basic.Market == MarketType.Custom)
                    return;
                var idx = 0;
                for (var i = 0; i < _filledLength; i++)
                {
                    unsafe
                    {
                        var head = i * RECORD_LENGTH;
                        fixed (byte* p = &_farm[0])
                        {
                            if (*(int*)(p + head + VOLUME_OFFSET) == 0)
                                idx += RECORD_LENGTH;
                            else
                                break;
                        }
                    }
                }
                if (idx == 0)
                    return;
                _byteLength -= idx;
                _filledLength = _byteLength / RECORD_LENGTH;
                for (var i = 0; i < _byteLength; i++)
                    _farm[i] = _farm[i + idx];
            }
        }
        public void Save(string filename)
        {
            if (_farm != null)
            { //エラーハンドリングできていない
                FileStream s = new FileStream(filename, FileMode.Create);
                s.Write(_farm, 0, _byteLength + _extraDataOffset);
                s.Close();
            }
        }
        internal void WriteExtraData(int record_offset, int value)
        {
            unsafe
            {
                fixed (byte* p = &_farm[0])
                {
                    *(int*)(p + _byteLength + _extraDataOffset + record_offset) = value;
                }
            }
        }
        internal void ProgressExtraDataAddress()
        {
            _extraDataOffset += RECORD_LENGTH;
            Debug.Assert(_extraDataOffset <= _farm.Length);
        }

        //連続的に複数の日付を更新することもできるが、増加方向であることが必須
        internal void UpdateDataFarm(int date, NewDailyData td)
        {
            int ld;
            if (this.IsEmpty)
            {
                //とりあえず1日書き込める分だけ初期化
                if (_farm == null)
                {
                    _farm = new byte[RECORD_LENGTH * 200]; //この上限はどこかで取得すべきだが
                    _filledLength = 0;
                    _data = null;
                    _byteLength = 0;
                    _extraDataOffset = 0;
                }
                ld = 0;
            }
            else
                ld = this.LastDate;


            int offset;
            if (ld < date)
            {
                offset = _byteLength + _extraDataOffset; //emptyのときは常にこれ
                _extraDataOffset += RECORD_LENGTH;
            }
            else
            {
                offset = _byteLength - RECORD_LENGTH;
                do
                {
                    int t = GetInt(offset);
                    if (t == date)
                        break;
                    else if (t < date)
                    {
                        offset += RECORD_LENGTH;
                        break;
                    }
                    else
                        offset -= RECORD_LENGTH;
                } while (true);
            }

            unsafe
            {
                fixed (byte* p = &_farm[0])
                {
                    byte* a = p + offset;
                    *(int*)(a + 0) = date;
                    *(int*)(a + 4) = td.open;
                    *(int*)(a + 8) = td.high;
                    *(int*)(a + 12) = td.low;
                    *(int*)(a + 16) = td.close;
                    *(int*)(a + 20) = td.volume;
                }
            }

        }

        //次の2つはTradeDataを作らないようにしている、注意
        public override int LastDate
        {
            get
            {
                return GetInt(_byteLength - RECORD_LENGTH);
            }
        }
        public override int FirstDate
        {
            get
            {
                return GetInt(0);
            }
        }

    }
    internal class WeeklyDataFarm : DataFarm
    {
        private int _firstDate;
        private int _lastDate;

        public WeeklyDataFarm()
            : base()
        {
        }
        public override void LoadFor(AbstractBrand br)
        {
            _brand = br;
            Construct(Util.GetDailyDataFileName(br.Code));
        }

        private void Construct(string filename)
        {
            _isEmpty = true;
            if (File.Exists(filename))
            {
                int length = (int)new FileInfo(filename).Length;
                if (length > 0)
                {
                    //まずは日足を読む
                    byte[] daily = new byte[length];
                    FileStream s = null;
                    try
                    {
                        s = new FileStream(filename, FileMode.Open);
                        s.Read(daily, 0, length);
                    }
                    finally
                    {
                        if (s != null) s.Close();
                    }

                    _isEmpty = false;
                    _firstDate = GetInt(daily, 0);
                    _lastDate = GetInt(daily, daily.Length - RECORD_LENGTH);
                    var daily_begin = Util.IntToDate(GetInt(daily, 0));
                    var weekly_begin = daily_begin.AddDays(-(int)daily_begin.DayOfWeek);
                    var daily_end = Util.IntToDate(GetInt(daily, daily.Length - RECORD_LENGTH));
                    var weekly_end = daily_end.AddDays(-(int)daily_end.DayOfWeek);
                    _filledLength = (int)(weekly_end - weekly_begin).TotalDays / 7 + 1;
                    _data = new TradeData[_filledLength + Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Weekly)];

                    //byte[]部分のデータ読み
                    _farm = new byte[_data.Length * RECORD_LENGTH];
                    _byteLength = _farm.Length;
                    int offset = 0;
                    var weekly = weekly_begin;
                    for (int i = 0; i < _filledLength; i++, weekly = weekly.AddDays(7))
                    {
                        offset = FillWeeklyData(i * RECORD_LENGTH, daily, offset, Util.DateToInt(weekly));
                        if (offset >= daily.Length) break;
                    }
                }
            }
        }
        private int FillWeeklyData(int farmoffset, byte[] daily, int offset, int firstdate)
        {

            int enddate = Util.DateToInt(Util.IntToDate(firstdate).AddDays(7));

            int vol = 0, high = Int32.MinValue, low = Int32.MaxValue;
            int open = 0, close = 0, cre_long = 0, cre_short = 0;

            int today = GetInt(daily, offset);
            bool is_index = _brand.IsBuiltIn;
            // base_splitを得るのに最初の取引日である 'today' を使うのは誤り。
            // 下の、SetInt(_farm, farmoffset, wi.FirstDate);
            // で、後に式を評価する際に用いられる基準日として日曜基準で 'wi.FirstDate' を使っているのだから、
            // ここでもこの値を使うべき。 2005/3/15 T. SARUKI
            //
            double base_split = this.CalcSplitRatio(firstdate); //分割を考慮する場合は期間内の調整が要る
            while (offset <= daily.Length - RECORD_LENGTH && today < enddate)
            {
                //if(!is_index && today>20031201) Debugger.Break();
                double split = Env.Preference.AdjustSplit ? this.CalcSplitRatio(today) / base_split : 1;
                int v = AdjustVolume(GetInt(daily, offset + VOLUME_OFFSET), split);
                if (is_index || v != 0)
                { //非indexで出来高0の日は集計しない
                    if (open == 0) open = AdjustPrice(GetInt(daily, offset + OPEN_OFFSET), split);
                    close = AdjustPrice(GetInt(daily, offset + CLOSE_OFFSET), split);
                    high = Math.Max(high, AdjustPrice(GetInt(daily, offset + HIGH_OFFSET), split));
                    low = Math.Min(low, AdjustPrice(GetInt(daily, offset + LOW_OFFSET), split));
                    cre_long = AdjustVolume(GetInt(daily, offset + CREDITLONG_OFFSET), split);
                    cre_short = AdjustVolume(GetInt(daily, offset + CREDITSHORT_OFFSET), split);
                    vol += v;
                }

                offset += RECORD_LENGTH;
                if (offset < daily.Length) today = GetInt(daily, offset);
            }

            SetInt(_farm, farmoffset, firstdate);
            SetInt(_farm, farmoffset + OPEN_OFFSET, open);
            SetInt(_farm, farmoffset + HIGH_OFFSET, high);
            SetInt(_farm, farmoffset + LOW_OFFSET, low);
            SetInt(_farm, farmoffset + CLOSE_OFFSET, close);
            SetInt(_farm, farmoffset + VOLUME_OFFSET, vol);
            SetInt(_farm, farmoffset + CREDITLONG_OFFSET, cre_long);
            SetInt(_farm, farmoffset + CREDITSHORT_OFFSET, cre_short);

            return offset;
        }

        public override int LastDate
        {
            get
            {
                return _lastDate;
            }
        }
        public override int FirstDate
        {
            get
            {
                return _firstDate;
            }
        }

    }
    internal class MonthlyDataFarm : DataFarm
    {
        private int _firstDate;
        private int _lastDate;

        public MonthlyDataFarm()
            : base()
        {
        }
        public override void LoadFor(AbstractBrand br)
        {
            _brand = br;
            Construct(Util.GetDailyDataFileName(br.Code));
        }

        private void Construct(string filename)
        {
            _isEmpty = true;
            if (File.Exists(filename))
            {
                int length = (int)new FileInfo(filename).Length;
                if (length > 0)
                {
                    //まずは日足を読む
                    byte[] daily = new byte[length];
                    _isEmpty = false;
                    FileStream s = null;
                    try
                    {
                        s = new FileStream(filename, FileMode.Open);
                        s.Read(daily, 0, length);
                    }
                    finally
                    {
                        if (s != null) s.Close();
                    }

                    _firstDate = GetInt(daily, 0);
                    _lastDate = GetInt(daily, daily.Length - RECORD_LENGTH);
                    DateTime monthly_begin = new DateTime(_firstDate / 10000, (_firstDate % 10000) / 100, (_firstDate % 100));
                    DateTime monthly_end = new DateTime(_lastDate / 10000, (_lastDate % 10000) / 100, (_lastDate % 100));
                    _filledLength = (monthly_end.Year - monthly_begin.Year) * 12 + monthly_end.Month + 1 - monthly_begin.Month;

                    _data = new TradeData[_filledLength + Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Monthly)];

                    // 以下WeeklyIndexとかぶって冗長

                    //byte[]部分のデータ読み
                    _farm = new byte[_data.Length * RECORD_LENGTH];
                    _byteLength = _farm.Length;
                    DateTime yearmonth = monthly_begin;
                    int offset = 0;
                    for (int i = 0; i < _filledLength; i++)
                    {
                        offset = FillMonthlyData(i * RECORD_LENGTH, daily, offset, yearmonth);
                        if (offset >= daily.Length) break;
                        yearmonth = yearmonth.AddMonths(1);
                    }
                }
            }
        }
        // このメソッドもWeeklyIndexのFillWeeklyDataとかぶってかなり冗長
        private int FillMonthlyData(int farmoffset, byte[] daily, int offset, DateTime yearmonth)
        {

            DateTime endmonth = yearmonth.AddMonths(1);
            int enddate = endmonth.Year * 10000 + endmonth.Month * 100 + 1;

            int vol = 0, high = Int32.MinValue, low = Int32.MaxValue;
            int open = 0, close = 0, cre_long = 0, cre_short = 0;

            int today = GetInt(daily, offset);
            bool is_index = _brand.IsBuiltIn;
            // base_splitを得るのに最初の取引日である 'today' を使うのは誤り。
            // 下の、SetInt(_farm, farmoffset, yearmonth.Year * 10000 + yearmonth.Month * 100 + 1);
            // で、後に式を評価する際に用いられる基準日として月の初日である 'yearmonth.Year * 10000 + yearmonth.Month * 100 + 1' を使っているのだから、
            // ここでもこの値を使うべき。 2005/3/15 T. SARUKI
            //
            double base_split = this.CalcSplitRatio(Util.DateToInt(yearmonth.Year, yearmonth.Month, 1));
            while (offset <= daily.Length - RECORD_LENGTH && today < enddate)
            {
                double split = Env.Preference.AdjustSplit ? this.CalcSplitRatio(today) / base_split : 1;
                int v = AdjustVolume(GetInt(daily, offset + VOLUME_OFFSET), split);
                if (is_index || v != 0)
                { //非indexで出来高0の日は集計しない
                    if (open == 0) open = AdjustPrice(GetInt(daily, offset + OPEN_OFFSET), split);
                    close = AdjustPrice(GetInt(daily, offset + CLOSE_OFFSET), split);
                    high = Math.Max(high, AdjustPrice(GetInt(daily, offset + HIGH_OFFSET), split));
                    low = Math.Min(low, AdjustPrice(GetInt(daily, offset + LOW_OFFSET), split));
                    cre_long = AdjustVolume(GetInt(daily, offset + CREDITLONG_OFFSET), split);
                    cre_short = AdjustVolume(GetInt(daily, offset + CREDITSHORT_OFFSET), split);
                    vol += v;
                }

                offset += RECORD_LENGTH;
                if (offset < daily.Length) today = GetInt(daily, offset);
            }

            SetInt(_farm, farmoffset, yearmonth.Year * 10000 + yearmonth.Month * 100 + 1);
            SetInt(_farm, farmoffset + OPEN_OFFSET, open);
            SetInt(_farm, farmoffset + HIGH_OFFSET, high);
            SetInt(_farm, farmoffset + LOW_OFFSET, low);
            SetInt(_farm, farmoffset + CLOSE_OFFSET, close);
            SetInt(_farm, farmoffset + VOLUME_OFFSET, vol);
            SetInt(_farm, farmoffset + CREDITLONG_OFFSET, cre_long);
            SetInt(_farm, farmoffset + CREDITSHORT_OFFSET, cre_short);

            return offset;
        }

        public override int LastDate
        {
            get
            {
                return _lastDate;
            }
        }
        public override int FirstDate
        {
            get
            {
                return _firstDate;
            }
        }

    }
    internal class YearlyDataFarm : DataFarm
    {
        private int _firstDate;
        private int _lastDate;

        public YearlyDataFarm()
            : base()
        {
        }
        public override void LoadFor(AbstractBrand br)
        {
            _brand = br;
            Construct(Util.GetDailyDataFileName(br.Code));
        }

        private void Construct(string filename)
        {
            _isEmpty = true;
            if (File.Exists(filename))
            {
                int length = (int)new FileInfo(filename).Length;
                if (length > 0)
                {
                    //まずは日足を読む
                    byte[] daily = new byte[length];
                    _isEmpty = false;
                    FileStream s = null;
                    try
                    {
                        s = new FileStream(filename, FileMode.Open);
                        s.Read(daily, 0, length);
                    }
                    finally
                    {
                        if (s != null) s.Close();
                    }

                    _firstDate = GetInt(daily, 0);
                    _lastDate = GetInt(daily, daily.Length - RECORD_LENGTH);
                    DateTime yearly_begin = new DateTime(_firstDate / 10000, (_firstDate % 10000) / 100, (_firstDate % 100));
                    DateTime yearly_end = new DateTime(_lastDate / 10000, (_lastDate % 10000) / 100, (_lastDate % 100));
                    _filledLength = yearly_end.Year - yearly_begin.Year + 1;

                    _data = new TradeData[_filledLength + Env.CurrentIndicators.GetAddedFutureLength(ChartFormat.Yearly)];

                    // 以下WeeklyIndexとかぶって冗長

                    //byte[]部分のデータ読み
                    _farm = new byte[_data.Length * RECORD_LENGTH];
                    _byteLength = _farm.Length;
                    DateTime yearmonth = yearly_begin;
                    int offset = 0;
                    for (int i = 0; i < _filledLength; i++)
                    {
                        offset = FillYearlyData(i * RECORD_LENGTH, daily, offset, yearmonth);
                        if (offset >= daily.Length) break;
                        yearmonth = yearmonth.AddYears(1);
                    }
                }
            }
        }
        // このメソッドもWeeklyIndexのFillWeeklyDataとかぶってかなり冗長
        private int FillYearlyData(int farmoffset, byte[] daily, int offset, DateTime yearmonth)
        {

            DateTime endyear = yearmonth.AddYears(1);
            int enddate = endyear.Year * 10000 + 101;

            int high = Int32.MinValue, low = Int32.MaxValue;
            int open = 0, close = 0, cre_long = 0, cre_short = 0;
            uint vol = 0;

            int today = GetInt(daily, offset);
            bool is_index = _brand.IsBuiltIn;
            // base_splitを得るのに最初の取引日である 'today' を使うのは誤り。
            // 下の、SetInt(_farm, farmoffset, yearmonth.Year * 10000 + yearmonth.Month * 100 + 1);
            // で、後に式を評価する際に用いられる基準日として月の初日である 'yearmonth.Year * 10000 + yearmonth.Month * 100 + 1' を使っているのだから、
            // ここでもこの値を使うべき。 2005/3/15 T. SARUKI
            //
            double base_split = this.CalcSplitRatio(Util.DateToInt(yearmonth.Year, 1, 1));
            while (offset <= daily.Length - RECORD_LENGTH && today < enddate)
            {
                double split = Env.Preference.AdjustSplit ? this.CalcSplitRatio(today) / base_split : 1;
                int v = AdjustVolume(GetInt(daily, offset + VOLUME_OFFSET), split);
                if (is_index || v != 0)
                { //非indexで出来高0の日は集計しない
                    if (open == 0) open = AdjustPrice(GetInt(daily, offset + OPEN_OFFSET), split);
                    close = AdjustPrice(GetInt(daily, offset + CLOSE_OFFSET), split);
                    high = Math.Max(high, AdjustPrice(GetInt(daily, offset + HIGH_OFFSET), split));
                    low = Math.Min(low, AdjustPrice(GetInt(daily, offset + LOW_OFFSET), split));
                    cre_long = AdjustVolume(GetInt(daily, offset + CREDITLONG_OFFSET), split);
                    cre_short = AdjustVolume(GetInt(daily, offset + CREDITSHORT_OFFSET), split);
                    vol += (uint)(v / 10);
                }

                offset += RECORD_LENGTH;
                if (offset < daily.Length) today = GetInt(daily, offset);
            }

            SetInt(_farm, farmoffset, yearmonth.Year * 10000 + 101);
            SetInt(_farm, farmoffset + OPEN_OFFSET, open);
            SetInt(_farm, farmoffset + HIGH_OFFSET, high);
            SetInt(_farm, farmoffset + LOW_OFFSET, low);
            SetInt(_farm, farmoffset + CLOSE_OFFSET, close);
            SetUInt(_farm, farmoffset + VOLUME_OFFSET, vol);
            SetInt(_farm, farmoffset + CREDITLONG_OFFSET, cre_long);
            SetInt(_farm, farmoffset + CREDITSHORT_OFFSET, cre_short);

            return offset;
        }

        public override int LastDate
        {
            get
            {
                return _lastDate;
            }
        }
        public override int FirstDate
        {
            get
            {
                return _firstDate;
            }
        }

    }

    //他の銘柄から導出される銘柄
    internal class DerivedDataFarm : DataFarm
    {
        private int _firstDate;
        private int _lastDate;
        private DerivedBrand _derivedBrand;
        private ChartFormat _chartFormat;

        public DerivedDataFarm(DerivedBrand br, ChartFormat fmt)
            : base()
        {
            _derivedBrand = br;
            _chartFormat = fmt;
        }
        public override void LoadFor(AbstractBrand br)
        {
            Debug.Assert(br is DerivedBrand);
            _brand = br;
            _derivedBrand = (DerivedBrand)br;
            Construct(_derivedBrand);
        }

        private void Construct(DerivedBrand br)
        {
            DataFarm[] fs = new DataFarm[br.Dependencies.Length];
            int len = Int32.MaxValue;
            int shortest_farm_index = 0;
            for (int i = 0; i < fs.Length; i++)
            {
                DataFarm f = Env.BrandCollection.ReserveFarm(br.Dependencies[i], _chartFormat);
                if (f.IsEmpty)
                {
                    _isEmpty = true;
                    return; //一つでも利用不可があればダメ
                }
                fs[i] = f;
                if (f.FilledLength < len)
                {
                    shortest_farm_index = i;
                    len = f.FilledLength;
                }
            }

            DataFarm shortest_farm = fs[shortest_farm_index];
            if (_farm == null || _farm.Length < len * RECORD_LENGTH) _farm = new byte[len * RECORD_LENGTH];
            _byteLength = len * RECORD_LENGTH;

            _data = new TradeData[len + Env.CurrentIndicators.GetAddedFutureLength(_chartFormat)];
            _filledLength = len;
            _isEmpty = false;

            _firstDate = shortest_farm.GetByIndex(0).Date;
            _lastDate = shortest_farm.GetByIndex(shortest_farm.FilledLength - 1).Date;
            //データの構築 本当はここも遅延評価すると効率的だが
            FillData(len, shortest_farm_index, br, fs);
        }

        private void FillData(int len, int shortest_farm_index, DerivedBrand br, DataFarm[] deps)
        {
            int[] indexmap = new int[deps.Length];
            EvalResult[][] args = new EvalResult[4][];
            for (int i = 0; i < 4; i++)
            {
                args[i] = new EvalResult[deps.Length];
                for (int j = 0; j < deps.Length; j++) args[i][j] = new EvalResult(0);
            }
            Indicator[] inds = new Indicator[] { 
												   Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Open), 
												   Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.High), 
												   Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Low), 
												   Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Close)};

            TradeData[] tds = new TradeData[deps.Length];

            Evaluator ev = new Evaluator(br.Name);

            for (int i = 0; i < len; i++)
            {
                ev.BaseIndex = i;

                //日付の決定
                int date = deps[shortest_farm_index].GetByIndex(i).Date;

                int farmoffset = i * RECORD_LENGTH;
                for (int j = 0; j < deps.Length; j++)
                {
                    int candidate = indexmap[j] + 1; //多くの場合日付とindexは一致しているので、DateToIndexの実行回数を減らすためindexmapを用意
                    TradeData td = candidate < deps[j].TotalLength ? deps[j].GetByIndex(candidate) : null;
                    if (td == null || td.Date != date)
                    {
                        candidate = deps[j].DateToIndex(date);
                        td = deps[j].GetByIndex(candidate);
                    }
                    indexmap[j] = candidate;

                    for (int k = 0; k < inds.Length; k++)
                        args[k][j].DoubleVal = td.GetValue(inds[k]);
                }

                //日付
                SetInt(_farm, farmoffset, date);

                //4本値の計算
                Expression expr = br.Expression;
                ev.Args = args[0];
                int open = (int)((EvalResult)expr.Apply(ev)).DoubleVal;
                SetInt(_farm, farmoffset + OPEN_OFFSET, open);
                ev.Args = args[3];
                int close = (int)((EvalResult)expr.Apply(ev)).DoubleVal;
                SetInt(_farm, farmoffset + CLOSE_OFFSET, close);
                ev.Args = args[1];
                int v1 = (int)((EvalResult)expr.Apply(ev)).DoubleVal;
                ev.Args = args[2];
                int v2 = (int)((EvalResult)expr.Apply(ev)).DoubleVal;

                //計算式により、それぞれの高値・安値で計算したものが結果としてどうなるかは変わってしまう
                SetInt(_farm, farmoffset + HIGH_OFFSET, Math.Max(Math.Max(open, close), Math.Max(v1, v2)));
                SetInt(_farm, farmoffset + LOW_OFFSET, Math.Min(Math.Min(open, close), Math.Min(v1, v2)));
            }
        }

        public override int LastDate
        {
            get
            {
                return _lastDate;
            }
        }
        public override int FirstDate
        {
            get
            {
                return _firstDate;
            }
        }
    }



    //internal delegate double Calculate(Indicator indicator, TradeData data);

    //節の種類
    internal enum Fushi
    {
        Unknown,
        None,
        High,
        Low
    }

    /// 日足・週足・月足などの1件のレコード
    internal class TradeData
    {
        private DataFarm _farm;
        private int _index;
        private int _offset;
        private double[] _data;
        private Fushi _fushi;

        public TradeData(DataFarm farm, int index, int offset)
        {
            _farm = farm;
            _index = index;
            _offset = offset;
            _data = new double[Env.CurrentIndicators.IndicatorCount];
            _fushi = Fushi.Unknown;
            for (int i = 0; i < _data.Length; i++)
                _data[i] = Double.NaN;
        }
        public DataFarm Farm
        {
            get
            {
                return _farm;
            }
        }
        public int Index
        {
            get
            {
                return _index;
            }
        }
        public int Offset
        {
            get
            {
                return _offset;
            }
        }
        public TradeData Prev
        {
            get
            {
                return _index > 0 ? _farm.GetByIndex(_index - 1) : null;
            }
        }
        public TradeData Next
        {
            get
            {
                return _index < _farm.TotalLength - 1 ? _farm.GetByIndex(_index + 1) : null;
            }
        }
        public bool IsFuture
        {
            get
            {
                return _index >= _farm.FilledLength;
            }
        }
        public bool CoversDate(int date)
        {
            if (date == this.Date)
                return true;
            else
            {
                int c = this.Date;
                if (c > date)
                    return false;
                else
                {
                    TradeData next = this.Next;
                    return next != null && date < next.Date;
                }
            }
        }

        public double GetValue(Indicator indicator)
        {
            double t = _data[indicator.LaneID];
            //overflowによる演算不可はPositiveInfinityであらわす
            if (Double.IsPositiveInfinity(t)) return Double.NaN;
            if (!Double.IsNaN(t)) return t; //キャッシュにヒット

            try
            {
                if (indicator.CheckRange(this))
                {
                    t = indicator.Calculate(this);
                    _data[indicator.LaneID] = t;
                }
                else
                    t = Double.NaN;
                return t;
            }
            catch (TradeDataOverflowException)
            {
                //Debug.WriteLine("Out of range!");
                _data[indicator.LaneID] = Double.PositiveInfinity;
                return Double.NaN;
            }
        }
        public int Date
        {
            get
            {
                return (int)GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Date));
            }
        }
        public double Open
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Open));
            }
        }
        public double High
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.High));
            }
        }
        public double Low
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Low));
            }
        }
        public double Close
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Close));
            }
        }
        public double Volume
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.Volume));
            }
        }
        public double CreditLong
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.CreditLong));
            }
        }
        public double CreditShort
        {
            get
            {
                return GetValue(Env.CurrentIndicators.GetPrimitive(PrimitiveIndicator.CreditShort));
            }
        }

        //節の計算
        public Fushi Fushi
        {
            get
            {
                if (_fushi != Fushi.Unknown) return _fushi;

                double h1 = Double.MinValue;
                double l1 = Double.MaxValue;
                double h2 = Double.MinValue;
                double l2 = Double.MaxValue;
                int fushi = Env.Preference.FushiRange;
                //あまり端に表示しても仕方ない
                if (_index < fushi || _index > _farm.FilledLength - fushi)
                {
                    _fushi = Fushi.None;
                    return _fushi;
                }
                for (int i = _index - fushi; i < _index; i++)
                {
                    h1 = Math.Max(h1, _farm.GetByIndex(i).High);
                    l1 = Math.Min(l1, _farm.GetByIndex(i).Low);
                }
                for (int i = _index + 1; i < _index + fushi; i++)
                {
                    h2 = Math.Max(h2, _farm.GetByIndex(i).High);
                    l2 = Math.Min(l2, _farm.GetByIndex(i).Low);
                }

                //過去に同値があるときは無視、未来にあるときは節
                if (h1 < this.High && h2 <= this.High)
                    _fushi = Fushi.High;
                else if (l1 > this.Low && l2 >= this.Low)
                    _fushi = Fushi.Low;
                else
                    _fushi = Fushi.None;

                return _fushi;
            }
        }

    }

    internal class TradeDataOverflowException : ApplicationException
    {
        public TradeDataOverflowException(string msg)
            : base(msg)
        {
        }
    }

    internal class IndicatorTimeSeries : TimeSeries
    {
        protected DataFarm _farm;
        protected int _begin;
        protected int _end;
        protected Indicator _indicator;

        public IndicatorTimeSeries(DataFarm farm, Indicator ind, int begin, int end)
        {
            _farm = farm;
            _begin = begin;
            _end = end;
            _indicator = ind;
        }

        public override int Count
        {
            get
            {
                return _end - _begin;
            }
        }
        public int BeginIndex
        {
            get
            {
                return _begin;
            }
        }
        public int EndIndex
        {
            get
            {
                return _end;
            }
        }
        public override double LastValue
        {
            get
            {
                return _farm.GetByIndex(_end - 1).GetValue(_indicator);
            }
        }


        protected class IndicatorCursor : TimeSeries.Cursor
        {
            private int _index;
            private IndicatorTimeSeries _parent;

            public IndicatorCursor(IndicatorTimeSeries parent)
            {
                _parent = parent;
                _index = _parent._begin;
            }
            public override bool HasNext
            {
                get
                {
                    return _index < _parent._end;
                }
            }
            public override double Next
            {
                get
                {
                    return _parent._farm.GetByIndex(_index++).GetValue(_parent._indicator);
                }
            }
        }

        public override Cursor CreateCursor()
        {
            return new IndicatorCursor(this);
        }
    }

}