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

namespace NovelInterpreter.Compiler
{
	/// <summary>
	/// 吉里吉里構文を中間コード形式のXMLへ変換するクラス
	/// </summary>
	public class NovelXMLCompiler
	{
		/// <summary>
		/// 読み込んだファイルの中身
		/// 第1引数にはファイルのパスを格納
		/// </summary>
		Dictionary<string, string> fileData;

		/// <summary>
		/// プリコンパイラのコンストラクタ
		/// </summary>
		public NovelXMLCompiler()
		{
			this.fileData = new Dictionary<string, string>();
		}

		/// <summary>
		/// UTF-8でXMLを出力する
		/// </summary>
		public void Convert()
		{
			// 全てのファイルパスを探索
			foreach (var fd in fileData)
			{
				// パスの作成
				string output = Path.GetDirectoryName(fd.Key) + "\\" + Path.GetFileNameWithoutExtension(fd.Key) + ".xml";

				// データを正規表現～XML形式に変換まで
				var list = RegexParser.Matches(fd.Value);
				var parsed = ListParser.Parse(list);
				XMLDocument.Write(parsed, output);
			}
		}

		public void Convert(ref string toXML)
		{
			// 途中だよ！
		}

		/// <summary>
		/// 変換対象のファイルを追加する
		/// 読み込んだデータはUTF8に変換される
		/// </summary>
		/// <param name="path">パス</param>
		/// <param name="encoding">読み込みエンコーディング</param>
		public void AddFile(string path, Encoding encoding)
		{
			CheckFilePath(path);

			FileStream fs = new FileStream(path, FileMode.Open);
			StreamReader sr = new StreamReader(fs, encoding);

			// 読み込んだ内容をUTF-8に変換
			string data = sr.ReadToEnd();
			byte[] byteData = Encoding.UTF8.GetBytes(data);
			data = Encoding.UTF8.GetString(byteData);

			fileData.Add(path, data);	// ここで追加

			sr.Close();
			fs.Close();
		}

		/// <summary>
		/// 変換対象のファイルを追加する
		/// 読み込みエンコーディングはShift_JIS
		/// </summary>
		/// <param name="path">パス</param>
		public void AddFile(string path)
		{
			AddFile(path);
		}

		/// <summary>
		/// 変換対象のテキストを追加する
		/// </summary>
		/// <param name="name">名前</param>
		/// <param name="text">バイト型なテキスト</param>
		public void AddText(string name, byte[] text)
		{
			var enc = GetCode(text);

			if (enc == Encoding.UTF8)
			{
				var str = enc.GetString(text);	// バイト型を元のエンコードの文字列に変換
				var bytes = Encoding.UTF8.GetBytes(str);	// 変換した文字列を対象のエンコードに変換
				fileData.Add(name, Encoding.UTF8.GetString(bytes));	// アドる
			}
			else
			{
				fileData.Add(name, Encoding.UTF8.GetString(text));
			}
		}

		/// <summary>
		/// 変換対象のディレクトリを追加する
		/// </summary>
		/// <param name="path">パス</param>
		/// <param name="encoding">読み込みエンコーディング</param>
		/// <param name="reflexive">フォルダ内のフォルダを全て探索するかどうか</param>
		public void AddFolder(string path, Encoding encoding, bool reflexive = false)
		{
			List<string> directories = new List<string>();

			if (!Directory.Exists(path)) throw new DirectoryNotFoundException("ディレクトリが見つかりません：" + path);
			directories.Add(path);
			if (reflexive) GetAllDirectory(directories, path);

			// 取得したディレクトリを探索
			foreach (var p in directories)
			{
				// ファイルを全て見つけ出して探索する
				string[] fpath = Directory.GetFiles(p);
				foreach (var fp in fpath)
					if (Path.GetExtension(fp) == ".kag") AddFile(fp, encoding);
			}
		}

		/// <summary>
		/// 変換対象のディレクトリを追加する
		/// 読み込みエンコードは自動でShift-JIS
		/// </summary>
		/// <param name="path">ディレクトリのパス</param>
		/// <param name="reflexive">フォルダ内のフォルダを全て探索するかどうか</param>
		public void AddFolder(string path, bool reflexive = false)
		{
			AddFolder(path, Encoding.GetEncoding("Shift_JIS"), reflexive);
		}

		/// <summary>
		/// ファイルのパスが存在するかチェック
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		bool CheckFilePath(string path)
		{
			// ファイルの存在チェック
			if (!File.Exists(path)) throw new FileNotFoundException("ファイルが見つかりません：" + path);

			// 読み込み拡張子チェック
			string extention = Path.GetExtension(path);
			if (extention != ".kag") throw new FileLoadException("関係のないファイル：" + path);

			return true;
		}

		/// <summary>
		/// 対象ディレクトリから子ディレクトリを全て探索する
		/// </summary>
		/// <param name="directories">ディレクトリを入れるリスト</param>
		/// <param name="path">探索対象</param>
		void GetAllDirectory(List<string> directories, string path)
		{
			if (!Directory.Exists(path)) return;	// 存在しないパスは何もしない

			string[] gets = Directory.GetDirectories(path);
			foreach (var p in gets)
			{
				// ディレクトリを追加
				directories.Add(p);
				GetAllDirectory(directories, p);	// 見つかったディレクトリを探索
			}
		}

