using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NovelInterpreter.Interpreter.Construction;
using NovelInterpreter.Interpreter.Enum;

namespace NovelInterpreter.Interpreter
{
	/// <summary>
	/// 構文コンテナ
	/// 構文の配列をラップする
	/// </summary>
	public class ConstructionContainer
	{
		/// <summary>
		/// 現在指示している構文番号
		/// </summary>
		int currentNumber;

		/// <summary>
		/// 内部にアクセサを持っておく
		/// </summary>
		Accessor accessor;

		/// <summary>
		/// 構文リスト
		/// 構文番号は構文リストの添え字と対応している
		/// KeyValuePairの第1引数は構文名と対応
		/// </summary>
		List<KeyValuePair<string, Construction.BaseConstruction>> constructionList;

		/// <summary>
		/// ラベルのリスト
		/// ラベル名をキーに構文番号を値としている
		/// </summary>
		Dictionary<string, int> labelList;

		/// <summary>
		/// ファイルのリスト
		/// ファイル名をキーに構文番号を値としている
		/// </summary>
		Dictionary<string, int> fileList;

		/// <summary>
		/// サブルーチン用のスタック
		/// </summary>
		Stack<int> numberStack;
		
		/// <summary>
		/// ラベルリストの大きさ
		/// </summary>
		public int listSize
		{
			get { return labelList.Count; }
		}

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public ConstructionContainer()
		{
			this.constructionList = new List<KeyValuePair<string, BaseConstruction>>();
			this.labelList = new Dictionary<string, int>();
			this.fileList = new Dictionary<string, int>();
			this.numberStack = new Stack<int>();
			this.currentNumber = 0;
			this.accessor = new Accessor(this);
		}

		/// <summary>
		/// 構文の追加
		/// </summary>
		/// <param name="constructionName">構文のクラス名, Elementの場合はElement名前空間を付加させる</param>
		/// <param name="args">構文クラスのコンストラクタに必要な引数</param>
		public void AddConstruction(string constructionName, object[] args)
		{
			// 構文名を型として追加
			try
			{
				// 構文をリストに追加
				AddConstructionByClassName(constructionName, args);

				// ラベルもしくはファイルかどうかの判定⇒それぞれのリストに追加
				// 名前をキーに構文番号を値にしてリスト化している
				switch (constructionName)
				{
					case "Label":
						int lNumber = (int)args[0];
						Dictionary<string, string> lDict = args[1] as Dictionary<string, string>;
						this.labelList.Add(lDict["Name"], lNumber);
						break;

					case "File":
						int fNumber = (int)args[0];
						Dictionary<string, string> fDict = args[1] as Dictionary<string, string>;
						this.fileList.Add(fDict["Path"], fNumber);
						break;
				}
			}
			catch (ArgumentNullException e)
			{
				Console.WriteLine("必要な構文クラスが型として存在していません");
				Console.WriteLine(e.Message);
				Console.WriteLine("");
			}
			catch (MissingMethodException e)
			{
				Console.WriteLine("構文クラスにコンストラクタが実装されていません");
				Console.WriteLine(e.Message);
				Console.WriteLine("");
			}
			catch (NullReferenceException e)
			{
				Console.WriteLine("構文クラスの取得時にNULLが返りました");
				Console.WriteLine(e.Message);
				Console.WriteLine("");
			}
		}

		/// <summary>
		/// 型の名前から構文リストに追加する
		/// 型はConstruction名前空間に所属していることが前提
		/// Elementが付いているかどうかは知らん
		/// </summary>
		/// <param name="constructionName">構文の型の名前</param>
		/// <param name="args">渡したい引数</param>
		void AddConstructionByClassName(string constructionName, object[] args)
		{
			KeyValuePair<string, Construction.BaseConstruction> pair = 
				GetConstructionByClassName(constructionName, args);
			this.constructionList.Add(pair);	// 構文リストに追加
		}

		/// <summary>
		/// 型の名前から構文クラスを取得する
		/// </summary>
		/// <param name="constructionName">構文のクラス名</param>
		/// <param name="args">引数</param>
		/// <returns>構文名をキーとした構文クラス</returns>
		KeyValuePair<string, Construction.BaseConstruction> GetConstructionByClassName(string constructionName, object[] args) 
		{
			string typeName = "NovelInterpreter.Interpreter.Construction." + constructionName;
			Type type = Type.GetType(typeName);
			if (type == null) throw new ArgumentNullException(typeName);
			return new KeyValuePair<string, Construction.BaseConstruction>(
				constructionName, Activator.CreateInstance(type, args) as Construction.BaseConstruction);
		}

