/*
 *	app.c
 *
 *	TabAlign.exe - タブ文字を追加して列幅を揃えるツール
 *	Copyright (C) 2015-2017 Naoyuki Sawa
 *
 *	* Tue Oct 20 21:27:55 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	- 既存の TabAlign.awk,TabAlign.bat を置き換えるツールです。動作内容やアルゴリズムは同じです。
 *	  TabAlign.awk,TabAlign.bat を置き換える事にした理由は、バッチ経由だと使い辛い事が有ったからです。
 *	- 既存の TabAlign.awk,TabAlign.bat を、「./keep/」フォルダに保存しておきました。
 *	  これまで問題なく動作していた実績が有るので、もし当ツールの問題が生じた場合は元に戻してください。
 *	* Sun Feb 21 21:02:01 JST 2016 Naoyuki Sawa
 *	- 「Boehm GC」をマルチスレッド対応にした事,及び,それに伴ってclipbpe.cモジュールを修正した事に伴い、当ツールもプロジェクト設定の変更を行ってリビルドしました。
 *	  詳細は、/clip/tool/gc/README.TXT と /clip/clipbpe.c の2015/11/03のコメントを参照して下さい。
 *	- libclipのライブラリ化の伴い、以下の変更を行いました。
 *	  ・extclip.hとextclip.cを削除しました。extclip.hとextclip.cはlibclipに含まれるようになったからです。
 *	  ・"CLiP Files"を削除しました。CLiPライブラリの各モジュールはlibclipに含まれるようになったからです。
 *	  当ツールの動作には変更有りません。
 *	* Tue May 09 22:14:45 JST 2017 Naoyuki Sawa
 *	- ライブラリのバグ修正に伴う、ツールのリビルドを行いました。
 *	  詳細は、clipmisc.cの'Sun May 07 09:13:01 JST 2017 Naoyuki Sawa'のコメントを参照して下さい。
 *	- 前回更新後〜本日までの間に、他のツール群に対して行ったのと同じビルド設定の変更を、当ツールにも対しても適用しました。
 */
#include "app.h"
#define VERSION		"20170509"	//最終更新日
/****************************************************************************
 *	
 ****************************************************************************/
__declspec(noreturn) static void usage();
static int subr(int argc, char* argv[]);
/****************************************************************************
 *	main
 ****************************************************************************/
int main(int argc, char* argv[]) {
	int exitCode;
//	putenv("GC_LOG_FILE=CON");//ログ出力先を指定する。
//	GC_set_find_leak(1);//メモリリーク検出を開始する。
	SEH_init();
//	if(!(TBL_RegTbl = LockResource(LoadResource(NULL, FindResource(NULL, "RegTbl", "RegTbl"))))) { DIE(); }
//	if(!(pPrpKey = REG_open_key(TBL_RegTbl, RegKey_PrpTbl))) { DIE(); }
//	PrpHdr_init(phApp, pPrpKey, PrpTbl_App);
	exitCode = subr(argc, argv);
//	CHECK_LEAKS();//メモリリークを検出する。
//	cputs("ok");
//	getch();
	return exitCode;
}
/****************************************************************************
 *	usage
 ****************************************************************************/
