// Copyright (C) 2003 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2004 M. Ishiguro <mishiguro@users.sourceforge.jp>
// Copyright (C) 2004, 2007, 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: ChartBox.cs 306 2010-03-19 13:21:22Z panacoran $

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;
using Protra.Lib.Db;
using Protra.Lib.Lang;

namespace Protra.Controls
{
	/// <summary>
	/// チャートを描画するコントロールです。
	/// </summary>
	public class ChartBox : System.Windows.Forms.UserControl
	{
		/// <summary>
		/// 左側にあける間隔
		/// </summary>
		private const int LeftMargin = 55;
		/// <summary>
		/// 上側にあける間隔
		/// </summary>
		private const int TopMargin = 10;
		/// <summary>
		/// 右側にあける間隔
		/// </summary>
		private const int RightMargin = 10;
		/// <summary>
		/// 下側にあける間隔
		/// </summary>
		private const int BottomMargin = 20;

		/// <summary>
		/// プログラムファイル名
		/// </summary>
        private string[] programFiles = new string[2];
		/// <summary>
		/// インタプリタ
		/// </summary>
        private Interpreter[] interpreters = new Interpreter[2];
		/// <summary>
		/// 描画対象となるPriceの配列
		/// </summary>
        private Price[][] priceLists = new Price[2][];
        /// <summary>
        /// チャートの右端のインデックス
        /// </summary>
        private int[] rightIndexes = new int[2];
		/// <summary>
		/// 横軸の間隔
		/// </summary>
		private int dx = 8;
        /// <summary>
        /// 表示モード
        /// </summary>
        private int chartMode;
        /// <summary>
        /// 日足と週足で異なるチャートを使用するか(使用するが1)
        /// </summary>
        private int useDifferentChart;

		/// <summary>
		/// 必要なデザイナ変数です。
		/// </summary>
		private System.ComponentModel.Container components = null;

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public ChartBox()
		{
			// この呼び出しは、Windows.Forms フォーム デザイナで必要です。
			InitializeComponent();
		}

		/// <summary>
		/// 使用されているリソースに後処理を実行します。
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Component Designer generated code
		/// <summary>
		/// デザイナ サポートに必要なメソッドです。このメソッドの内容を
		/// コード エディタで変更しないでください。
		/// </summary>
		private void InitializeComponent()
		{
            this.SuspendLayout();
            // 
            // ChartBox
            // 
            this.Name = "ChartBox";
            this.Size = new System.Drawing.Size(976, 150);
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.ChartBox_Paint);
            this.SizeChanged += new System.EventHandler(this.ChartBox_SizeChanged);
            this.ResumeLayout(false);

		}
		#endregion

        /// <summary>
        /// 表示モードを設定する。
        /// </summary>
        public int ChartMode
        {
            set
            {
                chartMode = value;
                Invalidate();
            }
        }

        /// <summary>
        /// 日足と週足で異なるチャートを使用するかを設定する。
        /// </summary>
        public bool UseDifferentChart
        {
            set
            {
                if (value && useDifferentChart == 0)
                {
                    programFiles[1] = programFiles[0];
                    interpreters[1] = interpreters[0];
                }
                else if (!value && useDifferentChart == 1 && chartMode == 1)
                {
                    programFiles[0] = programFiles[1];
                    interpreters[0] = interpreters[1];
                }
                useDifferentChart = (value) ? 1 : 0;
            }
        }

		/// <summary>
		/// プログラムファイル名を設定する。
		/// </summary>
        /// <param name="mode">チャートモードを指定する。</param>
        /// <param name="file">プログラムファイル名を指定する。</param>
        private void SetProgramFile(int mode, string file)
        {
            programFiles[mode] = file;
            if (file == null)
            {
                interpreters[mode] = null;
                return;
            }
            try
            {
                interpreters[mode] = new Interpreter(file);
            }
            catch (ParseException exc)
            {
                interpreters[mode] = null;
                MessageBox.Show(exc.Message);
            }
        }

