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

namespace NovelInterpreter.Interpreter
{
	/// <summary>
	/// XML形式を読み込んでパーズし、コマンド形式としてリスト化する
	/// </summary>
	public class XMLToConstructionParser
	{
		/// <summary>
		/// 構文コンテナ
		/// </summary>
		ConstructionContainer constContainer;

		/// <summary>
		/// 構文コンテナ
		/// </summary>
		public ConstructionContainer Container { get { return this.constContainer; } }

		/// <summary>
		/// コンストラクタ
		/// </summary>
		public XMLToConstructionParser()
		{
			this.constContainer = new ConstructionContainer();
		}

		/// <summary>
		/// XMLをパスから読み込み
		/// </summary>
		/// <param name="path">XMLのパス</param>
		public void LoadXMLByPath(string path)
		{
			if (File.Exists(path))
			{
				// XMLの読み込み
				FileStream fst = new FileStream(path, FileMode.Open, FileAccess.Read);
				ListupParsingXML(fst, Path.GetFileNameWithoutExtension(path));	// ファイル名を付加
			}
			else
				throw new FileNotFoundException("パスで指定したXMLが存在しません: " + path);
		}

		/// <summary>
		/// XMLを生テキストで読み込み
		/// </summary>
		/// <param name="text">生テキスト</param>
		public void LoadXMLByText(string splitName, string text)
		{
			// 読み込んだテキストをStreamに変換してXMLを開く
			Stream stream = new MemoryStream(Encoding.Unicode.GetBytes(text));
			ListupParsingXML(stream, splitName);
		}

		/// <summary>
		/// XMLを読み込んでパーズして専用のクラスへ入れる
		/// </summary>
		/// <param name="stream">XMLの実体</param>
		/// <param name="splitName">ファイル名もしくは分割名</param>
		void ListupParsingXML(Stream stream, string splitName)
		{
			XmlDocument doc = new XmlDocument();
			doc.Load(stream);	// XMLの読み込み

			// XMLのルートノードを取得⇒構文が子ノードなので取得
			XmlNode root = doc.DocumentElement;
			if (root.Name != Configuration.Config.rootNodeName) 
				throw new NovelInterpreter.Exception.InvalidDataException("無効なファイル形式です");
			XmlNodeList childs = root.ChildNodes;

			FormatFileNode(this.constContainer.listSize, splitName);

			// 全てのノードを順番に洗う
			// コンテナにはファイルから読み込んだ構文を順番に積み上げられる
			for (int i = 0; i < childs.Count; i++)
			{	// ドキュメントのルートからその子供の数だけ処理を行う
				XmlNode node = childs.Item(i);
				int number = i + constContainer.listSize + 1;
				
				// ノード名
				switch (node.Name)
				{
					case "Word":
						if (node.InnerText == "") break;	// 空白行のパス
						FormatWordNode(node, number);
						break;

					case "Label":
						FormatLabelNode(node, number, splitName);
						break;

					case "Element":	// 要素の処理
						FormatElementNode(node, number);
						break;

					default:
						throw new NovelInterpreter.Exception.InvalidDataException("無効な要素が含まれています");
				}
			}

			stream.Close();
		}

		/// <summary>
		/// ファイルの追加
		/// </summary>
		void FormatFileNode(int constructionNumber, string fileName)
		{
			Dictionary<string, string> pList = new Dictionary<string, string>();
			pList.Add("Path", fileName);

			this.constContainer.AddConstruction("File", new object[] { constructionNumber, pList });
		}

		/// <summary>
		/// 文章ノード
		/// </summary>
		/// <param name="node">要素ノード</param>
		/// <param name="constructionNumber">構文番号</param>
		void FormatWordNode(XmlNode node, int constructionNumber)
		{
			Dictionary<string, string> pList = new Dictionary<string, string>();
			pList.Add("Text", node.InnerText);

			this.constContainer.AddConstruction(node.Name, new object[] { constructionNumber, pList });
		}

		/// <summary>
		/// ラベルノード
		/// </summary>
		/// <param name="node">要素ノード</param>
		/// <param name="constructionNumber">構文番号</param>
		void FormatLabelNode(XmlNode node, int constructionNumber, string splitName)
		{
			Dictionary<string, string> pList = new Dictionary<string, string>();
			string argName, argValue;

			// ラベル名を追加(Name)
			argName = node.Attributes.Item(0).Name;
			argValue = node.Attributes.Item(0).Value;
			pList.Add(argName, argValue);

			// しおり名を追加(Shiori)
			argName = node.Attributes.Item(1).Name;
			argValue = node.Attributes.Item(1).Value;
			pList.Add(argName, argValue);

			this.constContainer.AddConstruction(splitName + "." + node.Name, new object[] { constructionNumber, pList });
		}

		/// <summary>
		/// 要素ノードを整形してなんとかする
		/// </summary>
		/// <param name="node">要素ノード</param>
		/// <param name="constructionNumber">構文番号</param>
		void FormatElementNode(XmlNode node, int constructionNumber)
		{
			XmlNodeList args = node.ChildNodes;	// 要素の下にある属性をすべて取得する
			Dictionary<string, string> pList = new Dictionary<string, string>();

			if (args.Count > 0)
			{	// 属性の名前と値を取得
				for (int cnt = 0; cnt < args.Count; cnt++)
				{
					string argName = args.Item(cnt).Attributes.Item(0).Value;
					string argValue = args.Item(cnt).Attributes.Item(1).Value;
					pList.Add(argName, argValue);
				}
			}

			// ノード名から型を生成（Elementノードの場合、名前空間が付加される）
			this.constContainer.AddConstruction("Element." + node.Attributes.Item(0).Value, new object[] { constructionNumber, pList });
		}
	}
}