__declspec(noreturn) static void usage() {
	fprintf(stderr, "TabAlign.exe - タブ文字を追加して列幅を揃えるツール (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2017 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  TabAlign.exe [option ...] [filename ...] \n");
	fprintf(stderr, "  ・filenameを指定しなければ、標準入力から読み込みます。\n");
	fprintf(stderr, "  ・filenameは、ワイルドカードによる複数指定も可能です。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "  -tタブ幅 タブ幅を指定します。\n");
	fprintf(stderr, "           このオプションを指定しなければ、8タブと見なします。\n");
	fprintf(stderr, "  -h       使い方を表示します。\n");
	exit(EXIT_FAILURE);
}
/****************************************************************************
 *	subr
 ****************************************************************************/
static int subr(int argc, char* argv[]) {
	int TabWidth = 8;
	{
		int opt;
		char* endptr;
		opterr = 0; //オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
		while((opt = getopt(argc, argv, "t:h")) != -1) {	//コマンドラインオプションを処理する。
			switch(opt) {
			case 't':
				TabWidth = strtoul(optarg, &endptr, 0);
				if(*endptr || !TabWidth) { usage(); }
				break;
			case 'h':
			default:
				usage();
			}
		}
	}
	{
		int nRow    = 0;
		int iMaxCol = 0;				//最大列数
		ST_Worksheet* wsRowSiz = Worksheet_new();	//各行の列数		[行番号]
		ST_Worksheet* wsFldStr = Worksheet_new();	//各フィールドの文字	[行番号,列番号]
		ST_Worksheet* wsMaxLen = Worksheet_new();	//各列の最大文字数	[列番号]
		//--- 入力処理 ---
		{
			char* glbuf = NULL;	//┬getline()用バッファ
			int   glcap = 0;	//┘
			FILE* fp = stdin;	//入力ファイルが指定されていなければ、標準入力を一回処理する。
			do {
				if(optind < argc) {	//入力ファイルが指定されていたら…
					fp = fopen(argv[optind], "r");		//入力ファイルを開く。
					if(!fp) { error(EXIT_FAILURE, errno, "%sが開けません。", argv[optind]); }
				}
				while(getline(&glbuf, &glcap, fp) != EOF) {	//一行読み込む。ファイル終端でなければ…
					int iCol, nCol;
					char** v;
					strchomp(glbuf);				//行末の、改行,及び,空白文字を削除する。
					v = strsplit(glbuf, "\t", 0);			//入力行をタブ文字で分割し、フィールド配列を得る。
					nCol = strv_length(v);				//列数を取得する。
					if(nCol > iMaxCol) { iMaxCol = nCol; }		//最大列数を更新する。
					Worksheet_set_l(wsRowSiz, nCol, nRow, -1);	//各行の列数を格納する。
					for(iCol = 0; iCol < nCol; iCol++) {		//各フィールドについて…
						char* s = v[iCol];
						int len = strlen(s);
						Worksheet_set_l(wsFldStr, quark_from_string(s), nRow, iCol, -1);				//各フィールドの文字を格納する。
						if(len > Worksheet_get_l(wsMaxLen, iCol, -1)) { Worksheet_set_l(wsMaxLen, len, iCol, -1); }	//各列の最大文字数を更新する。
					}
					strfreev(v);	//フィールド配列を開放する。
					nRow++;		//次の行へ進める。
				}
				if(optind < argc) {	//入力ファイルが指定されていたら…
					fclose(fp);	//入力ファイルを閉じる。
					optind++;	//次の入力ファイルへ進める。
				}
			} while(optind < argc);
			free(glbuf);		//getline()用バッファを開放する。
		}
		//--- 出力処理 ---
		{
			//(TabWidth=8の場合)
			//最大文字数が 0〜 7の場合					タブ追加不要
			//最大文字数が 8〜15の場合	フィールド文字数が 0〜 7の場合	タブ 1個追加
			//				フィールド文字数が 8〜15の場合	タブ追加不要
			//最大文字数が16〜23の場合	フィールド文字数が 0〜 7の場合	タブ 2個追加
			//				フィールド文字数が 8〜15の場合	タブ 1個追加
			//				フィールド文字数が16〜23の場合	タブ追加不要
			//最大文字数が24〜31の場合	フィールド文字数が 0〜 7の場合	タブ 3個追加
			//				フィールド文字数が 8〜15の場合	タブ 2個追加
			//				フィールド文字数が16〜23の場合	タブ 1個追加
			//				フィールド文字数が24〜31の場合	タブ追加不要
			//…以上の例から、ルールを推測して処理した。
			int iRow;
			for(iRow = 0; iRow < nRow; iRow++) {
				int iCol, nCol = Worksheet_get_l(wsRowSiz, iRow, -1);
				for(iCol = 0; iCol < nCol; iCol++) {
					const char* s = quark_to_string(Worksheet_get_l(wsFldStr, iRow, iCol, -1));	//各フィールドの文字を取得する。
					int x = strlen(s)                           / TabWidth;	//フィールド文字数/TabWidth
					int y = Worksheet_get_l(wsMaxLen, iCol, -1) / TabWidth;	//最大文字数      /TabWidth
					fputs(s, stdout);
					if(iCol < nCol - 1) {					//この行の最後のフィールドでなければ…
						while(x++ <= y) { fputc('\t', stdout); }	//上記のルールに従って追加するタブ(0〜?個)と、次のフィールドとの区切りのためのタブ(1個)を出力する。
					}
				}
				fputc('\n', stdout);
			}
		}
		Worksheet_free(wsRowSiz);
		Worksheet_free(wsFldStr);
		Worksheet_free(wsMaxLen);
	}
	return EXIT_SUCCESS;
}
