// Copyright (C) 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: ChartPanel.cs 453 2013-05-28 15:31:44Z panacoran $

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Protra.Lib;
using Protra.Lib.Config;
using Protra.Lib.Data;

namespace Protra.Controls
{
    /// <summary>
    /// 複数のChartBoxを管理するクラス。
    /// </summary>
    public class ChartPanel : UserControl
    {
        private int _chartNum;
        private string _code;
        private Color _foreColor, _backColor;
        private bool _useDifferentChart;
        private bool _drawLastWeek;
        private HScrollBar _hScrollBar;
        private ChartBox _activeChartBox;
        private TimeFrame _timeFrame;
        private readonly Dictionary<TimeFrame, PriceList> _priceLists = new Dictionary<TimeFrame, PriceList>();
        private readonly Dictionary<TimeFrame, int> _rightIndex = new Dictionary<TimeFrame, int>();
        private readonly List<ChartBox> _chartBoxes = new List<ChartBox>();
        private int _splitterHeight;

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public ChartPanel()
        {
            _priceLists[TimeFrame.Daily] = _priceLists[TimeFrame.Weekly] = null;
            _rightIndex[TimeFrame.Daily] = _rightIndex[TimeFrame.Weekly] = 0;
        }

        /// <summary>
        /// 設定を反映させる。
        /// </summary>
        /// <param name="config">Protraの設定</param>
        public void SetConfig(ProtraConfig config)
        {
            ChartForeColor = Color.FromArgb(config.ChartForeColor);
            ChartBackColor = Color.FromArgb(config.ChartBackColor);
            ChartNum = config.ChartNum;
            UseDifferentChart = config.UseDifferentChart;
            for (var n = 0; n < _chartNum && n < config.ChartList.Count; n++)
            {
                var chartConfig = config.ChartList[n];
                var box = _chartBoxes[n];
                if (chartConfig.DailyFile != null)
                    box.DailyProgram = Path.Combine(Global.DirChart, chartConfig.DailyFile);
                if (chartConfig.WeeklyFile != null)
                    box.WeeklyProgram = Path.Combine(Global.DirChart, chartConfig.WeeklyFile);
                box.Height = chartConfig.Height;
                box.Proportion = box.Height / BoxesHeight;
            }
            DrawLastWeek = config.DrawLastWeek;
            Code = config.SelectedCode;
        }

        /// <summary>
        /// 設定を取得する。
        /// </summary>
        /// <param name="config"></param>
        public void GetConfig(ProtraConfig config)
        {
            config.ChartNum = _chartNum;
            config.TimeFrame = _timeFrame;
            config.SelectedCode = _code;
            config.ChartForeColor = _foreColor.ToArgb();
            config.ChartBackColor = _backColor.ToArgb();
            config.UseDifferentChart = _useDifferentChart;
            config.DrawLastWeek = _drawLastWeek;
            var configs = new List<ChartConfig>();
            foreach (var box in _chartBoxes)
                configs.Add(new ChartConfig
                    {
                        DailyFile = StripChartDirName(box.DailyProgram),
                        WeeklyFile = StripChartDirName(box.WeeklyProgram),
                        Height = box.Height,
                    });
            config.ChartList = configs;
        }

        private static string StripChartDirName(string path)
        {
            if (path == null)
                return null;
            return path.Substring(Global.DirChart.Length + 1, path.Length - Global.DirChart.Length - 1);
        }

        /// <summary>
        /// ChartBoxの数を設定する。
        /// </summary>
        [DefaultValue(0)]
        public int ChartNum
        {
            set
            {
                SuspendLayout();
                if (value < _chartNum)
                {
                    for (var i = 0; i < (_chartNum - value) * 2; i++)
                        Controls.RemoveAt(0);
                    _chartBoxes.RemoveRange(value, _chartNum - value);
                    _chartBoxes[value - 1].Dock = DockStyle.Fill;
                }
                else if (value > _chartNum)
                {
                    if (_chartNum > 0)
                        _chartBoxes[_chartNum - 1].Dock = DockStyle.Top;
                    for (var i = _chartNum; i < value; i++)
                    {
                        if (i != 0)
                        {
                            var splitter = new Splitter {Dock = DockStyle.Top, BorderStyle = BorderStyle.Fixed3D};
                            splitter.SplitterMoved += splitter_SplitterMoved;
                            _splitterHeight = splitter.Height;
                            Controls.Add(splitter);
                            Controls.SetChildIndex(splitter, 0);
                        }
                        var box = new ChartBox
                            {
                                ForeColor = _foreColor,
                                BackColor = _backColor,
                                Dock = (i == value - 1) ? DockStyle.Fill : DockStyle.Top
                            };
                        box.MouseDown += chartBox_MouseDown;
                        box.MouseMove += chartBox_MouseMove;
                        Controls.Add(box);
                        Controls.SetChildIndex(box, 0);
                        _chartBoxes.Add(box);
                    }
                }
                if (_chartNum != value)
                {
                    _chartNum = value;
                    foreach (var box in _chartBoxes)
                    {
                        box.Proportion = 1.0 / value;
                        box.Height = (int)Math.Round(BoxesHeight / value);
                    }
                }
                ResumeLayout();
            }
        }

