module yamalib.auxil.list2scenario;

debug {

private import dtmpl.dom;

private import std.stream;
private import std.conv;
private import std.string;

private import y4d_aux.filesys;
private import y4d_aux.lineparser;
private import y4d_aux.widestring;
private import ytl.y4d_result;

private import yamalib.draw.scenariodraw;

private import yamalib.log.log;

/**
	エクセルリストから生成したＣＳＶを元に
	シナリオスクリプトファイルを生成する。
*/
class List2Script {

public:

	/// コンストラクタ
	this(char[] voiceDirId, char[] charaListFile) {
		// リストの列定義ファイルを読み込む
		Dom domRoot = Dom.parse( 
						clearText (
							cast(char[]) FileSys.read(cast(char[]) "data/config/list.xml") ) );

		Dom domColumns = domRoot.get(cast(char[]) "list").get(cast(char[]) "columns");
		
		foreach(inout Dom d; domColumns.array) {
			if ( d.value == "column" ) {
				cols ~= setCoumnAtr(d);
			}
		}
		
		// インデックス番号の構築
		int addIndex;
		int tmp;
		int num;
		for (int i; i < cols.length; ++i) {
			if (cols[i].optionIndex != 0) {
				tmp = cols[i].optionIndex + 1;
				num = cols[i].optionNum;
			}
			
			cols[i].index = i;
			
			indexs ~= i + addIndex;
			
			if (tmp > 0 ) {
				if ( --tmp == 0 ) {
					addIndex += num;
				}
			}
		}
		
		Log.print("colnum: %s\n", cols.length);
		foreach ( inout str; charId) {
			str ~= "";
		}
		
		// キャラクター情報読み込み
		readCharaInfo(charaListFile);
		
		// ヴォイスＩＤとキャラ番号のマップ
		voiceCharaIdMap["02"] = cast(char[]) "tatuwo";
		voiceCharaIdMap["03"] = cast(char[]) "midoriko";
		voiceCharaIdMap["04"] = cast(char[]) "alice";
		voiceCharaIdMap["05"] = cast(char[]) "tomie";
		voiceCharaIdMap["06"] = cast(char[]) "tadano";
		voiceCharaIdMap["07"] = cast(char[]) "anzai";
		voiceCharaIdMap["08"] = cast(char[]) "rubi";
		voiceCharaIdMap["09"] = cast(char[]) "yuiri";
		voiceCharaIdMap["10"] = cast(char[]) "hayami";
		voiceCharaIdMap["11"] = cast(char[]) "kuramochi";
		voiceCharaIdMap["12"] = cast(char[]) "jicho";
		voiceCharaIdMap["13"] = cast(char[]) "aya";
		voiceCharaIdMap["15"] = cast(char[]) "tasuku";
		
		const char[] voiceDirTmp = "snd/voice/{0}/voice.lst";
		char[] voiceLstFilename = cast(char[]) std.string.replace( voiceDirTmp, "{0}", voiceDirId ); 
		
		// リスト読み込み ここをシナリオごとに直す必要有り！！
		ubyte[] mem = cast(ubyte[]) FileSys.read( voiceLstFilename );
		if (mem !is null) {
			// メモリストリーム
			MemoryStream m = new MemoryStream(mem);
			LineParser lp = new LineParser();
			
			while (!m.eof) {
				char[] linebuf = cast(char[]) m.readLine();
				lp.setLine( linebuf );
				
				lp.getStr();
				voiceIdLst ~= lp.getStr();
			}
			
			m.close();
		}
		
		
/++		
		// 空
		cols ~= new ColumnAtr();

		// シーンNO
		cols ~= new ColumnAtr("シーンNO", false);

		// 背景No
		cols ~= new ColumnAtr("背景NO", true, BG );
		// 背景名称
		cols ~= new ColumnAtr("背景名称", false);

		// セリフキャラ番号
		cols ~= new ColumnAtr("セリフキャラ番号", false);
		// セリフキャラ名称
		cols ~= new ColumnAtr("セリフキャラ名称", false);

		// 左キャラ番号
		cols ~= new ColumnAtr("左キャラ番号", true, CHARAIN);
		// 左キャラ名称
		cols ~= new ColumnAtr("左キャラ名称", false);
		// 左キャラユニークID
		cols ~= new ColumnAtr("左キャラユニークID", false);
		// 左キャラポーズ名称
		cols ~= new ColumnAtr("左キャラポーズ名称", false);
		// 左キャラ表情名称
		cols ~= new ColumnAtr("左キャラ表情名称", false);

		// 中キャラ番号
		cols ~= new ColumnAtr("中キャラ番号", true, CHARAIN);
		// 中キャラ名称
		cols ~= new ColumnAtr("中キャラ名称", false);
		// 中キャラユニークID
		cols ~= new ColumnAtr("中キャラユニークID", false);
		// 中キャラポーズ名称
		cols ~= new ColumnAtr("中キャラポーズ名称", false);
		// 中キャラ表情名称
		cols ~= new ColumnAtr("中キャラ表情名称", false);

		// 右キャラ番号
		cols ~= new ColumnAtr("右キャラ番号", true, CHARAIN);
		// 右キャラ名称
		cols ~= new ColumnAtr("右キャラ名称", false);
		// 右キャラユニークID
		cols ~= new ColumnAtr("右キャラユニークID", false);
		// 右キャラポーズ名称
		cols ~= new ColumnAtr("右キャラポーズ名称", false);
		// 右キャラ表情名称
		cols ~= new ColumnAtr("右キャラ表情名称", false);

		// 音楽番号
		cols ~= new ColumnAtr("音楽番号", true, BGM_PLAY);
		// 曲名
		cols ~= new ColumnAtr("曲名", false);
		// 効果音番号
		cols ~= new ColumnAtr("効果音番号", true, SE);
		// 効果音名称
		cols ~= new ColumnAtr("効果音名称", false);
		// テキスト
		cols ~= new ColumnAtr("テキスト", true, TEXT);
		// 備考
		cols ~= new ColumnAtr("備考", false);
++/		
	}
	
	/// コメント出力フラグの設定
	static void setComment(bool b) {
		bComment = b;
	}
	
	/// コメント出力フラグの取得
	static bool getComment() {
		return bComment;
	}
	
	/// ファイルのコンバート
	bool convert(char[] inFilename, char[] outFilename) {
		// タグ変換テーブル定義の読み込み
		domRoot = Dom.parse( 
					clearText(
						cast(char[]) FileSys.read(cast(char[]) "data/config/tagConvert.xml") ) );
Log.print("xml LOAD");		
		// リスト読み込み
		ubyte[] mem = cast(ubyte[]) (FileSys.read(inFilename));
		if (mem is null) return false; // 読み込みエラー
		// メモリストリーム
		MemoryStream m = new MemoryStream(mem);

		int		nLine = 0;
		int		nColNum = 0;
		lineParser = new LineParser();
		listLineNo = 0;
		
		try {
			while (!m.eof) {
				char[] linebuf =  cast(char[]) m.readLine();
				lineParser.setLine( linebuf );
				//	ライン パーサーに文字列をセットする
				
				lineInfo = null;
				lineTags = null;
				while (!lineParser.isEnd()) {
					lineInfo ~= lineParser.getStr();
				}
	
				for (int i; i < cols.length; ++i) {
					// 列属性を調べて処理を行う
					if ( cols[i].enable ) {
//printf("index:%d dataINdex:%d ", i, indexs[i]);	
						// 実際に書きにいく
						write(cols[i], indexs[i]);
	
					} else if ( cols[i].comment ) {
						// コメントをのせる
						writeComment(cols[i], indexs[i]);
	
					}
				}
			
				buffer ~= formatTagInfo();
				
				listLineNo++;
			}
		
		} catch (Error e) {
			Log.print("Error line:%s in %s", listLineNo, inFilename);
			FileSys.writeSimple(outFilename, buffer);
			throw e;
		} catch (Object e) {
			Log.print("Error line:%s in %s", listLineNo, inFilename);
			FileSys.writeSimple(outFilename, buffer);
			Log.print("%s", e.toString());
		}
		
		// あたまに <HR> タグがあるので除去する
		int removePos = ifind(buffer, "<HR>");
		if (-1 != removePos) {
			buffer = buffer[removePos+4 .. length-1]; 
		}
		
		// ファイル書き出し！		
		FileSys.writeSimple(outFilename, buffer);
		Log.print("LOG : Convert complete! : %s", inFilename);
			
		return true;
	}

private:

/*
	static const char[] BG = "Background";
	static const char[] BGM_PLAY = "BgmPlay";
	static const char[] BGM_STOP = "BgmStop";
	
	
	static const char[]  = "SePlay";
	static const char[]  = "SeStop";
	static const char[]  = "Voice";
	static const char[]  = "NextScene";
	static const char[]  = "Headline";
	static const char[]  = "CharIn";
	static const char[]  = "CharOut";
	static const char[]  = "ReadStop";
	static const char[]  = "TEXT";
	static const char[] SCENENO = "SCENARIO";

	static const char[]  = "<BR>";
	static const char[] STR_CONMA = ",";
	static const char[] COMMENT = "COMMENT:";
	static const char[] TAG_READSTOP = "<ReadStop>";
	*/


	/// 現在のタグ情報を優先順位にならべて修正する
	char[] formatTagInfo() {
		char[] lineBuffer;
		TagInfo[] orderedTags = sortTagInfoByOrder();
		foreach(inout TagInfo tag; orderedTags) {
			lineBuffer ~= tag.str;
		}
		
		return lineBuffer;
	}
	
	TagInfo[] sortTagInfoByOrder() {
		char[][] orderDef = getOrder();
		TagInfo[] result;

		foreach (char[] order; orderDef) {
			foreach(inout TagInfo tag; lineTags) {
				if (order == tag.prefix) {
					result ~= tag;
				}
			}
		}
		return result;
	}
	
	/// 書き出し順序
	char[][] getOrder() {
		char[][] order = null;
		order ~= cast(char[]) SCENENO; // it mean <HR>
		order ~= cast(char[]) BG;
		order ~= cast(char[]) BGM_PLAY; // 通常プレイ命令が先で次にストップ（→ＢＧＭ切り替えで無音時間を作らない）
		order ~= cast(char[]) BGM_STOP;
		order ~= cast(char[]) NEXTSCENE;
		order ~= cast(char[]) CHARAIN;
		order ~= cast(char[]) CHARAOUT;
		order ~= cast(char[]) NEXTSCENE;
		order ~= cast(char[]) HEADLINE;
		order ~= cast(char[]) SE_PLAY;
		order ~= cast(char[]) SE_STOP;
		order ~= cast(char[]) VOICE;
		order ~= cast(char[]) TEXT;
		order ~= cast(char[]) READSTOP;
		order ~= cast(char[]) NEW_LINE;
		return order;
	}
	
	/// 指定属性の書き込み
	void write(ColumnAtr col, int index) {
		char[] prefix = analyzeTag(col, index);
		// これが返ってこないということは、NOPなのだ
		if (prefix is null) return;
printf("prefix:%*s\n", prefix);
//		buffer ~= tagFormat(col, index, prefix);
		
		auto tag = new TagInfo();
		tag.str = tagFormat(col, index, prefix);
		tag.prefix = prefix;
		lineTags ~= tag;
		
	}
	
	/// コメントを書き出す
	void writeComment(ColumnAtr col, int index) {
		char[] strBuffer;
		
		// コメントフラグがOFFであるか、コメントが未記入ならなにもしない
		if (!bComment || lineInfo[index] is null) return;
		
		// タグ開始を設定
		buffer ~= "<" ~ COMMENT ~ lineInfo[index] ~ ">";
		
		// 見栄えのために改行するか
		if ( col.newline ) {
			buffer ~= "\r\n";
		}
	}
	
	char[] createThumbnailInfoTag() {
		char[] result = cast(char[]) "<thuminfo";
		
		// 背景
		result ~= " BG=" ~ bgNo;

		// キャラクター
		foreach(i,id;charId) {
			if (id is null) {
				continue;
			}
			result ~= " chara" ~ std.string.toString(i) ~ "=" ~ charaMap[id].charaId 
				~ charaMap[id].fileId;
		}
		
		// BGM
		result ~= " BGM=" ~ std.string.toString(bgmNo);
		result ~= " BGM_LOOP=" ~ std.string.toString(bgmLoop);
		
		// SE
		if (LC_NOT_PLAY_SE != seNo) {
			result ~= " SE" ~ "=" ~ std.string.toString(seNo);
			result ~= " SE_LOOP=" ~ std.string.toString(seLoop);
		}
		
		return result ~ ">";
	}
	
	/// タグの種類に従ってフォーマットする
	char[] tagFormat(ColumnAtr col, int index, char[] prefix) {		
		char[] strBuffer;
		char[] strBufferEx;
		char[] strVal = lineInfo[index];
		bool bBgmCross;

		switch (prefix) {
		case SCENENO:
			strVal = createThumbnailInfoTag();
			strVal ~= "<HR>" ~ "\r\n";
//			prefix = null;
			break;

		case TEXT:
			strVal = cast(char[]) std.string.replace(strVal, REP_KEY_CANMA, ",");
			// テキストには<BR>を付加しておくかー
//			strVal ~= TAG_READSTOP ~ TAG_BR ~ "\r\n";
			prefix = null;
			break;
			
		case BGM_PLAY:
			if (bgmNoOld != -1) {
				// ＢＧＭが変更になった...クロスフェード
				bBgmCross = true;
			}
			break;
			
		case BGM_STOP:
			strVal = cast(char[]) std.string.toString( bgmNoOld );
			break;
			
		case SE_STOP:
			// SE の明示的指定は停止する番号のマイナスとして表現する
			strVal = cast(char[]) std.string.toString( seNoOld*-1 );
			break;
			
		case VOICE:
			// voiceは全引数をここで全部つくってしまうのだ 
			int voiceNo = voiceCountMap[strVal];
			char[] vCharId = voiceCharaIdMap[strVal];
			char[] voiceId = getVoiceId(vCharId, voiceNo);
			
			if (voiceId is null) {
				// そんなヴォイスは存在しないみたいだ
				Log.printWarn("Voice Not Found! %s %s", vCharId, voiceNo);
				return null;
			}
			
			strVal = voiceId ~ STR_CONMA ~ "128";
			break;
		case NEW_LINE:
			if (1 == std.conv.toInt(strVal)) {
				strVal = TAG_READSTOP ~ TAG_BR ~ "\r\n";
			} else {
				strVal = cast(char[]) "";
			}
			prefix = null;
			break;

/++		case BG:
			break;

		case BGM_PLAY:
			prefix = BGM_PLAY;
			break;

		case SE:
			prefix = SE;
			break;

		case CHARAIN:
			prefix = CHARAIN;
			break;
++/			
		default:
			break;
		}
		
		// タグがないのはベタテキストとする
		if (prefix is null || prefix == SCENENO) {
			return strVal;
		}
		
//		printf("prefix %s\n ", prefix);
		
		// タグの引数定義を読み込む
		Dom domTag = getTagAtr(prefix);
		
		// タグ先頭の書き出し
		Dom domPrefix = domTag.get(cast(char[]) PREFIX);
		domPrefix.type = Dom.TEXT;
		strBuffer ~= "<" ~ getArgPrefix(domPrefix);
		strBuffer ~= STR_CONMA;
		
		// クロスフェードならＳＴＯＰ命令を自分でかくかー
		if ( bBgmCross ) {			
			strBufferEx ~= "<" ~ "BGM_STOP" ~ STR_CONMA ~ std.string.toString( bgmNoOld );
		}
		
		bool cIn,cOut;
		char[] oldChara;
		int argCount;
		
		// キャラ入れ替えのデフォルトの引数
		static const char[][] CHARA_PEP_DEF_ARG = [
			"0",		// スライド
			"e20",		// エフェクト番号（フェード）
			"3",		// エフェクトスピード
			"FFFFFF",
			"0",		// 待機フラグ
		];
		
		// メイン値 
		// CHARAOUT は未記入として表現される
		// BGM_STOP は未記入として表現される
		if ( !(strVal is null) || prefix == CHARAOUT ) {
//printf("strVal:%*s ,", strVal);
	
			if ( prefix == CHARAIN ) {
				strVal = charaMap[strVal].charaId ~ charaMap[strVal].fileId;

				// キャラタグであれば暗黙属性である表示位置を設定する
				strVal ~= STR_CONMA;
				strVal ~= ScenarioDrawEx.presetPos[charaPos-1];
				cIn = true;

			} else if ( prefix == CHARAOUT ) {
				oldChara = charIdOld[charaPos-1];
				strVal = charaMap[oldChara].charaId ~ charaMap[oldChara].fileId;
				cOut = true;
			}
			
			strBuffer ~= strVal;

			Dom domArg = getArg(domTag, 0);
			for(int i = 0; i < col.optionNum; ++i) {
				// 引数定義を読み込む
				Dom domArg2 = getArg(domTag, i);
				
// Log.print("optionIndex : %s\n", index+cols[col.index].optionIndex + i);
				
				char[] str = lineInfo[index+cols[col.index].optionIndex + i + 1];

				// タグ仕様脆弱のため、自前で引数を間引きする（ボリュームはスキップ）
				if ( prefix == BGM_STOP && i == 0) {
					continue;
				}

				// 入力がなければ以下はデフォルト引数である...
				if (!str) break;

				// BGM_PLAY時 クロスフェードならば...
				if ( prefix == BGM_PLAY && i == 1) {
					strBufferEx ~= STR_CONMA ~ str;	
				}

				if ( (i == 0 && prefix == BG) 			// 背景タグの第一引数である
					|| ( (cIn || cOut) && i == 1 ) ) 	// キャラタグの第二引数である
				{			
					// BG, CHARAIN, CHARAOUTのエフェクト番号を
					// エフェクト識別子に変換する
					int bgEffectNo = std.conv.toInt(str);
//	printf("Num2EffectId -> %d\n",bgEffectNo);
					str = cast(char[]) ScenarioDrawEx.effect[bgEffectNo];
				}
				
				strBuffer ~= STR_CONMA;
				strBuffer ~= str;
				argCount++;
			}

		}
		
		// 入れ替えであれば、前のキャラIdを指定する
		if ( prefix == CHARAIN && !(charIdOld[charaPos-1] is null) ) {
			
			// 引数が未記入であると、引数エラーだから、足りない分をデフォルトの引数で埋める
			for (int i = argCount; i < CHARA_PEP_DEF_ARG.length; ++i) {
				strBuffer ~= STR_CONMA ~ CHARA_PEP_DEF_ARG[i];
			}
			
			oldChara = charIdOld[charaPos-1];
			strBuffer ~= STR_CONMA;
			strBuffer ~= strVal = charaMap[oldChara].charaId ~ charaMap[oldChara].fileId;
		}
		
		// タグ終了
		strBuffer ~= ">";
		
		// 見栄えのために改行するか
		if ( col.newline ) {
			buffer ~= "\r\n";
		}

		if (bBgmCross) {
			strBufferEx ~= ">";
		}
		
		if (bBgmCross) {
			return strBufferEx ~ strBuffer;
		}
						
		return strBuffer;
	}
	
	/// XMLファイルよりタグの属性を取得
	Dom getTagAtr(char[] prefix) {
		return domRoot.get(cast(char[]) "Convert_Table").get(prefix);
	}
	
	/// タグ変換定義から引数ブロックを番号で取り出す
	Dom getArg(Dom domTag, int num) {
		Dom domArg = domTag.get(cast(char[]) "arguments");
		
		int count = 0;
		foreach(inout Dom d; domTag.get(cast(char[]) "arguments").array) {
			if ( d.value == "argument" ) {
				if (count == num) {
					return d;
				}
				++count;
			}
		}
		
		return null;
	}
	
	/// 引数ブロックから接頭辞ブロックを取り出す
	char[] getArgPrefix(Dom domArg) {
		foreach(inout Dom d; domArg.array) {
			if (d.type == Dom.TEXT) {
				return d.value;
			}
		}
		
		return null;
	}
	
	/// xmlファイルから改行、タブ文字を取り除く
	char[] clearText(char[] str) {
		str = cast(char[]) std.string.replace(str, "\t", "");
		return cast(char[]) std.string.replace(str, "\r\n", "");
	}
	
	/// タグを解析して状態をスタックします
	char[] analyzeTag(ColumnAtr col, int index) {
		char[] str = lineInfo[index];
		char[] prefix;

		switch (col.prefix) {
		case SCENENO:
			prefix = tagScene(str);
			break;

		case BG:
			prefix = tagBG(str);
			break;

		case BGM_PLAY:
			prefix = tagBGM(str);
			break;

		case SE_PLAY:
			prefix = tagSE(str);
			break;

		case CHARAIN:

			// 仮処置
			if (3 == charaPos) {
				charaPos = 0;
			}
			
			prefix = tagChara(str, charaPos);
			++charaPos;
			break;
			
		case TEXT:
			prefix = cast(char[]) TEXT;
			break;
			
		case VOICE:
			prefix = tagVoice(str);
			break;
			
		case NEW_LINE:
			if (str is null) {
				prefix = null;
			} else {
				prefix = cast(char[]) NEW_LINE;
			}
			break;
			
		default:
			Log.print("UNDEFIND PREFIX :%s", col.prefix);
			break;
		}
		
		return prefix;
	}
	
	/// タグBG
	char[] tagBG(char[] str) {
		
		try {
			if ( !(str is null) ) {
				if (bgNo is null) {
					Log.print("new BG\n");
					bgNo = str;
					return cast(char[]) BG;
				} else {
					if (bgNo == str) {
						// 状態維持
						return null;
					}
					Log.print("bg replace to %s from %s\n", str, bgNo);
					bgNo = str;
					return cast(char[]) BG;
				}
			} else {
				
				// 入力文字列がなければ、それは背景続行であると見なす
				
/*				if (bgNo != -1) {
					Log.print("BG out from %s\n", bgNo);
					bgNo = -1;
					return BG;
				}
*/
			}
		} catch (Error e) {
			Log.printFatal("tagBG : Error %s\n", e.toString() );
		}
		
		return null;
	}
	
	/// sceneタグ
	char[] tagScene(char[] str) {
		
		if (str is null) return null;	// 未入力OK

		float newSceneNo = std.conv.toFloat(str);
		
		// 同シーン
		if (newSceneNo == sceneNo) {
			return null;
		} 
		Log.print("Scene Change to %s form %s\n", newSceneNo, sceneNo);
		sceneNo = newSceneNo;
		return cast(char[]) SCENENO;
	}
	
	/// キャラクタータグ
	char[] tagChara(char[] str, int pos) {
		
		if (str is null) {
			
			// 未表示のまま
			if (charId[pos] is null) {
				return null;
			} else {
				Log.print("CHARA OUT from %s Pos %s\n", charId[pos], pos);
				charIdOld[pos] = charId[pos];
				charId[pos] = null;
				
				return cast(char[]) CHARAOUT;
			} 
			
		} else {
			
			if (charId[pos] is null) {
				Log.print("CHARA IN %s Pos %s\n", str, pos);
				charIdOld[pos] = null;
				charId[pos] = str;
				return cast(char[]) CHARAIN;

			} else {
				// 同表示維持
				if (charId[pos] == str) {
					return null;
				} else {
					// キャラ入れ替え
					Log.print("CHARA REPLACE to %s form %s Pos %s\n", str, charId[pos], pos);
					charIdOld[pos] = charId[pos];
					charId[pos] = str;
					return cast(char[]) CHARAIN;
				}
			}
		}
	}
	
	/// タグBGMの解析
	char[] tagBGM(char[] str) {
		
		if (str is null) {
			if (bgmNo != -1) {
				Log.print("BGM STOP %s\n", bgmNo);
				bgmNoOld = bgmNo;
				bgmNo = -1;
				bgmLoop = bgmLoop.init;
								
				return cast(char[]) BGM_STOP;
			}
		} else {
			int newBgm = std.conv.toInt( str );
			
			if ( bgmNo == newBgm ) {
				// 同一BGMNOであれば、続行を意味する
				return null;
			}
			
			bgmNoOld = bgmNo;
			bgmLoop = bgmLoop.init;
			if (bgmNo != -1) {
				bgmNo = newBgm;
				
				// これはクロスフェードになる
				Log.print("Change BGM %s", bgmNo);
				
				return cast(char[]) BGM_PLAY;
			} else {
				Log.print("New BGM %s", bgmNo);
				bgmNo = newBgm;
				
				return cast(char[]) BGM_PLAY;
			}
		}
		
		return null;
		
	}
	
	/// タグSEの解析
	char[] tagSE(char[] str) {
		static const int LC_NOT_PLAY_SE = int.max;
		
		if (str is null) {

			// SEは必ずしも停止を明示する必要はないので、この判定では仕様を満たせない
			
//			if (seNo != LC_NOT_PLAY_SE) {
//				Log.print("SE STOP %s\n", seNo);
//				seNo = LC_NOT_PLAY_SE;
//				return SE_STOP;
//			}

		} else {
			int newSe = std.conv.toInt( str );
			
			if ( newSe < 0 ) {
				// 指定マイナスは、ABS( newBG )を停止させることを意味する
				seNoOld = newSe * -1;
				seNo = LC_NOT_PLAY_SE;
				
				return cast(char[]) SE_STOP;
				
			} else if (seNo != LC_NOT_PLAY_SE) {
				seNo = newSe;
				Log.print("New SE %s", seNo);
				return cast(char[]) SE_PLAY;

			} else {
				Log.print("SE CHANGE to %s from %s\n", newSe, seNo);
				seNo = newSe;
				return cast(char[]) SE_PLAY;
			}
		}
		
		return null;
	}
	
	
	/// ヴォイスタグを解析します
	char[] tagVoice(char[] str) {
		if (str is null) return null;
		
		char[]* vCharaId = str in voiceCharaIdMap;
		
		if ( vCharaId is null ) {
			Log.print("Voice tag skip! Voice char id - %s", str);
			return null;
		}
		
		int* pCount = str in voiceCountMap;

		if ( !(pCount is null) ) {
			(*pCount)++;
		} else {
			voiceCountMap[str] = 0;
		} 
		
		Log.print("Voice Play : CharaId - %s No - %s", *vCharaId, voiceCountMap[str]);
		
		return cast(char[]) VOICE;
	}

	/// カラムブロックから属性クラスを構築する
	ColumnAtr setCoumnAtr(Dom domColunm) {
		ColumnAtr col = new ColumnAtr();
		
		foreach (int i, inout Dom dd; domColunm.array) {
			
			char[] strValue;
			Dom domCur;
			// 要素の中の値にアクセスする必要がある
			foreach (inout Dom d; dd.array) {
				if (d.type == Dom.TEXT) {
					strValue = d.value;
				}
			}
			
			switch (i) {
			case 0:
				col.name = strValue;
				Log.print("name : %s\n",strValue);
				break;
			case 1:
				col.prefix = strValue;
				Log.print("prefix : %s\n",strValue);
				break;
			case 2:
				col.enable = strValue == "1" ? true : false;
				Log.print("enable : %s\n",strValue);
				break;
			case 3:
				col.comment = strValue == "1" ? true : false;
				Log.print("comment : %s\n",strValue);
				break;
			case 4:
				col.newline = strValue == "1" ? true : false;
				Log.print("newline : %s\n", strValue);
				break;
			case 5:
				col.optionNum = std.conv.toInt( strValue );
				col.optionIndex = getOptionIndex( domColunm.array[i] );
				Log.print("optionNum : %s\n",strValue);
				break;
			default:
				assert(false);
			}
		}
		
		return col;
	}
	
	/// オプションインデックスブロックからインデックス番号を取り出す
	int getOptionIndex(Dom domArg) {
		foreach (Dom d; domArg.array) {
			if (d.value == "index") {
				Log.print("------ find index\n");
				foreach (Dom dd; d.array) {
					if (dd.type == Dom.TEXT) {
						Log.print("---  %s\n", dd.value);
						return std.conv.toInt( dd.value );
					}
				}
			}
		}
		
		// んなの記述されてないよ...
		return 0;
	}
	
	/// キャラＩＤと出現回数から、ユニークヴォイスＩＤを取得する
	char[] getVoiceId (char[] cId, int count) {
		char[] vId;
		int testCount;
		for (int i = 0; i < voiceIdLst.length; ++i) {
			vId = voiceIdLst[i];
			if ( std.string.find( vId, cId ) != -1 ) {

				if ( testCount == count ) {
					return vId;
				}
				
				testCount++;
			}
		}
		
		return null;
	}
	
	/// キャラクター情報読み込み
	public bool readCharaInfo(char[] filename) {
				
		// リスト読み込み
		ubyte[] mem = cast(ubyte[]) FileSys.read(filename);
		if (mem is null) return false; // 読み込みエラー
		// メモリストリーム
		MemoryStream m = new MemoryStream(mem);

		LineParser lp = new LineParser();
		
		CharaInfo charaOrg;
		
		while (!m.eof) {
			CharaInfo chara = new CharaInfo();
			char[] linebuf = cast(char[]) m.readLine();
			lp.setLine( linebuf );
			
			// 空行
			lp.getStr();
			
			// 通し番号
			chara.uniNo = lp.getStr();
			Log.printLook(	"CharaInfo: %s", chara.uniNo  );
			// キャラクター番号
			chara.charaNo = lp.getStr();
			// キャラクター名
			chara.name = lp.getStr();
			// ポーズ
			chara.poseName = lp.getStr();
			
			// 隠しデータ破棄
			lp.getStr();
			
			// 表情番号
			chara.expNo = lp.getStr();
			if (chara.expNo is null) {
				// 指定がある場合、こいつが必ず入っていなければならない
				continue;
			}
			// 表情名
			chara.expName = lp.getStr();
			// キャラ識別子
			chara.charaId = lp.getStr();
			// 画像番号
			chara.fileId = lp.getStr();
			
			if ( !(chara.charaNo is null) ) {
				// キャラクター番号あるということは新しいキャラ
				charaOrg = chara;
				
			} else if ( !(chara.poseName is null) ) {
				// ポーズ名があるということは、同キャラポーズ違い
				charaOrg.poseName = chara.poseName;
			}
			
			// 欠損している可能性があるデータを補填する
			chara.charaNo = charaOrg.charaNo;
			chara.name = charaOrg.name;
			chara.poseName = charaOrg.poseName;
			chara.charaId = charaOrg.charaId;

			// キャラクターマップに追加
			charaMap[chara.uniNo] =  chara;
		}
		
		foreach( CharaInfo c; charaMap ) {
			Log.printLook(	"CharaInfo: %s, %s, %s, %s, %s", c.uniNo, c.charaNo, c.expNo, c.charaId, c.fileId  );
		}
		
		return true;
	}
	
	/** キャラクター設定情報クラス */
	static class CharaInfo {
		char[] name;		// キャラクター名
		char[] uniNo;		// ユニークＩＤ
		char[] charaNo;		// キャラクター番号	
		char[] poseName;	// ポーズ名
		char[] expName;		// 表情名
		char[] expNo;		// 表情番号
		char[] charaId;		// キャラ識別子
		char[] fileId;		// 画像番号
	}
	
	/** タグ情報を記録するクラス */
	private static class TagInfo {
		char[] prefix;
		char[] str;
	}
	
private:

	static const char[] PREFIX = "prefix";
	
	static const char[] BG = "Background";
	static const char[] BGM_PLAY = "BgmPlay";
	static const char[] BGM_STOP = "BgmStop";
	static const char[] SE_PLAY = "SePlay";
	static const char[] SE_STOP = "SeStop";
	static const char[] VOICE = "Voice";
	static const char[] NEXTSCENE = "NextScene";
	static const char[] HEADLINE = "Headline";
	static const char[] CHARAIN = "CharIn";
	static const char[] CHARAOUT = "CharOut";
	static const char[] READSTOP = "ReadStop";
	static const char[] TEXT = "TEXT";
	static const char[] SCENENO = "SCENARIO";
	static const char[] NEW_LINE = "NEW_LINE";

	static const char[] TAG_BR = "<BR>";
	static const char[] STR_CONMA = ",";
	static const char[] COMMENT = "COMMENT:";
	static const char[] TAG_READSTOP = "<ReadStop>";
	
	static const final char[] REP_KEY_CANMA = "&canma";
	static const int LC_NOT_PLAY_SE = int.max;

	static bool bComment = false;	//<! コメントを出力するか？
	
	TagInfo[] lineTags;
	ColumnAtr[] cols;
	Dom domRoot;
	char[][] lineInfo;
	int[] indexs;
	char[] buffer;
	LineParser lineParser;
	
	CharaInfo[char[]] charaMap;	// キャラクター情報マップ
	int[char[]] voiceCountMap;	// ヴォイスカウントマップ
	char[][char[]] voiceCharaIdMap;	// キャラＩＤマップ
	char[][] voiceIdLst;
		
	/**
		カラムの属性
	*/
	class ColumnAtr {
		char[] name;	//!< リストの日本語名
		char[] prefix;	//!< 接頭辞
		bool enable;	//!< シナリオスクリプト対応列か
		bool comment;	//!< リストの値をコメントとして出力するか
		bool newline;	//!< タグを書き出したあと改行するか
		int optionNum;	//!< この属性従属のオプション数
		int optionIndex;
		int index;
		
		/// コンストラクタ
		this(char[] name_, bool enable_, char[] prefix_=null, bool comment_=true) {
			this();
			name = name_;
			enable = enable_;
			prefix = prefix_;
			comment = comment_;
		}
		
		/// コンストラクタ
		this() {
			// デフォルト改行
			newline = true;
		}
	}
	
private:
	// 状態保持用変数
	char[][4] charId;
	char[][4] charIdOld;
	int charaPos;
	int bgmNo = -1;
	int bgmLoop = -1;
	int bgmNoOld = -1;
	int seNo = LC_NOT_PLAY_SE;
	int seLoop = -1;
	int seNoOld = LC_NOT_PLAY_SE;
	char[] bgNo;
	float sceneNo = -1.0f;
	int listLineNo;
}

}