// Copyright (C) 2003,2005 Daisuke Arai <darai@users.sourceforge.jp>
// Copyright (C) 2004 M. Ishiguro <mishiguro@users.sourceforge.jp>
// Copyright (C) 2004-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: SimulateTextBox.cs,v 1.14 2008-01-12 14:25:35 panacoran Exp $

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.InteropServices;
using System.Threading;
using Protra.Lib.Db;
using Protra.Lib.Lang;
using Protra.Lib.Lang.Executer;

namespace Protra.Lib.Controls
{
	/// <summary>
	/// システムのシミュレーション用のテキストボックスです。
	/// </summary>
	public class SimulateTextBox : System.Windows.Forms.TextBox, IExecuterFactory
	{
		/// <summary>
		/// トレーディングシステム
		/// </summary>
		private TradingSystem system;
		/// <summary>
		/// 売買対象となるPriceの配列
		/// </summary>
		private Price[] prices;
		/// <summary>
		/// 売買対象となるBrand
		/// </summary>
		private Brand brand;
		/// <summary>
		/// 現在売買中のインデックス
		/// </summary>
		private int index;
		/// <summary>
		/// ログを使用するか？
		/// </summary>
		private bool useLog;
		/// <summary>
		/// インタプリタファサード
		/// </summary>
		private InterpreterFacade facade;
		/// <summary>
		/// FunctionTypeとDelegateExecuter.Delegate
		/// を対応させるハッシュ
		/// </summary>
		private Hashtable hash = new Hashtable();
		/// <summary>
		/// IExecuterを生成するFactory
		/// </summary>
		private ExecuterFactoryUnion factory;

		private delegate void AppendTextDelegate(string msg);
		private AppendTextDelegate appendTextDelegate;

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

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

			// ハッシュにデリゲートを登録します。
			hash.Add(new FunctionType("Index", 0), new DelegateExecuter.Delegate(Index));
			hash.Add(new FunctionType("Year", 0), new DelegateExecuter.Delegate(Year));
			hash.Add(new FunctionType("Month", 0), new DelegateExecuter.Delegate(Month));
			hash.Add(new FunctionType("Day", 0), new DelegateExecuter.Delegate(Day));
			hash.Add(new FunctionType("DayOfWeek", 0), new DelegateExecuter.Delegate(DayOfWeek));
			hash.Add(new FunctionType("Open", 0), new DelegateExecuter.Delegate(Open));
			hash.Add(new FunctionType("High", 0), new DelegateExecuter.Delegate(High));
			hash.Add(new FunctionType("Low", 0), new DelegateExecuter.Delegate(Low));
			hash.Add(new FunctionType("Close", 0), new DelegateExecuter.Delegate(Close));
			hash.Add(new FunctionType("Volume", 0), new DelegateExecuter.Delegate(Volume));
			hash.Add(new FunctionType("Code", 0), new DelegateExecuter.Delegate(Code));
			hash.Add(new FunctionType("Print", 1), new DelegateExecuter.Delegate(Print));
			hash.Add(new FunctionType("PrintLog", 1), new DelegateExecuter.Delegate(PrintLog));
			hash.Add(new FunctionType("Buy", 2), new DelegateExecuter.Delegate(Buy));
			hash.Add(new FunctionType("Sell", 2), new DelegateExecuter.Delegate(Sell));
			
			// 数学関数との合同Factoryを形成
			factory = new ExecuterFactoryUnion();
			factory.Add(this);
			factory.Add(new MathExecuterFactory());

			appendTextDelegate = new AppendTextDelegate(AppendText);
		}

		
		/// <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()
		{
			components = new System.ComponentModel.Container();
		}
		#endregion

		/// <summary>
		/// 全システムログを削除します。
		/// </summary>
		public static void DeleteLog()
		{
			SystemLogTable.Delete();
			if(Directory.Exists(Global.DirGlobalCache))
				Directory.Delete(Global.DirGlobalCache, true);
		}