		/// <summary>
		/// 文字コードを判別する
		/// </summary>
		/// <remarks>
		/// Jcode.pmのgetcodeメソッドを移植したものです。
		/// Jcode.pm(http://openlab.ring.gr.jp/Jcode/index-j.html)
		/// Jcode.pmのCopyright: Copyright 1999-2005 Dan Kogai
		/// </remarks>
		/// <param name="bytes">文字コードを調べるデータ</param>
		/// <returns>適当と思われるEncodingオブジェクト。
		/// 判断できなかった時はnull。</returns>
		System.Text.Encoding GetCode(byte[] bytes)
		{
			const byte bEscape = 0x1B;
			const byte bAt = 0x40;
			const byte bDollar = 0x24;
			const byte bAnd = 0x26;
			const byte bOpen = 0x28;    //'('
			const byte bB = 0x42;
			const byte bD = 0x44;
			const byte bJ = 0x4A;
			const byte bI = 0x49;

			int len = bytes.Length;
			byte b1, b2, b3, b4;

			//Encode::is_utf8 は無視

			bool isBinary = false;
			for (int i = 0; i < len; i++)
			{
				b1 = bytes[i];
				if (b1 <= 0x06 || b1 == 0x7F || b1 == 0xFF)
				{
					//'binary'
					isBinary = true;
					if (b1 == 0x00 && i < len - 1 && bytes[i + 1] <= 0x7F)
					{
						//smells like raw unicode
						return System.Text.Encoding.Unicode;
					}
				}
			}
			if (isBinary)
			{
				return null;
			}

			//not Japanese
			bool notJapanese = true;
			for (int i = 0; i < len; i++)
			{
				b1 = bytes[i];
				if (b1 == bEscape || 0x80 <= b1)
				{
					notJapanese = false;
					break;
				}
			}
			if (notJapanese)
			{
				return System.Text.Encoding.ASCII;
			}

			for (int i = 0; i < len - 2; i++)
			{
				b1 = bytes[i];
				b2 = bytes[i + 1];
				b3 = bytes[i + 2];

				if (b1 == bEscape)
				{
					if (b2 == bDollar && b3 == bAt)
					{
						//JIS_0208 1978
						//JIS
						return System.Text.Encoding.GetEncoding(50220);
					}
					else if (b2 == bDollar && b3 == bB)
					{
						//JIS_0208 1983
						//JIS
						return System.Text.Encoding.GetEncoding(50220);
					}
					else if (b2 == bOpen && (b3 == bB || b3 == bJ))
					{
						//JIS_ASC
						//JIS
						return System.Text.Encoding.GetEncoding(50220);
					}
					else if (b2 == bOpen && b3 == bI)
					{
						//JIS_KANA
						//JIS
						return System.Text.Encoding.GetEncoding(50220);
					}
					if (i < len - 3)
					{
						b4 = bytes[i + 3];
						if (b2 == bDollar && b3 == bOpen && b4 == bD)
						{
							//JIS_0212
							//JIS
							return System.Text.Encoding.GetEncoding(50220);
						}
						if (i < len - 5 &&
							b2 == bAnd && b3 == bAt && b4 == bEscape &&
							bytes[i + 4] == bDollar && bytes[i + 5] == bB)
						{
							//JIS_0208 1990
							//JIS
							return System.Text.Encoding.GetEncoding(50220);
						}
					}
				}
			}

			//should be euc|sjis|utf8
			//use of (?:) by Hiroki Ohzaki <ohzaki@iod.ricoh.co.jp>
			int sjis = 0;
			int euc = 0;
			int utf8 = 0;
			for (int i = 0; i < len - 1; i++)
			{
				b1 = bytes[i];
				b2 = bytes[i + 1];
				if (((0x81 <= b1 && b1 <= 0x9F) || (0xE0 <= b1 && b1 <= 0xFC)) &&
					((0x40 <= b2 && b2 <= 0x7E) || (0x80 <= b2 && b2 <= 0xFC)))
				{
					//SJIS_C
					sjis += 2;
					i++;
				}
			}
			for (int i = 0; i < len - 1; i++)
			{
				b1 = bytes[i];
				b2 = bytes[i + 1];
				if (((0xA1 <= b1 && b1 <= 0xFE) && (0xA1 <= b2 && b2 <= 0xFE)) ||
					(b1 == 0x8E && (0xA1 <= b2 && b2 <= 0xDF)))
				{
					//EUC_C
					//EUC_KANA
					euc += 2;
					i++;
				}
				else if (i < len - 2)
				{
					b3 = bytes[i + 2];
					if (b1 == 0x8F && (0xA1 <= b2 && b2 <= 0xFE) &&
						(0xA1 <= b3 && b3 <= 0xFE))
					{
						//EUC_0212
						euc += 3;
						i += 2;
					}
				}
			}
			for (int i = 0; i < len - 1; i++)
			{
				b1 = bytes[i];
				b2 = bytes[i + 1];
				if ((0xC0 <= b1 && b1 <= 0xDF) && (0x80 <= b2 && b2 <= 0xBF))
				{
					//UTF8
					utf8 += 2;
					i++;
				}
				else if (i < len - 2)
				{
					b3 = bytes[i + 2];
					if ((0xE0 <= b1 && b1 <= 0xEF) && (0x80 <= b2 && b2 <= 0xBF) &&
						(0x80 <= b3 && b3 <= 0xBF))
					{
						//UTF8
						utf8 += 3;
						i += 2;
					}
				}
			}
			//M. Takahashi's suggestion
			//utf8 += utf8 / 2;

			System.Diagnostics.Debug.WriteLine(
				string.Format("sjis = {0}, euc = {1}, utf8 = {2}", sjis, euc, utf8));
			if (euc > sjis && euc > utf8)
			{
				//EUC
				return System.Text.Encoding.GetEncoding(51932);
			}
			else if (sjis > euc && sjis > utf8)
			{
				//SJIS
				return System.Text.Encoding.GetEncoding(932);
			}
			else if (utf8 > euc && utf8 > sjis)
			{
				//UTF8
				return System.Text.Encoding.UTF8;
			}

			return null;
		}
	}
}