        private double BoxesHeight
        {
            get { return Height - (_chartNum - 1) * _splitterHeight; }
        }

        /// <summary>
        /// MainFormのHScrollBarを設定する。
        /// </summary>
        public HScrollBar HScrollBar
        {
            set
            {
                _hScrollBar = value;
                value.Scroll += hScrollBar_Scroll;
            }
        }

        /// <summary>
        /// ChartBoxの前景色を設定する。
        /// </summary>
        public Color ChartForeColor
        {
            set
            {
                _foreColor = value;
                foreach (var box in _chartBoxes)
                    box.ForeColor = _foreColor;
            }
        }

        /// <summary>
        /// ChartBoxの背景色を設定する。
        /// </summary>
        public Color ChartBackColor
        {
            set
            {
                _backColor = value;
                foreach (var box in _chartBoxes)
                    box.BackColor = _backColor;
            }
        }

        /// <summary>
        /// ChartBoxのタイムフレームを取得または設定する。
        /// </summary>
        [DefaultValue(TimeFrame.Daily)]
        public TimeFrame TimeFrame
        {
            get { return _timeFrame; }
            set
            {
                _timeFrame = value;
                InvalidateChartBoxes();
            }
        }

        /// <summary>
        /// ChartBoxで表示する銘柄を取得または設定する。
        /// </summary>
        [DefaultValue(null)]
        public string Code
        {
            get { return _code; }
            set
            {
                _code = value;
                _priceLists[TimeFrame.Daily] = _priceLists[TimeFrame.Weekly] = null;
                InvalidateChartBoxes();
            }
        }

        /// <summary>
        /// 終わっていない週の週足を描画するかを設定する。
        /// </summary>
        [DefaultValue(false)]
        public bool DrawLastWeek
        {
            set
            {
                if (_drawLastWeek == value)
                    return;
                _drawLastWeek = value;
                _priceLists[TimeFrame.Weekly] = null;
                if (_timeFrame == TimeFrame.Daily)
                    return;
                InvalidateChartBoxes();
            }
        }

        /// <summary>
        /// 株価データを取得する。
        /// </summary>
        public PriceList Prices
        {
            get
            {
                if (PricesCache != null)
                    return PricesCache;
                if (_code == null)
                    return null;
                var prices = PriceData.GetPrices(_code, _timeFrame, _drawLastWeek);
                if (prices == null)
                    return null;
                PricesCache = prices;
                RightIndex = prices.Count - 1;
                return prices;
            }
        }

        private PriceList PricesCache
        {
            get { return _priceLists[_timeFrame]; }
            set { _priceLists[_timeFrame] = value; }
        }

        /// <summary>
        /// チャートの右端のインデックスを取得または設定する。
        /// </summary>
        public int RightIndex
        {
            get { return _rightIndex[_timeFrame]; }
            set { _rightIndex[_timeFrame] = value; }
        }

        /// <summary>
        /// 日足と週足で異なるチャートを使用するかを設定する。
        /// </summary>
        public bool UseDifferentChart
        {
            set
            {
                _useDifferentChart = value;
                foreach (var box in _chartBoxes)
                    box.UseDifferentChart = value;
            }
        }

        /// <summary>
        /// 価格情報を表示するデリゲート
        /// </summary>
        public Action<Price> SetPriceInfo { get; set; }

        /// <summary>
        /// 株価データを無効化する。
        /// </summary>
        public void InvalidatePrices()
        {
            _priceLists[TimeFrame.Daily] = _priceLists[TimeFrame.Weekly] = null;
            InvalidateChartBoxes();
        }

        private void InvalidateChartBoxes()
        {
            if (_chartNum == 0)
                return;
            foreach (var box in _chartBoxes)
                box.Invalidate();
            SetScrollBar();
            SetPriceInfo(Prices[RightIndex]);
        }

        private void SetScrollBar()
        {
            if (_hScrollBar == null)
                return;
            if (Prices.Count <= _chartBoxes[0].Count)
                _hScrollBar.Enabled = false;
            else
            {
                _hScrollBar.Enabled = true;
                _hScrollBar.Maximum = Prices.Count - 1;
                _hScrollBar.LargeChange = _chartBoxes[0].Count;
                _hScrollBar.Value = RightIndex - _chartBoxes[0].Count;
            }
        }