        /// <summary>
        /// 現在の表示モードに応じてプログラムファイルを設定する。
        /// </summary>
		public string ProgramFile
		{
			set
			{
                SetProgramFile(chartMode * useDifferentChart, value);
                Invalidate();
			}
		}

        /// <summary>
        /// 日足用のプログラムファイル名を取得あるいは設定する。
        /// </summary>
        public string DailyProgramFile
        {
            get
            {
                return programFiles[0];
            }
            set
            {
                SetProgramFile(0, value);
            }
        }

        /// <summary>
        /// 週足用のプログラムファイル名を取得あるいは設定する。
        /// </summary>
        public string WeeklyProgramFile
        {
            get
            {
                return programFiles[1];
            }
            set
            {
                SetProgramFile(1, value);
            }
        }

        /// <summary>
        /// Priceの配列を設定する。
        /// </summary>
        /// <param name="mode">チャートモードを指定する。</param>
        /// <param name="prices">Priceの配列を指定する。</param>
        public void SetPrices(int mode, Price[] prices)
        {
            priceLists[mode] = prices;
            if (prices != null)
                rightIndexes[mode] = prices.Length - 1;
            if (mode == chartMode)
                Invalidate();
        }

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

		/// <summary>
		/// 何個分の価格が描画されるかを取得します。
		/// </summary>
		public int Count
		{
			get { return (this.Width - LeftMargin - RightMargin - 1) / dx; } 
		}

        /// <summary>
        /// スクロールバーが必要かどうかを取得する。
        /// </summary>
        public bool NeedScrollBar
        {
            get
            {
                if (priceLists[chartMode] == null || priceLists[chartMode].Length < Count)
                    return false;
                return true;
            }
        }