		/// <summary>
		/// 指定されたシステムのシステムログを削除します。
		/// </summary>
		public static void DeleteLog(int systemId)
		{
			SystemLogTable.Delete(systemId);
			string dir = Path.Combine(Global.DirGlobalCache, systemId.ToString());
			if(Directory.Exists(dir))
				Directory.Delete(dir, true);
		}

		/// <summary>
		/// シミュレーションを実行します。
		/// </summary>
		/// <param name="prices">売買対象となるPriceの配列</param>
        /// <param name="worker">このメソッドを実行するBackgroundWorker</param>
        /// <param name="e">BackgroundWorkerのDoWorkイベントの引数</param>
		public void Simulate(Price[] prices, BackgroundWorker worker, DoWorkEventArgs e)
		{
			if(prices.Length == 0)
				return;
			this.prices = prices;
			brand = prices[0].Brand;
			facade.GlobalVariableTable.Clear();
			index = 0;
			if(useLog)
			{
				LoadGlobalCache();
				Split[] split = SplitTable.GetRecords(brand.Code);
				if (index > 0 && split.Length > 0 &&
					prices[prices.Length - 1].Date >= split[split.Length - 1].Date &&
					prices[index - 1].Date < split[split.Length - 1].Date)
				{
					string msg = string.Format(
						"{0} {1} 分割調整のためログを削除して再実行\r\n",
						brand.Code, brand.Name);
					BeginInvoke(appendTextDelegate, new object[] {msg});
					SystemLogTable.Delete(system.Id, brand.Id);
					index = 0;
					facade.GlobalVariableTable.Clear();
				}
			}
			while(index < prices.Length)
			{
                if (worker.CancellationPending)
                {
                    e.Cancel = true;
                    return;
                }
				facade.Execute();
				index++;
			}
			if(useLog)
				SaveGlobalCache();
		}

		/// <summary>
		/// グローバルキャッシュをロードします。
		/// </summary>
		private void LoadGlobalCache()
		{
			string file =
				Path.Combine(Global.DirGlobalCache,
				system.Id.ToString() + Path.DirectorySeparatorChar 
				+ (brand.Id / 1000) + Path.DirectorySeparatorChar
				+ brand.Id);
			if(File.Exists(file))
			{
				Stream stream = new FileStream(file, FileMode.Open);
				BinaryFormatter formatter = new BinaryFormatter();
				index = (int)formatter.Deserialize(stream);
				facade.GlobalVariableTable =
					(VariableTable)formatter.Deserialize(stream);
				stream.Close();
			}
		}

		/// <summary>
		/// グローバルキャッシュをセーブします。
		/// </summary>
		private void SaveGlobalCache()
		{
			if(! Directory.Exists(Global.DirGlobalCache))
				Directory.CreateDirectory(Global.DirGlobalCache);
			string dir = Path.Combine(Global.DirGlobalCache, system.Id.ToString());
			if(! Directory.Exists(dir))
				Directory.CreateDirectory(dir);
			dir = Path.Combine(dir, (brand.Id / 1000).ToString());
			if(! Directory.Exists(dir))
				Directory.CreateDirectory(dir);
			string file = Path.Combine(dir, brand.Id.ToString());
			Stream stream = new FileStream(file, FileMode.Create);
			BinaryFormatter formatter = new BinaryFormatter();
			formatter.Serialize(stream, index);
			formatter.Serialize(stream, facade.GlobalVariableTable);
			stream.Close();
		}

		/// <summary>
		/// トレーディングシステムを取得または設定します。
		/// </summary>
		public TradingSystem System
		{
			get { return system; }
			set
			{
				system = value;
				if(system == null)
				{
					facade = null;
					return;
				}
				// プログラムのロード
				string programFile = Path.Combine(Global.DirSystem, system.File);
				facade = new InterpreterFacade(programFile, factory);
			}
		}

		/// <summary>
		/// ログを使用するかどうか示す値を取得または設定します。
		/// </summary>
		public bool UseLog
		{
			get { return useLog; }
			set { useLog = value; }
		}

