module yamalib.auxil.list2kirikiri;

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 List2KiriKiriScript {

public:

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

		Dom domColumns = domRoot.get("list").get("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 ( char[] str; charId) {
			str ~= "";
		}
		
		// キャラクター情報読み込み
		readCharaInfo(charaListFile);
		
		// ヴォイスＩＤとキャラ番号のマップ
		voiceCharaIdMap["02"] = "tatuwo";
		voiceCharaIdMap["03"] = "midoriko";
		voiceCharaIdMap["04"] = "alice";
		voiceCharaIdMap["05"] = "tomie";
		voiceCharaIdMap["06"] = "tadano";
		voiceCharaIdMap["07"] = "anzai";
		voiceCharaIdMap["08"] = "rubi";
		voiceCharaIdMap["09"] = "yuiri";
		voiceCharaIdMap["10"] = "hayami";
		voiceCharaIdMap["11"] = "kuramochi";
		voiceCharaIdMap["12"] = "jicho";
		voiceCharaIdMap["13"] = "aya";
		voiceCharaIdMap["15"] = "tasuku";
		
		const char[] voiceDirTmp = "snd/voice/{0}/voice.lst";
		char[] voiceLstFilename = std.string.replace( voiceDirTmp, "{0}", voiceDirId ); 
		scenarioDirId = 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 = m.readLine();
				lp.setLine( linebuf );
				
				lp.getStr();
				voiceIdLst ~= lp.getStr();
			}
			
			m.close();
		}
	}
	
	/// コメント出力フラグの設定
	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("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 = 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 ) {
						// 実際に書きにいく
						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);
			throw e;
		} catch (Object e) {
			Log.print("Error line:%s in %s", listLineNo, inFilename);
			Log.print("%s", e.toString());
		}
		
		// ファイル書き出し！		
		FileSys.writeSimple(outFilename, buffer);
		Log.print("LOG : Convert complete! : %s", inFilename);
			
		return true;
	}

private:

	/// 現在のタグ情報を優先順位にならべて修正する
	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 ~= SCENENO; // it mean <HR>
		order ~= BG;
		order ~= BGM_PLAY; // 通常プレイ命令が先で次にストップ（→ＢＧＭ切り替えで無音時間を作らない）
		order ~= BGM_STOP;
		order ~= NEXTSCENE;
		order ~= CHARAIN;
		order ~= CHARAOUT;
		order ~= NEXTSCENE;
		order ~= HEADLINE;
		order ~= SE_PLAY;
		order ~= SE_STOP;
		order ~= VOICE;
		order ~= TEXT;
		order ~= READSTOP;
		order ~= NEW_LINE;
		return order;
	}
	
	/// 指定属性の書き込み
	void write(ColumnAtr col, int index) {
		char[] prefix = analyzeTag(col, index);
		
		// これが返ってこないということは、NOPなのだ
		if (prefix is null) return;
		
		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 ~= TAG_START ~ COMMENT ~ lineInfo[index] ~ TAG_END;
		
		// 見栄えのために改行するか
		if ( col.newline ) {
			buffer ~= "\r\n";
		}
	}
	
	/// タグの種類に従ってフォーマットする
	char[] tagFormat(ColumnAtr col, int index, char[] prefix) {		
		char[] strBuffer;
		char[] strBufferEx;
		char[] strVal = lineInfo[index];
		bool bBgmCross;

		switch (prefix) {
		case SCENENO:
			strVal = "[p]" ~ "\r\n";
//			prefix = null;
			break;

		case TEXT:
			strVal = 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 = std.string.toString( bgmNoOld );
			break;
			
		case SE_STOP:
			// SE の明示的指定は停止する番号のマイナスとして表現する
			strVal = 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 ~ COM_SEP ~ "128";
			strVal = scenarioDirId ~ "/" ~ vCharId ~ "/" ~ voiceId;
			break;
		case NEW_LINE:
			if (1 == std.conv.toInt(strVal)) {
				strVal = TAG_READSTOP ~ TAG_BR ~ "\r\n";
			} else {
				strVal = "";
			}
			prefix = null;
			break;

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

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

			} else if ( prefix == CHARAOUT ) {
				oldChara = charIdOld[charaPos-1];
				char[] charaFilename = CHARA_DIR ~ "/" ~ charaMap[oldChara].charaId ~ "/" ~ charaMap[oldChara].charaId ~ charaMap[oldChara].fileId ~ ".0.png";
				// ファイル名
				strBuffer = replaceArgValue(0, charaFilename, strBuffer);

//				strVal = charaMap[oldChara].charaId ~ charaMap[oldChara].fileId;
				strVal = "";
				cOut = true;
			}
			
//			strBuffer ~= strVal;
			strBuffer = replaceArgValue(0, strVal, strBuffer);

			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 ~= COM_SEP ~ 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 = ScenarioDrawEx.effect[bgEffectNo];
				}
				
