#include "app.h"
/****************************************************************************
 *	
 ****************************************************************************/
__declspec(noreturn) static void usage();
static void dumpKeyBody(int indent, const unsigned char* keyBody);
static void printIndent(int indent, const char* fmt, ...);
static void printIndent_nonewline(int indent, const char* fmt, ...);
//{{2017/09/16追加:ラベルテーブルに対応しました。
static char* getLabelSymbol(int nameOrValue);
//}}2017/09/16追加:ラベルテーブルに対応しました。
/****************************************************************************
 *	
 ****************************************************************************/
//{{2017/09/16追加:ラベルテーブルに対応しました。
static ght_hash_table_t* ht_labTbl;	//「ラベルの数値⇒ラベルの名称」辞書
//}}2017/09/16追加:ラベルテーブルに対応しました。
/****************************************************************************
 *	
 ****************************************************************************/
int main(int argc, char* argv[]) {
//{{2017/09/16追加:ラベルテーブルに対応しました。
	int opt_l = 0;	//0=ラベルを数値で表示する。1=ラベルを名称で表示する。
	int opt_t = 0;	//0=レジストリテーブルをダンプする。1=ラベルテーブルをダンプする。
//}}2017/09/16追加:ラベルテーブルに対応しました。
	FILE* fp;
	void* buf;
	int opt, len;
	//コマンドラインオプションを処理する。
	opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
	while((opt = getopt(argc, argv, "lth")) != -1) {
		switch(opt) {
//{{2017/09/16追加:ラベルテーブルに対応しました。
		case 'l':
			if(opt_t) { usage(); }	//'-l'オプションと'-t'オプションは併用不可。
			opt_l = 1;	//ラベルを名称で表示する。
			break;
		case 't':
			if(opt_l) { usage(); }	//'-l'オプションと'-t'オプションは併用不可。
			opt_t = 1;	//ラベルテーブルをダンプする。
			break;
//}}2017/09/16追加:ラベルテーブルに対応しました。
		case 'h':
		default:
			usage();
		}
	}
	//レジストリテーブルのバイナリデータを読み込む。
	if(optind != (argc - 1)) { usage(); }	//引数の数が不正
	fp = fopen(argv[optind], "rb");
//{{2016/05/28追加:ファイルオープンエラーを検査し忘れていたバグを修正しました。
	if(!fp) { error(EXIT_FAILURE, errno, "%s が開けません。", argv[optind]); }
//}}2016/05/28追加:ファイルオープンエラーを検査し忘れていたバグを修正しました。
	fseek(fp, 0, SEEK_END);
	len = ftell(fp);
	rewind(fp);
	buf = malloc(len);
	if(!buf) { DIE(); }	//メモリ不足
	fread(buf, 1, len, fp);
	fclose(fp);
//{{2017/09/16追加:ラベルテーブルに対応しました。
	//ラベルテーブルの有無にかかわらず、「ラベルの数値⇒ラベルの名称」辞書を作成する。
	// - '-l'オプションが指定されていなければ空のままとなり、結果的に、ラベルを数値で表示する動作となります。
	//   また、'-t'オプションが指定されていた場合は、辞書を使用しないので無駄ですが、動作上は問題有りません。
	ght_set_rehash((ht_labTbl = ght_create(0)), 1);
	//'-l'オプション,又は,'-t'オプションが指定されていたら…
	if(opt_l || opt_t) {
		//ラベルテーブルを取得する。
		// - ラベルテーブルが格納されていなかった場合の動作は未定義です。
		//   おそらく、ツールが異常終了するか、不正な結果が出力されます。
		//   ラベルテーブルが格納されているかを明示的に判断する方法が無いので、やむをえません。
		const unsigned char* labelTable = REG_get_label_table(buf);
	    //{{この範囲の処理は必須ではありません。
		{
			//上で「ラベルテーブルが格納されているかを明示的に判断する方法が無い」と書きましたが、それだけではあんまりなので、推測で判断する事にしました。
			//labelTableが本当にラベルテーブルであるならば…
			const unsigned char* keyBody = labelTable;
			int mask, n, dist;
			//maskを読み出せるはずです。
			if((keyBody < (char*)buf) || ((keyBody + 1) > ((char*)buf + len))) { error(EXIT_FAILURE, 0, "ラベルテーブルが格納されていません。"); }
			mask = *keyBody++;
			//ラベルテーブルは文字列を0個以上含んでいるだけで、キーも値もBLOBも含んでいないはずです。即ち、maskは(1<<6)か0であるはずです。		←←←←←←←←2017/09/16現在は先の通りなのですが、もし、今後ラベルテーブルの構造を再変更して文字列以外も含めるようにした場合は、この行の条件は変更する必要が有る事に注意して下さい。
			if(mask & ~(1<<5)) { error(EXIT_FAILURE, 0, "ラベルテーブルが格納されていません。"); }
			//ラベルテーブルは文字列を1個以上含んでいるならば…
			if(mask == (1<<5)) {
				//文字列の要素数を読み出せるはずです。
				if((keyBody < (char*)buf) || ((keyBody + 2) > ((char*)buf + len))) { error(EXIT_FAILURE, 0, "ラベルテーブルが格納されていません。"); }
				n        =         *keyBody++;
				n      <<= 8;
				n       |=         *keyBody++;
				n++; //(要素数-1)⇒要素数に変換する。
				keyBody += n * (2/*name*/+3/*data*/);
				//ラベルテーブルのヘッダの後に、ラベルテーブル自身への相対距離が格納されているはずです。
				if((keyBody < (char*)buf) || ((keyBody + 3) > ((char*)buf + len))) { error(EXIT_FAILURE, 0, "ラベルテーブルが格納されていません。"); }
				dist     = (int8_t)*keyBody++;
				dist   <<= 8;
				dist    |=         *keyBody++;
				dist   <<= 8;
				dist    |=         *keyBody++;
				keyBody += dist;
				if(keyBody != labelTable) { error(EXIT_FAILURE, 0, "ラベルテーブルが格納されていません。"); }
			}
		}
	    //}}この範囲の処理は必須ではありません。
		//'-t'オプションが指定されていたら…
		if(opt_t) {
			//ラベルテーブルをダンプする。
			dumpKeyBody(0, labelTable);
			return EXIT_SUCCESS;	//ここまで
		}
		//辞書に、「ラベルの数値⇒ラベルの名称」を登録する。
		{
			int i, labelName;
			const char* labelSymbol;
			for(i = 0; (labelSymbol = REG_get_nth_string(labelTable, i, &labelName)) != NULL; i++) {
				if(!iscsymf(labelSymbol[0])) { labelSymbol = strconcat("`", labelSymbol, NULL); }	//一文字目がCシンボル文字の先頭文字でなければ「`」を付与する。レジストリスクリプトの書式です。
				if(ght_insert(ht_labTbl, (void*)labelSymbol, sizeof(int), &labelName)) { DIE(); }
			}
		}
	}
//}}2017/09/16追加:ラベルテーブルに対応しました。
	//レジストリテーブルをダンプする。
	dumpKeyBody(0, buf);
	return EXIT_SUCCESS;
}
/*---------------------------------------------------------------------------*/
__declspec(noreturn) static void usage() {
	fprintf(stderr, "dRegTblD - レジストリテーブル逆コンパイラ (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2018 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  dRegTblD [option] RegTbl.bin\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
//{{2017/09/16追加:ラベルテーブルに対応しました。
	fprintf(stderr, "  -l  ラベルを名称で表示します。\n");
	fprintf(stderr, "      このオプションを指定しなければ、ラベルを数値で表示します。\n");
	fprintf(stderr, "      '-t'オプションと同時に指定する事は出来ません。\n");
	fprintf(stderr, "  -t  ラベルテーブルをダンプします。\n");
	fprintf(stderr, "      このオプションを指定しなければ、レジストリテーブルをダンプします。\n");
	fprintf(stderr, "      '-l'オプションと同時に指定する事は出来ません。\n");
//}}2017/09/16追加:ラベルテーブルに対応しました。
	fprintf(stderr, "  -h  使い方を表示します。\n");
//}}2017/09/16追加:ラベルテーブルに対応しました。
	fprintf(stderr, "\n");
	fprintf(stderr, "NOTE:\n");
	fprintf(stderr, "  ・ラベルテーブルを格納していないレジストリデータに対して、'-l',又は,'-t'を指定してはいけません。\n");
	fprintf(stderr, "    もし、そうした場合の動作は未定義であり、ツールがクラッシュしたり、間違った結果が表示されます。\n");
	fprintf(stderr, "  ・ラベルテーブルにレジストリデータを格納するには、dRegTblC.exeに'-l'オプションを指定して下さい。\n");
//{{2017/09/16追加:ラベルテーブルに対応しました。
	exit(EXIT_FAILURE);
}
/*--------------------------------------------------------------------------*/
static void dumpKeyBody(int indent, const unsigned char* keyBody) {
//{{2018/04/12追加:空のキーをダンプする時に、「prevent-deletion」を出力するようにしました。
	if(!REG_num_key(keyBody) &&
	   !REG_num_value(keyBody) &&
	   !REG_num_string(keyBody) &&
	   !REG_num_blob(keyBody)) {
		printIndent(indent, "prevent-deletion");
		return;	//ここまで
	}
//}}2018/04/12追加:空のキーをダンプする時に、「prevent-deletion」を出力するようにしました。
	//--- キー ---
	{
		const unsigned char* keyBody2;
		int index, keyName;
		for(index = 0;
		   (keyBody2 = REG_open_nth_key(keyBody, index, &keyName)) != NULL;
		    index++) {
//{{2017/09/16変更:ラベルテーブルに対応しました。
//			printIndent(indent, "%u", keyName);
//↓2017/09/16変更:ラベルテーブルに対応しました。
			printIndent(indent, "%s", getLabelSymbol(keyName));
//}}2017/09/16変更:ラベルテーブルに対応しました。
			dumpKeyBody(indent + 1, keyBody2);
			printIndent(indent, "end");
		}
	}
	//--- 値 ---
	{
		int index, valueName, valueData;
		for(index = 0;
		   (valueData = REG_get_nth_value(keyBody, index, &valueName)) != -1;
		    index++) {
//{{2017/09/16変更:ラベルテーブルに対応しました。
//			printIndent(indent, "%u\t= %u", valueName, valueData);
//↓2017/09/16変更:ラベルテーブルに対応しました。
			printIndent(indent, "%s\t= %s", getLabelSymbol(valueName), getLabelSymbol(valueData));
//}}2017/09/16変更:ラベルテーブルに対応しました。
		}
	}
	//--- 文字列 ---
	{
		const char* stringData;
		int index, stringName;
		for(index = 0;
		   (stringData = REG_get_nth_string(keyBody, index, &stringName)) != NULL;
		    index++) {
//{{2017/09/16変更:ラベルテーブルに対応しました。
//			printIndent(indent, "%u\t= \"%s\"", stringName, stringData);
//↓2017/09/16変更:ラベルテーブルに対応しました。
			printIndent(indent, "%s\t= \"%s\"", getLabelSymbol(stringName), stringData);
//}}2017/09/16変更:ラベルテーブルに対応しました。
		}
	}
	//--- BLOB ---
	{
		const uint8_t* blobData;
		int index, blobName, blobSize, i;
		for(index = 0;
		   (blobData = REG_get_nth_blob(keyBody, index, &blobName)) != NULL;
		    index++) {
//{{2017/09/16変更:ラベルテーブルに対応しました。
//			printIndent_nonewline(indent, "%u\t= {", blobName);
//↓2017/09/16変更:ラベルテーブルに対応しました。
			printIndent_nonewline(indent, "%s\t= {", getLabelSymbol(blobName));
//}}2017/09/16変更:ラベルテーブルに対応しました。
			blobSize = REG_get_blob_size(blobData);
			for(i = 0; i < blobSize; i++) {
				printf("0x%02X", blobData[i]);
				if(i < (blobSize - 1)) {
					putchar(',');
					if((i & 15) == 15) {	//BLOBは一行16バイトで改行する事にしました。変更しても構いません。
						putchar('\n');
						printIndent_nonewline(indent, "\t   ");
					}
				}
			}
			printf("}\n");
		}
	}
}
/*--------------------------------------------------------------------------*/
static void printIndent(int indent, const char* fmt, ...) {
	va_list ap;
	while(--indent >= 0) { putchar('\t'); }
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
	putchar('\n');
}
static void printIndent_nonewline(int indent, const char* fmt, ...) {
	va_list ap;
	while(--indent >= 0) { putchar('\t'); }
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
}
/*--------------------------------------------------------------------------*/
//{{2017/09/16追加:ラベルテーブルに対応しました。
static char* getLabelSymbol(int nameOrValue) {
	//ラベルならば、ラベルの名称を返します。(一文字目がCシンボル文字の先頭文字でなければ「`」を付与する処理は、既に登録時に行っているのでここでは不要です。)
	//ラベルでなければ、数値を文字列にして返します。
	// - ラベルテーブルに登録されている数値ならばラベルであり、ラベルテーブルに登録されていない数値ならばラベルではないと判断出来ます。
	//   なぜなら、ラベルの数値には、データとして使用されていない値だけを割り当てているからです。
	//   詳細は、/clip/tool/dRegTblC/winapp.cのresolveLabel()の、「明示的な数値として使用されなかった数値をラベルに割り当てる」のコメントを参照して下さい。
	char* label = ght_get(ht_labTbl, sizeof(int), &nameOrValue);
	if(!label) { label = strdup_printf("%u", nameOrValue); }
	return label;
}
//}}2017/09/16追加:ラベルテーブルに対応しました。