		/// <summary>
		/// 構文の実行
		/// </summary>
		public bool Invoke()
		{
			return this.accessor.InvokeConstruction();
		}

		/// <summary>
		/// 構文コンテナにアクセスするためのクラス
		/// </summary>
		public class Accessor
		{
			/// <summary>
			/// コンテナ
			/// </summary>
			ConstructionContainer container;

			/// <summary>
			/// 構文の名前空間
			/// </summary>
			string nameSpace;

			public Accessor(ConstructionContainer container)
			{
				this.container = container;
				this.nameSpace = "NovelInterpreter.Interpreter.Construction.";
			}

			/// <summary>
			/// 構文の実行
			/// </summary>
			public bool InvokeConstruction()
			{
				// 範囲外の場合は終端を迎えてしまった
				InvokeResult result = null;
				try
				{
					result = this.container.constructionList[this.container.currentNumber].Value.Invoke(this);
				}
				catch (ArgumentOutOfRangeException)
				{
					return false;
				}

				// 次の構文に対するアクションはどうするか
				switch (result.nextInvokeType)
				{
					case InvokeResult.ResultType.InvokeOnce:	// 次へ遷移せずにもう一度実行する
						break;

					case InvokeResult.ResultType.Next:			// 次に遷移する
						this.container.currentNumber++;
						break;

					case InvokeResult.ResultType.ResetNext:		// 構文をリセットしてから次へ遷移する
						// 引数を作成する
						// 0番には構文番号が入っていないのでここで追加する
						object[] arg = new object[result.suggestForContainerData.Count + 1];
						arg[0] = this.container.currentNumber;
						result.suggestForContainerData.ToArray().CopyTo(arg, 1);

						// 代入するためのペアの作成
						KeyValuePair<string, Construction.BaseConstruction> cpair =
							this.container.GetConstructionByClassName(this.container.constructionList[this.container.currentNumber].Key, arg);
						this.container.constructionList[this.container.currentNumber] = cpair;
						this.container.currentNumber++;
						break;
				}
				return true;
			}

			/// <summary>
			/// あるラベルの構文番号を探す
			/// </summary>
			/// <param name="targetLabel">探したいラベル</param>
			/// <returns>見つかった構文番号, 見つからない場合はintの最大値が返る</returns>
			public int SearchLabel(string targetLabel)
			{
				foreach (var item in this.container.labelList)
				{
					if (targetLabel == item.Key) return item.Value;
				}
				throw new Exception.LabelNotFoundException(targetLabel);
			}

			/// <summary>
			/// 指定した構文番号が指定した構文名かどうか
			/// </summary>
			/// <param name="constructionNumber">構文番号</param>
			/// <param name="constructionName">検査したい構文名</param>
			/// <returns>構文名が一致していればtrue</returns>
			public bool IsConstructionName(int constructionNumber, string constructionName)
			{
				if (this.container.constructionList[constructionNumber].Value.ConstructionName == constructionName)
					return true;
				return false;
			}

			/// <summary>
			/// あるファイルの構文番号を探す
			/// </summary>
			/// <param name="targetFile">指定したファイル</param>
			/// <returns>構文番号</returns>
			public int SearchFile(string targetFile)
			{
				foreach (var item in this.container.fileList)
				{
					if (targetFile == item.Key) return item.Value;
				}
				throw new Exception.FileNotFoundException(targetFile);
			}

			/// <summary>
			/// 特定の構文番号へ飛ぶ
			/// </summary>
			/// <param name="targetNumber">飛びたい番号</param>
			/// <returns>成功したかどうか</returns>
			public bool GotoNumber(int targetNumber)
			{
				// 最大値チェック
				if (targetNumber >= this.container.constructionList.Count)
					throw new Exception.ConstructionNumberOutOfRangeException(targetNumber, this.container.constructionList.Count);
				this.container.currentNumber = targetNumber;
				return true;
			}

			/// <summary>
			/// 特定のラベルへ飛ぶ
			/// </summary>
			/// <param name="targetLabel">飛びたいラベル名</param>
			/// <returns>成功したかどうか</returns>
			public void GotoLabel(string targetLabel)
			{
				this.container.currentNumber = SearchLabel(targetLabel);	// 失敗すると例外投げるので無問題
			}

			/// <summary>
			/// 特定のファイルへ飛ぶ
			/// </summary>
			/// <param name="targetFile">ファイル名</param>
			public void GotoFile(string targetFile)
			{
				this.container.currentNumber = SearchFile(targetFile);
			}