        /// <summary>
        /// SizeChangeイベントを処理する。
        /// </summary>
        /// <param name="e">イベントの引数</param>
        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            SuspendLayout();
            foreach (var box in _chartBoxes)
                box.Height = (int)Math.Round(box.Proportion * BoxesHeight);
            ResumeLayout();
            SetScrollBar();
        }

        private void hScrollBar_Scroll(object sender, ScrollEventArgs e)
        {
            if (e.Type != ScrollEventType.EndScroll)
                return;
            var index = e.NewValue + _hScrollBar.LargeChange - 1;
            if (index == RightIndex)
                return;
            RightIndex = index;
            InvalidateChartBoxes();
        }

        /// <summary>
        /// ChartBox用のコンテキストメニューを生成します。
        /// </summary>
        /// <returns>ChartBox用のコンテキストメニューです。</returns>
        private ContextMenuStrip CreateContextMenuChartBox()
        {
            var contextMenuStrip = new ContextMenuStrip();
            var dir = Global.DirChart;
            if (Directory.Exists(dir))
            {
                var dirlist = Directory.GetDirectories(dir);
                foreach (var s in dirlist)
                {
                    var menuItem = CreateContextMenuItemChartBox(s);
                    contextMenuStrip.Items.Add(menuItem);
                }
                var filelist = Directory.GetFiles(dir, "*.pt");
                foreach (var s in filelist)
                {
                    var menuItem = new ToolStripMenuItem
                        {
                            // ReSharper disable PossibleNullReferenceException
                            Text = Path.GetFileNameWithoutExtension(s).Replace("&", "&&"),
                            // ReSharper restore PossibleNullReferenceException
                            Tag = s
                        };
                    menuItem.Click += menuItemChartFile_Click;
                    contextMenuStrip.Items.Add(menuItem);
                }
            }
            return contextMenuStrip;
        }

        /// <summary>
        /// ChartBox用のコンテキストメニューのMenuItemを生成します。
        /// </summary>
        /// <returns>ChartBox用のコンテキストメニューのMenuItemです。</returns>
        private ToolStripMenuItem CreateContextMenuItemChartBox(string dir)
        {
            // ReSharper disable PossibleNullReferenceException
            var menuItem = new ToolStripMenuItem {Text = Path.GetFileName(dir).Replace("&", "&&")};
            // ReSharper restore PossibleNullReferenceException
            var dropDown = new ToolStripDropDownMenu();
            menuItem.DropDown = dropDown;
            if (Directory.Exists(dir))
            {
                var dirlist = Directory.GetDirectories(dir);
                foreach (var s in dirlist)
                {
                    var item = CreateContextMenuItemChartBox(s);
                    dropDown.Items.Add(item);
                }
                var filelist = Directory.GetFiles(dir, "*.pt");
                foreach (var s in filelist)
                {
                    var item = new ToolStripMenuItem
                        {
                            // ReSharper disable PossibleNullReferenceException
                            Text = Path.GetFileNameWithoutExtension(s).Replace("&", "&&"),
                            // ReSharper restore PossibleNullReferenceException
                            Tag = s
                        };
                    item.Click += menuItemChartFile_Click;
                    dropDown.Items.Add(item);
                }
            }
            return menuItem;
        }

        private void chartBox_MouseDown(object sender, MouseEventArgs e)
        {
            _activeChartBox = (ChartBox)sender;
            if (e.Button != MouseButtons.Right)
                return;
            var contextMenu = CreateContextMenuChartBox();
            contextMenu.Show((Control)sender, new Point(e.X, e.Y));
        }

        private void menuItemChartFile_Click(object sender, EventArgs e)
        {
            var file = (string)((ToolStripMenuItem)sender).Tag;
            _activeChartBox.Program = file;
        }

        private int _prevIndex = -1;

        private void chartBox_MouseMove(Object sender, MouseEventArgs e)
        {
            if (Prices == null)
                return;
            var index = ((ChartBox)sender).CalcIndexFromX(e.X);
            if (index < 0 || _prevIndex == index)
                return;
            _prevIndex = index;
            SetPriceInfo(Prices[index]);
            foreach (var box in _chartBoxes)
                box.DrawIndicatorValue(index);
        }

        private void splitter_SplitterMoved(Object sender, SplitterEventArgs e)
        {
            foreach (var box in _chartBoxes)
                box.Proportion = box.Height / BoxesHeight;
        }
    }
}