//				strBuffer ~= COM_SEP;
//				strBuffer ~= str;
				strBuffer = replaceArgValue(i+1, str, strBuffer);
				argCount++;
			}

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

		if (bBgmCross) {
			strBufferEx ~= TAG_END;
		}
		
		if (bBgmCross) {
			return strBufferEx ~ strBuffer;
		}
						
		return strBuffer;
	}

	// キャラ立ち位置のプリセットID
	static const char[][] PRESET_POS = [
			"left_center",
			"center",
			"right_center",
	];
	
	char[] replaceArgValue(int argNo, char[] value, char[] srcStr) {
		char[] ident = "{" ~ std.string.toString(argNo) ~ "}";
		return std.string.replace(srcStr, ident, value);
	
	}
	
	char[] convertKiriKriTag(char[] prefix) {
		switch(prefix) {
			case "BG":
				return "image storage=\"{0}\" layer=\"base\"";
				 
			case "BGM_PLAY":
				return "fadeinbgm storage=\"{0}\" time=\"{2}\"";
			case "BGM_STOP":
				return "fadeoutbgm time=\"{0}\"";
			case "SE":
				return "playse storage=\"{0}\" loop=\"false\"";
			case "SE_STOP":
				return "stopse";
			case "VOICE":
				return "playse storage=\"{0}\" loop=\"false\"";
			case "CharaIn":
				return "image storage=\"{0}\" layer=\"{1}\" pos=\"{2}\" visible=true";
			case "CharaOut":
				return "image storage=\"{0}\" layer=\"{1}\" visible=\"false\"";
			default:
				break;
		}
//		printf("unknown prefix:%*s\n");
		return "";
	}
	
	/// XMLファイルよりタグの属性を取得
	Dom getTagAtr(char[] prefix) {
		return domRoot.get("Convert_Table").get(prefix);
	}
	
	/// タグ変換定義から引数ブロックを番号で取り出す
	Dom getArg(Dom domTag, int num) {
		Dom domArg = domTag.get("arguments");
		
		int count = 0;
		foreach(inout Dom d; domTag.get("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 = std.string.replace(str, "\t", "");
		return 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 = TEXT;
			break;
			
		case VOICE:
			prefix = tagVoice(str);
			break;
			
		case NEW_LINE:
			if (str is null) {
				prefix = null;
			} else {
				prefix = 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 BG;
				} else {
					if (bgNo == str) {
						// 状態維持
						return null;
					}
					Log.print("bg replace to %s from %s\n", str, bgNo);
					bgNo = str;
					return 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 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 CHARAOUT;
			} 
			
		} else {
			
			if (charId[pos] is null) {
				Log.print("CHARA IN %s Pos %s\n", str, pos);
				charIdOld[pos] = null;
				charId[pos] = str;
				return 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 CHARAIN;
				}
			}
		}
	}
	
	/// タグBGMの解析
	char[] tagBGM(char[] str) {
		
		if (str is null) {
			if (bgmNo != -1) {
				Log.print("BGM STOP %s\n", bgmNo);
				bgmNoOld = bgmNo;
				bgmNo = -1;
				
				return BGM_STOP;
			}
		} else {
			int newBgm = std.conv.toInt( str );
			
			if ( bgmNo == newBgm ) {
				// 同一BGMNOであれば、続行を意味する
				return null;
			}
			
			bgmNoOld = bgmNo;
			if (bgmNo != -1) {
				bgmNo = newBgm;
				
				// これはクロスフェードになる
				Log.print("Change BGM %s", bgmNo);
				
				return BGM_PLAY;
			} else {
				Log.print("New BGM %s", bgmNo);
				bgmNo = newBgm;
				
				return 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 SE_STOP;
				
			} else if (seNo != LC_NOT_PLAY_SE) {
				seNo = newSe;
				Log.print("New SE %s", seNo);
				return SE_PLAY;

			} else {
				Log.print("SE CHANGE to %s from %s\n", newSe, seNo);
				seNo = newSe;
				return 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 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 = 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";	//[image storage="filename" layer="base"]
	static const char[] BGM_PLAY = "BgmPlay";	//[fadeinbgm storage="filename" time="ms"]
	static const char[] BGM_STOP = "BgmStop"; //[fadeoutbgm time="ms"]
	static const char[] SE_PLAY = "SePlay";	// [playse storage="filename" loop="false"]
	static const char[] SE_STOP = "SeStop";	//[stopse]
	static const char[] VOICE = "Voice"; // [playse storage="filename" loop="false"]
	static const char[] NEXTSCENE = "NextScene"; 
	static const char[] HEADLINE = "Headline";
	static const char[] CHARAIN = "CharIn"; 	//[image storage="filename" layer="0" pos="left_center"]
	static const char[] CHARAOUT = "CharOut";	//[image storage="filename" layer="0" pos="left_center" visible="false"]
	static const char[] READSTOP = "ReadStop";	//[l]
	static const char[] TEXT = "TEXT";
	static const char[] SCENENO = "SCENARIO";	//[r]
	static const char[] NEW_LINE = "NEW_LINE";	//[p]

	static const char[] TAG_START = "[";
	static const char[] TAG_END = "]";

	static const char[] TAG_BR = "[r]";
	static const char[] COM_SEP = " ";
	static const char[] COMMENT = "COMMENT:";
	static const char[] TAG_READSTOP = "[l]";
	
	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;
	char[] scenarioDirId;
	int charaPos;
	int bgmNo = -1;
	int bgmNoOld = -1;
	int seNo = LC_NOT_PLAY_SE;
	int seNoOld = LC_NOT_PLAY_SE;
	char[] bgNo;
	float sceneNo = -1.0f;
	int listLineNo;
}

}