		/// <summary>
		/// IExecuterを生成します。
		/// </summary>
		/// <exception cref="Protra.Lib.Lang.NoSuchExecuterException">
		/// IExecuterが存在しない場合にthrowされます。
		/// </exception>
		/// <param name="ft">関数のタイプ</param>
		/// <returns>関数を実行するIExcecuterです。</returns>
		public IExecuter CreateExecuter(FunctionType ft)
		{
			if(hash.ContainsKey(ft))
				return new DelegateExecuter((DelegateExecuter.Delegate)hash[ft]);
			throw new NoSuchExecuterException();
		}

		/// <summary>
		/// 現在売買中のインデックスを取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>インデックスです。</returns>
		private Value Index(Value[] args, int at)
		{
			return new Value(index + at);
		}

		/// <summary>
		/// 現在売買中の年を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>年です。</returns>
		private Value Year(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Date.Year);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 現在売買中の月を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>月です。</returns>
		private Value Month(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Date.Month);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 現在売買中の日付を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>日付です。</returns>
		private Value Day(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Date.Day);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 現在売買中の曜日を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>曜日です。</returns>
		private Value DayOfWeek(Value[] args, int at)
		{
			try
			{
				return new Value((int)prices[index + at].Date.DayOfWeek);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 始値を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>始値です。</returns>
		private Value Open(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Open);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 高値を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>高値です。</returns>
		private Value High(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].High);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 安値を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>安値です。</returns>
		private Value Low(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Low);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 終値を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>終値です。</returns>
		private Value Close(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Close);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 出来高を取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>出来高です。</returns>
		private Value Volume(Value[] args, int at)
		{
			try
			{
				return new Value(prices[index + at].Volume);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 銘柄コードを取得します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>銘柄コードです。</returns>
		private Value Code(Value[] args, int at)
		{
			try
			{
				return new Value(brand.Code);
			}
			catch(IndexOutOfRangeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 引数の値をテキストボックスに出力します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>銘柄コードです。</returns>
		private Value Print(Value[] args, int at)
		{
			BeginInvoke(appendTextDelegate, new object[] {((args[0] == null) ? "null" : args[0].InnerValue.ToString()) + "\r\n"});
			return null;
		}

		/// <summary>
		/// 引数の値を現在の銘柄と日付の後にテキストボックスに出力します。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>銘柄コードです。</returns>
		private Value PrintLog(Value[] args, int at)
		{
			string msg = string.Format(
				"{0} {1} {2} {3}\r\n",
				brand.Code, brand.Name,
				prices[index + at].Date.ToString("yy/MM/dd"),
				(args[0] == null) ? "null" : args[0].InnerValue.ToString());
			BeginInvoke(appendTextDelegate, new object[] {msg});
			return null;
		}

		/// <summary>
		/// 買います。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>nullを返します。</returns>
		private Value Buy(Value[] args, int at)
		{
			int price = (int)args[0].InnerValue;
			int number = (int)args[1].InnerValue;
			if(useLog)
				SystemLogTable.Add(
					system.Id, brand.Id, prices[index + at].Date, price, number, Order.Buy);
			string msg = string.Format(
				"{0} {1} {2} {3}円 {4}株 買\r\n",
				brand.Code, brand.Name,
				prices[index + at].Date.ToString("yy/MM/dd"),
				price, number);
			BeginInvoke(appendTextDelegate, new object[] {msg});
			return null;
		}

		/// <summary>
		/// 売ります。
		/// </summary>
		/// <param name="args">引数の配列</param>
		/// <param name="at">@作用素の値</param>
		/// <returns>nullを返します。</returns>
		private Value Sell(Value[] args, int at)
		{
			int price = (int)args[0].InnerValue;
			int number = (int)args[1].InnerValue;
			if(useLog)
				SystemLogTable.Add(
					system.Id, brand.Id, prices[index + at].Date, price, number, Order.Sell);
			string msg = string.Format(
				"{0} {1} {2} {3}円 {4}株 売\r\n",
				brand.Code, brand.Name,
				prices[index + at].Date.ToString("yy/MM/dd"),
				price, number);
			BeginInvoke(appendTextDelegate, new object[] {msg});
			return null;
		}
	}
}
