module yamalib.auxil.scenariochecker;

debug {

private import std.utf;
private import std.string;
private import y4d;
private import y4d_aux.direnumerator;
private import y4d_aux.filesys;
private import y4d_draw.scenariodraw;
private import y4d_aux.lineparser;
private import yamalib.draw.textdraw;
private import yamalib.log.log;

/// シナリオのタグの正当性を検証するクラス
class ScenarioChecker {

	/// ファイル名から文法妥当性を検証する
	void check(char[] filename) {
		char[] strText = cast(char[])FileSys.readSimple(filename);
		if (!strText) {
			Log.print("ファイル名が無効です");
			return;
		}
		
		Log.print("starting scenario sntax check #########################################");

		textDrawContext.setText( toWCS(strText) );
		sd.setTextDrawContext( textDrawContext );
		listupTags();

		analyzeTag();

		foreach (int no; seNo) {
			if (no != -1) {
				Log.printError(("do'nt stop se no：" ~ .toString(no)));
			}
		}

		foreach (int no; bgmNo) {
			if (no != -1) {
				Log.printError(("do'nt stop bgm no：" ~ .toString(no)));
			}
		}

		foreach (char[] c; charId) {
			if (c) {
				Log.printError(("do'nt out chara id：" ~ c));
			}
		}

		Log.print(filename ~ "Scenario script file checked!! #########################################");
	}

	/// カレントディレクトリ階層を検索して、.htmlファイルの文法妥当性を検証する
	void checkAll() {
		
		DirEnumerator dir = new DirEnumerator;
		dir.setSubDir(false);
		foreach (char[] file; dir) {
			if (std.string.tolower(std.path.getExt(file)) == "html") {
				char[] strText = cast(char[])FileSys.readSimple(file);
				if (!strText) {
					Log.printError("ファイル名が無効です");
					return;
				}

				textDrawContext.setText(toWCS(strText));
				sd.setTextDrawContext(textDrawContext);
				listupTags();

				analyzeTag();

				foreach (int no; seNo) {
					if (no != -1) {
						Log.printWarn(("do'nt stop se no：" ~ .toString(no)));
					}
				}

				foreach (int no; bgmNo) {
					if (no != -1) {
						Log.printWarn(("do'nt stop bgm no：" ~ .toString(no)));
					}
				}

				foreach (char[] c; charId) {
					if (c) {
						Log.printWarn(("do'nt out chara id：" ~ c));
					}
				}

				Log.print(file ~ "のシナリオ解析が終了しました。");
			}
		}
	}

	/// コンストラクタ
	this() {
		textDrawContext = new TextDrawContext;
		sd = new ScenarioTextDraw;
		lineparser = new LineParser;
	}

private :


	/// タグをリストアップする
	void listupTags() {
		tags = null;
		while(sd.updateText()) {
			tags ~= sd.getUnknownTagInfo();
		}
	}

	/// エフェクトタイプの列挙
	static const char[][] effect = [
		"e01","e02","e03","e04","e05","e06","e07","e08","e09","e10",
		"e11","e12","e13","e14","e15","e16","e17","e18","e19","e20",
	];

	/// タグの解析を行います
	void analyzeTag() {

		//	どのタグに該当するかを調べる
		static const char[][] tags = [
			"readstop",	 	// 0 .読み止まり
			"char_in",		// 1. キャラクターイン
			"char_out",		// 2. キャラクターアウト
			"bg",			// 3. 背景描画
			"voice",		// 4. 音声再生
			"se",			// 5. ＳＥ再生
			"se_stop",		// 6. ＳＥ停止
			"bgm_play",		// 7. ＢＧＭの再生
			"bgm_stop",		// 8. ＢＧＭの停止
			"wait",			// 9. エフェクト処理が終わるまで待つ
			"voice_wait",	//10. ボイス再生終了を待つ
			"headline",		//11. タイトル表示
			"nextscene",	//12. 指定したシーンへ移行
			
			"hr",
			"voicewait"
		];

		int analyzedTag;
		while(analyzedTag<this.tags.length) {

			char[] strTag = cast(char[]) toUTF8( this.tags[analyzedTag].tag );
			lineparser.setLine(strTag);
			char[] cc = lineparser.getStr();
			char[] strErorr;

			// タグは小文字化して探査しよう
			foreach (inout char c; cc) {
				c = std.ctype.tolower(c);
			}

			int found = -1; // not found marker
			for(int i=0;i<tags.length;++i){
				if ( cc == tags[i]) {
					found = i;
					break;
				}
			}

			//	このタグの処理
			switch(found) {
			case -1:
				Log.printWarn( "UnkownTag<" ~ cc ~ ">" );
				break;

			case 0: // <ReadStop> : 読み止まり
//				Log.print("<ReadStop>");
				tagReadStop();
				break;

			case 1: // <Chara_In> : キャライン
				Log.print("<Chara_In>:" ~ strTag);
				tagCharaIn();
				break;

			case 2: // <Chara_Out> : キャラアウト
				Log.print("<Chara_Out>:" ~ strTag);
				tagCharaOut();

				break;

			case 3: // <BG> : 背景描画
				Log.print("<BG>");
				tagBG();
				break;

			case 4: // <Voice> : Voiceのプレイ
				Log.print("<Voice>");
				tagVoice();
				break;

			case 5: // <SE> : ＳＥの再生
				Log.print("<SE>");
				tagSe();
				break;

			case 6: // <SE_Stop> : ＳＥ停止
				Log.print("<SE_Stop>");
				tagSeStop();
				break;

			case 7: // <BGM_Play> : ＢＧＭの再生
				Log.print("<BGM_Play>");
				tagBgmPlay();
				break;

			case 8: // <BGM_Stop> : ＢＧＭの停止
				Log.print("<BGM_Stop>");
				tagBgmStop();
				break;

			case 9: // <Wait> : エフェクト処理待機
				Log.print("<Wait>");
				break;

			case 10:
				Log.print("<VoiceWait>");
				break;

			case 11:	// <HeadLine> 有効
				Log.print("<HeadLine>");
				tagHeadline();
				break;

			case 12:	// <NextScene> 有効
				Log.print("<NextScene>");
				tagNextScene();
				break;
				
			default:
				// それ以外は無視しとけ
				break;
			}

			// 解析タグを進める
			analyzedTag++;
		}	// while
	}


	/// タグ<ReadStop>を処理する
	/// @return 読み込みに成功したか
	bool tagReadStop() {
		return true;
	}

	/// タグ<CharaIn>を処理する
	/// @return 読み込みに成功したか
	bool tagCharaIn() {

		char[] strCharNo,strCharNoOld=null;
		strCharNo = lineparser.getStr();
		char[] strPos = lineparser.getStr();
		int	   stand_pos;
		char[] strEffect;
		int    effectNo = -1;
		int	   slide = 0;
		bool charin_wait = false;

		// スライドインの取得
		if (!lineparser.isEnd()) {
			slide = cast(int) lineparser.getNum(0);
		}

		// エフェクト番号の取得
		if (!lineparser.isEnd()) {
			strEffect = lineparser.getStr();

			// 小文字化して判定する
			foreach (inout char c; strEffect) {
				c = std.ctype.tolower(c);
			}

			for(int i=0; i<effect.length; ++i) {
				if (strEffect == effect[i]) {
					effectNo = i;
					break;
				}
			}
			if (effectNo==-1) {
				Log.print(("ERROR!!:<Chara_In,EffectNo>param [" ~strEffect~ "] effect no is invalied!"));
			}
		}

		// エフェクトスピードの取得
		int effectspd;
		if (!lineparser.isEnd()) {
			effectspd = cast(int) lineparser.getNum(int.max);
			if (effectspd == int.max || effectspd == 0) {
				effectspd = 1;
			}
		} else {
			effectspd = 2;
		}

		/// 処理待ちウェイトの取得
		if (!lineparser.isEnd()) {
			charin_wait = (lineparser.getNum(0)==0) ? false : true;
			if (charin_wait) {
				Log.print("CharaInWait");
			}
		}

		// 置き換えようキャラ番号の取得
		if (!lineparser.isEnd()) {
			strCharNoOld = lineparser.getStr();
		}

		// 置き換えか？
		bool replace = strCharNoOld==null ? false : true;

		int found = -1;	// not faound marker
		foreach (int i,inout char[] c; charId) {
			if (replace) {	// 指定キャラを探す
				if (c==null) continue;

				if (c == strCharNoOld) {
					found = i;
					c = strCharNo;
					break;
				}
			} else {	// あいているところに登録
				if (c==null) {
					found = i;
					c = strCharNo;
					break;
				}
			}
		}

		if (found==-1 && !replace) {
			Log.print("ERROR CharaIn[] is FULL!");
			return false;
		} else if (found==-1 && replace){
			Log.print(("ERROR replace chara not found. CharaID:" ~ strCharNoOld));
			return false;
		}

		return true;
	}

	/// タグ<CharaOut>を処理する
	/// @return 読み込みに成功したか
	bool tagCharaOut() {
		char[] strChar = lineparser.getStr();	// キャラＩＤ

		// 指定キャラを探す
		bool found;
		foreach (int i,inout char[] c; charId) {
			if (c==null) continue;

			if (c == strChar) {
				c = null;
				found = true;
				break;
			}
		}

		if (!found) {
			Log.printError(("ERROR CharaOut:[" ~strChar~"]is not found!"));
			Log.print("--------------DebugPrint-----------------");
				foreach(int i,inout char[] c; charId) {
					Log.printError((.toString(i) ~ ":Char:"~ c));
				}
			Log.printError(("--------------DebugPrint-----------------"));

			return false;
		}

		return true;
	}

	/// タグ<BG>を処理する
	/// @return 読み込みに成功したか
	bool tagBG() {
		int bgNo = cast(int) lineparser.getNum(-1);
		if (bgNo == -1) {
			Log.printError("invalied TAG <BG>!");
			return false;
		}
		return true;
	}

	/// タグ<BGM_Play>を処理する
	/// @param noAction 読み込みのみ行ってプレイはしないようにするか
	/// @return 読み込みに成功したか
	bool tagBgmPlay(bool noAction=false) {
		int bgmNo=-1,bgmVol=-1;
		int bgmFadeTime=-1;

		bgmNo = cast(int) lineparser.getNum(-1);
		bgmVol = cast(int) lineparser.getNum(-1);

		if (-1==bgmNo || -1==bgmVol) {
			Log.print("invalied TAG <BGM_Play>");
			return false;
		}

		this.bgmNo ~= bgmNo;

		return true;
	}

	/// タグ<BGM_STOP>を処理する
	/// @param noAction 読み込みのみ行ってストップはしないようにするか
	/// @return 読み込みに成功したか
	bool tagBgmStop(bool noAction=false) {
		int bgmNo = 1;
		int fade = -1;
		bgmNo = cast(int) lineparser.getNum(-1);

		if (-1==bgmNo) {
			Log.printError("invalied Tag <BGM_STOP>");
			return false;
		}
		bool found;
		foreach(inout int no; this.bgmNo) {
			if (no == bgmNo) {
				no = -1;
				found = true;
			}
		}

		if (!found) {
			Log.print(("To bgm stop #No is not play!:" ~ .toString(bgmNo)));
		}

		return true;
	}

	/// タグ<SE>を処理する
	/// @return 読み込みに成功したか
	bool tagSe() {
		int seNo=-1,vol=-1,time=-1;
		seNo = cast(int) lineparser.getNum(-1);
		vol = cast(int) lineparser.getNum(80);

		if (-1==seNo) {
			Log.printError("invalied Tag<SE>");
			return false;
		}

		if (!lineparser.isEnd()) {
			time = cast(int) lineparser.getNum(time);
		}
		if (time == -1) {
			this.seNo ~= seNo;
		}

		return true;
	}

	/// タグ<se_stop>を処理する
	/// @return 読み込みに成功したか
	bool tagSeStop() {
		int seVol = -1;
		int seLoop = -1;
		int seNo = cast(int) lineparser.getNum(-1);

		if (-1==seNo) {
			Log.printError("invalied Tag<SeStop>");
			return false;
		}
		bool found;
		foreach (inout int no; this.seNo) {
			if (no == seNo) {
				found = true;
				no = -1;
			}
		}

		if (!found) {
			Log.printWarn(("To stop seNo if not play!:" ~ .toString(seNo)));
		}

		return true;
	}

	/// タグ<Voice>を処理する
	/// @return 読み込みに成功したか
	bool tagVoice() {
		char[] name = lineparser.getStr();
		if (!name) {
			Log.print("ERROR:<Voice,name,no,vol>!");
			return false;
		}
		char[] no = lineparser.getStr();
		if (!no) {
			Log.printError("Voice tag format must be like <Voice,name,no,vol>!");
			return false;
		}
		
		return true;
	}

	/// タグ<HeadLine>を処理する
	/// @return 読み込みに成功したか
	bool tagHeadline() {
		return true;
	}

	/// タグ<NextScene>を処理する
	/// @return 読み込みに成功したか
	bool tagNextScene() {
		char[] nextSceneID = lineparser.getStr();
		Log.print(("nextSceneID:" ~ nextSceneID));
		if (nextSceneID==null) return false;
		return true;
	}

private :
	TextDrawContext textDrawContext;;
	ScenarioTextDraw sd;
	ScenarioTextDraw.UnknownTagInfo[] tags;
	LineParser lineparser;

	char[][4] charId;
	int[] bgmNo;
	int[] seNo;
}

}