			/// <summary>
			/// 構文番号をスタックに記録する
			/// </summary>
			public void RecordNumberStack()
			{
				this.container.numberStack.Push(this.container.currentNumber);
			}

			/// <summary>
			/// 構文番号をスタックからポップする
			/// </summary>
			/// <returns>スタックの先頭に積まれた構文番号</returns>
			public int ReturnNumberStack()
			{
				return this.container.numberStack.Pop();
			}

			/// <summary>
			/// 直近の構文を探す
			/// </summary>
			/// <param name="type">検索方法</param>
			/// <param name="findName">検索したい構文名</param>
			/// <param name="findNumber">何件取得したいか, 検索取得件数</param>
			/// <returns>見つかった構文番号</returns>
			public int[] FindNearConstruction(FindType type, string findName, int findNumber=1)
			{
				int[] result = null;

				if (findNumber > 0) throw new ArgumentOutOfRangeException("findNumber", findNumber, "検索取得件数の値が0です。");
				result = new int[findNumber];
				
				Type t = Type.GetType("NovelInterpreter.Interpreter.Construction." + findName);
				if (t == null) throw new ArgumentNullException("検索した型が存在しません：" + findName);

				// 取得したい数だけループ
				int i = 0;	// 例外で使いたい
				try
				{
					for (; i < findNumber; i++)
					{
						switch (type)
						{
							case FindType.Front:
								result[i] = FindFront(findName, i);
								break;

							case FindType.Back:
								result[i] = FindBack(findName, i);
								break;

							case FindType.Both:
								result[i] = FindBoth(findName, i);
								break;
						}
					}
				}
				catch (IndexOutOfRangeException)
				{
					for (; i < findNumber; i++) result[i] = -1;	// 検索に失敗すると-1で埋めるようにしてある
				}
				throw new NotImplementedException();
				//return result;
			}

			/// <summary>
			/// 前方に向かって探索
			/// </summary>
			/// <param name="findName">調べたい構文名</param>
			/// <param name="findNumber">何番目の構文か</param>
			/// <returns>取得できた構文番号</returns>
			int FindFront(string findName, int findNumber=1)
			{
				// 現在位置から直近のものを探す
				int fNum = findNumber--;
				for (int now = this.container.currentNumber; now < this.container.listSize; now++)
				{
					// 構文の名前と探索する構文名を比較
					if (this.container.constructionList[now].Value.ConstructionName == findName)
						if (findNumber == 0) return now;	// 探索数から抜けるかどうか判断
						else fNum--;	// 探索対象じゃなければ-1
				}
				return -1;
			}

			/// <summary>
			/// 後方に向かって探索
			/// </summary>
			/// <param name="findName">調べたい構文名</param>
			/// <param name="findNumber">何番目の構文か</param>
			/// <returns>取得できた構文番号</returns>
			int FindBack(string findName, int findNumber=1)
			{
				int fNum = findNumber--;
				for (int now = this.container.currentNumber; now >= 0; now--)
				{
					if (this.container.constructionList[now].Value.ConstructionName == findName)
						if (findNumber == 0) return now;
						else fNum--;
				}
				return -1;
			}

			/// <summary>
			/// 両方向で探索, 前方優先で交互に探索を行う
			/// </summary>
			/// <param name="findName">調べたい構文名</param>
			/// <param name="findNumber">何番目の構文か</param>
			/// <returns>取得できた構文番号</returns>
			int FindBoth(string findName, int findNumber=1)
			{
				int fNum = findNumber--;
				int point = this.container.currentNumber;

				// 両端を交互に探索、先頭優先
				for (int i = 0; ; i++)
				{
					// 両端からはみ出たらとにかく脱出
					if (point + i >= this.container.listSize &&
						point - i < 0) break;

					try
					{
						if (point + i < this.container.listSize)
						{
							if (this.container.constructionList[point + i].Value.ConstructionName == findName)
								if (findNumber == 0) return point + i;
								else fNum--;
						}
					}	// 片方出ただけだと無視するだけ
					catch (IndexOutOfRangeException) { }	// 終端になったので何もしない

					try
					{
						if (point - i >= 0)
						{
							if (this.container.constructionList[point - i].Value.ConstructionName == findName)
								if (findNumber == 0) return point - i;
								else fNum--;
						}
					}
					catch (IndexOutOfRangeException) { }	// ここも同じ
				}
				return -1;
			}
		}
	}
}