		private void ChartBox_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
		{
            Interpreter interpreter = interpreters[chartMode * useDifferentChart];
            Price[] prices = priceLists[chartMode];
            if (interpreter == null || prices == null || prices.Length == 0)
				return;
            int rightIndex = rightIndexes[chartMode];

            Graphics g = e.Graphics;
			Brush brush = new SolidBrush(this.ForeColor);
			Pen pen = new Pen(this.ForeColor);
			Color dotColor = Color.FromArgb(
				(this.BackColor.A + this.ForeColor.A) / 2,
				(this.BackColor.R + this.ForeColor.R) / 2,
				(this.BackColor.G + this.ForeColor.G) / 2,
				(this.BackColor.B + this.ForeColor.B) / 2);
			Pen dotPen = new Pen(dotColor);
			dotPen.DashStyle = DashStyle.Dot;
			Rectangle chartRect = new Rectangle(
				LeftMargin, TopMargin,
				this.Width - LeftMargin - RightMargin - 1,
				this.Height - TopMargin - BottomMargin);
	
			// グローバル変数の設定
			interpreter.GlobalVariableTable.Clear();
			interpreter.GlobalVariableTable["$Names"] = new Value(new Value[6]);
			interpreter.GlobalVariableTable["$Colors"] = new Value(new Value[6]);
			interpreter.GlobalVariableTable["$IsTimeSeries"] = new Value(true);

			// 仮想描画によって縦軸のスケールを得る
            DrawBuiltins blt = new DrawBuiltins();
            int count = chartRect.Width / dx;
			blt.Index = Math.Max(0, rightIndex - count + 1);
            blt.Prices = prices;
			blt.X = chartRect.Right - (rightIndex - blt.Index + 1) * dx;
            blt.Dx = dx;
            blt.RightIndex = rightIndex;
            interpreter.Builtins = blt;
			while (blt.Index <= rightIndex)
			{
				try
				{
					interpreter.Execute();
				}
				catch(Exception exc)
				{
                    interpreters[chartMode * useDifferentChart] = null;
                    MessageBox.Show(exc.Message);
					return;
				}
				blt.X += dx;
				blt.Index++;
			}

			// 縦軸の値の描画
			if(blt.MaxY != double.MinValue)
			{
				if(blt.MaxY == blt.MinY)
				{
					blt.MaxY += 1;
					blt.MinY -= 1;
				}
				double band = (blt.MaxY - blt.MinY) / 5;
				double a = 0.01;
				while (true)
				{
					if (band < 10 * a)
					{
						for (double b = a; b <= 10 * a; b += a)
							if (b >= band)
							{
								band = b;
								break;
							}
						break;
					}
					a *= 10;
				}
				if (blt.MaxY < 0)
					blt.MaxY = (int)(blt.MaxY / band) * band;
				else
					blt.MaxY = Math.Ceiling(blt.MaxY / band) * band;
				if (blt.MinY < 0)
					blt.MinY = -Math.Ceiling(-blt.MinY / band) * band;
				else
					blt.MinY = (int)(blt.MinY / band) * band;
				double m = blt.MinY + band;
				double ratio = chartRect.Height / (blt.MaxY - blt.MinY);
				while(m < blt.MaxY)
				{
                    m = Math.Round(m, 2);
					string text = m.ToString();

					SizeF size = g.MeasureString(text, this.Font);
					g.DrawString(text, this.Font, brush,
						chartRect.Left - size.Width,
						(float)(chartRect.Bottom - (m - blt.MinY) * ratio - size.Height / 2));
					g.DrawLine(dotPen,
						chartRect.Left, (float)(chartRect.Bottom - (m - blt.MinY) * ratio),
						chartRect.Right, (float)(chartRect.Bottom - (m - blt.MinY) * ratio));
					m += band;
				}
			}

            int x;
			// 日付の描画
			if (interpreter.GlobalVariableTable["$IsTimeSeries"].IsTrue)
			{
				int index = Math.Max(0, rightIndex - count + 1);
                x = chartRect.Right - (rightIndex - index + 1) * dx;
				DateTime preDate = prices[index].Date;
				while(index <= rightIndex)
				{
					DateTime date = prices[index].Date;
                    int monthInterval = (chartMode == 0) ? 1 : 3;
					if((date.Month - 1) % monthInterval == 0)
						if(date.Month != preDate.Month)
						{
							string text = date.ToString("yy/MM");
							SizeF size = g.MeasureString(text, this.Font);
							g.DrawString(text, this.Font, brush,
								x - (dx + size.Width) / 2, chartRect.Bottom);
							g.DrawLine(dotPen,
								x - dx / 2, chartRect.Top,
								x - dx / 2, chartRect.Bottom);
							preDate = date;
						}
					x += dx;
					index++;
				}
			}

			// 本番の描画
			Region oldClip = g.Clip;
			g.Clip = new Region(chartRect);
            blt.Draw(g, Font, chartRect);
			g.Clip = oldClip;

			// 枠の描画
			g.DrawRectangle(pen, chartRect);

			// 指標名の描画
			Value[] names = (Value[])interpreter.GlobalVariableTable["$Names"].InnerValue;
			Value[] colors = (Value[])interpreter.GlobalVariableTable["$Colors"].InnerValue;
            x = chartRect.Left + 10;
			for (int i = 0; i < names.Length; i++)
				if (names[i] != null && colors[i] != null)
				{
					string text = (string)names[i].InnerValue;
					int rgb = (int)colors[i].InnerValue;
					SizeF size = g.MeasureString(text, this.Font);
					Rectangle rect = new Rectangle(x, chartRect.Top + 10, (int)size.Height, (int)size.Height);
					g.FillRectangle(new SolidBrush(Color.FromArgb(rgb)), rect);
					g.DrawRectangle(pen, rect);
					g.DrawString(text, this.Font, brush, rect.Right, rect.Top);
					x = (int)(rect.Right + size.Width); 
				}
		}

		private void ChartBox_SizeChanged(object sender, System.EventArgs e)
		{
			this.Invalidate();
		}
	}
}