/*
 *	app.c
 *
 *	natsort.exe - "自然順"アルゴリズムでテキストファイルをソートする
 *	Copyright (C) 2015-2016 Naoyuki Sawa
 *
 *	* Thu Oct 15 21:01:52 JST 2015 Naoyuki Sawa
 *	- 1st リリース。
 *	* Fri Oct 16 21:16:43 JST 2015 Naoyuki Sawa
 *	- strnatcmp(),strnatcasecmp()の動作を変更した事に伴い、当ツールの処理も変更しました。
 *	  詳細は、clipstr.hのstrnatcmp(),strnatcasecmp()のコメントを参照して下さい。
 *	* 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に含まれるようになったからです。
 *	  当ツールの動作には変更有りません。
 */
#include "app.h"
#define VERSION		"20160221"	//最終更新日
/****************************************************************************
 *	
 ****************************************************************************/
__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;
}
/****************************************************************************
 *	subr
 ****************************************************************************/
static int opt_i, opt_r;
/*--------------------------------------------------------------------------*/
static int compar(const void* _x, const void* _y) {
	char* x = *(char**)_x;
	char* y = *(char**)_y;
//{{---
  //	int diff = opt_i ? strnatcasecmp(x, y) : strnatcmp(x, y);
  //	return opt_r ? -diff : diff;
//↓---
//↓■問題点
//↓上の方法では不十分です。
//↓たとえば、入力ファイルが下記のような場合、結果が不定になります。
//↓│123
//↓│ 123
//↓なぜ結果が不定になるかというと、strnatcmp()は空白文字を除外して比較するので、一行目と二行目を'同じ'と見なすからです。
//↓'同じ'と見なした行に対して、qsort()は不安定ソートですから、qsort()の実装に依存してどちらが先になるかが予測出来ません。
//↓従って、上記のソート結果は
//↓│123
//↓│ 123
//↓になったり、
//↓│ 123
//↓│123
//↓になったりします。元ファイルの行の順番と、処理系のqsort()の実装に依存します。
//↓■対策
//↓対策として、strnatcmp()が'同じ'と見なした場合は、strcmp()で比較した結果を返す事にしました。
//↓そうする事によって、異なる文字列に対しては、順番が一意に決まります。
//↓■工夫した点
//↓工夫した点として、strcmp()の結果を逆にして使用する事にしました。
//↓もしstrcmp()の結果を逆にせずに使用すると、前出の例のソート結果はこうなります。
//↓│ 123
//↓│123
//↓strcmp()は、空白文字よりも'1'を大きいと見なすからです。
//↓しかし、直感的には、以下のようにソートされる方が自然だと思います。
//↓│123
//↓│ 123
//↓strcmp()は、空白文字よりも'1'を小さいと見なすようにすれば解決出来ます。
//↓strnatcmp()が'同じ'と見なしたと言う事は、既に、両方の文字列の空白文字以外は一致している事が判っているわけですから、
//↓strcmp()の結果を正負逆にすれば(=空白文字よりも'1'を小さいと見なすようにすれば)、解決する訳です。
//↓■注意点
//↓上記の対策を行っても、両方の文字列の空白文字の種類が異なる時に、期待した結果と違う結果になる場合が有ります。
//↓│<SPC><SPC>123
//↓│<TAB>123
//↓一行目の方が空白文字が二つ有るので後にソートされて欲しいのですが、<SPC>=32,<TAB>=9なので、その逆順で上の結果になってしまいます。
//↓この問題を解決するには、比較する前に、行内の空白文字を全て<SPC>に統一してから比較する方法などがあります。
//↓しかし、実用上はこの問題が気になる事はほとんど無いし、このままでも大きな問題にはならないと思うので、このままにする事にしました。
//↓---
  //	int         diff = opt_i ? strnatcasecmp(x, y) : strnatcmp(x, y);
  //	if(!diff) { diff = opt_i ?   -strcasecmp(x, y) :   -strcmp(x, y); }
  //	return opt_r ? -diff : diff;
//↓---
//↓■問題点
//↓上の方法でもまだ不十分でした。
//↓たとえば、入力ファイルが下記のような場合、明らかに不自然な結果になります。
//↓│123 
//↓│123
//↓strcmp()の結果を正負逆にする弊害で、行の末尾に有る空白文字だけの違いである場合、行の末尾に空白文字が有る方が上に来てしまいます。
//↓■対策
//↓この問題を避けるために、行の末尾に有る空白文字だけの違いである場合は、strcmp()の結果を正負逆にせずに比較する事にしました。
//↓---
  //	//まず"自然順"アルゴリズムで比較してみる。一致しなければ、それが結果となる。
  //	int diff = opt_i ? strnatcasecmp(x, y) : strnatcmp(x, y);
  //	//"自然順"アルゴリズムで比較した結果が、一致だったならば…
  //	if(!diff) {
  //		char *xTmp, *yTmp;
  //		//末尾の空白を取り除いた、一時的な文字列を作成する。
  //		xTmp = strtrim(x, 2/*右端にある指定文字を除去する*/, NULL/*"\t\n\v\f\r ":isspace()と同等*/);
  //		yTmp = strtrim(y, 2/*右端にある指定文字を除去する*/, NULL/*"\t\n\v\f\r ":isspace()と同等*/);
  //		//末尾の空白を取り除いた結果で比較する。
  //		diff = opt_i ? strcasecmp(xTmp, yTmp) : strcmp(xTmp, yTmp);
  //		//末尾の空白を取り除いた結果が一致したら…
  //		if(!diff) {
  //			//元の文字列をstrcmp()で比較した結果をそのまま使用する。要するに、末尾の空白部分での比較となる。
  //			diff = opt_i ?  strcasecmp(x, y) :  strcmp(x, y);
  //		} else {
  //			//元の文字列をstrcmp()で比較した結果を正負逆にして使用する。要するに、不一致箇所で空白文字が先に現れる方が後になる。
  //			diff = opt_i ? -strcasecmp(x, y) : -strcmp(x, y);
  //		}
  //		//一時的な文字列を開放する。
  //		free(xTmp);
  //		free(yTmp);
  //	}
  //	//'-r'オプションが指定されていたら、逆順とする。
  //	return opt_r ? -diff : diff;
//↓---
//↓ * Fri Oct 16 21:16:43 JST 2015 Naoyuki Sawa
//↓ - strnatcmp(),strnatcasecmp()の動作を変更した事に伴い、当ツールの処理も変更しました。
//↓   詳細は、clipstr.hのstrnatcmp(),strnatcasecmp()のコメントを参照して下さい。
//↓---
	int diff;
	//"自然順"アルゴリズムで比較する。一致しなければ、それが結果となる。
	diff = opt_i ? strnatcasecmp(x, y)
	             :     strnatcmp(x, y);
	//一致だったならば…
	if(!diff) {
		//先頭部分の空白文字の数を数える。先頭部分の空白文字が多い方を、後とする。
		// - "123"＜" 123"＜"  123" の順にするのが自然だと思ったからです。
		//   strnatcmp(),strnatcasecmp()は汎用性を重視するためにこの判断基準は含んでおらず、当ツール独自の判断基準です。
		diff = strspn(x, "\t\n\v\f\r "/*isspace()と同等*/)
		     - strspn(y, "\t\n\v\f\r "/*isspace()と同等*/);
		//同じ数だったならば…
		if(!diff) {
			//あきらめて、strcmp()で比較した結果をそのまま返します。
			//もしかしたら直感と違う結果になるかも知れませんが、これ以上複雑な条件を設けても判りづらいと思うので、ここで打ち切る事にしました。
			diff = opt_i ? strcasecmp(x, y)
			             :     strcmp(x, y);
		}
	}
	//'-r'オプションが指定されていたら、逆順とする。
	return opt_r ? -diff : diff;
//}}---
}
/*--------------------------------------------------------------------------*/
static int subr(int argc, char* argv[]) {
	char** lines;
	int  n_lines;
	{
		int opt;
		opterr = 0;	//オプションエラーは当プログラム内で処理するので、getopt()内でのエラー表示は不要。
		while((opt = getopt(argc, argv, "hir")) != -1) {
			switch(opt) {
			default:
			case 'h':
				usage();
				break;
			case 'i':
				opt_i = 1;
				break;
			case 'r':
				opt_r = 1;
				break;
			}
		}
	}
	{
		char* glbuf = NULL;	//┬getline()用
		int   glcap = 0;	//┘
		struct obstack o;
		obstack_init(&o);
		if(optind < argc) {
			do {
				FILE* fp = fopen(argv[optind], "r");
				if(!fp) { error(EXIT_FAILURE, errno, "%s が開けません。", argv[optind]); }
				while(getline(&glbuf, &glcap, fp) != EOF) {
					//getline()は行末の改行文字を削除せずにそのまま格納しますが、ここで取り除いておく事にしました。
					//そうしないと、「123<TAB><CR>」の方が「123<CR>」よりも上にソートされてしまう問題が生じるからです。
					//<TAB>よりも<CR>の方が文字コードが大きいからです。
					//予め<CR>を削除しておけば、「123<TAB>」と「123」の比較になり、「123」「123<TAB>」の順になります。
					obstack_ptr_grow(&o, strtrim(glbuf, 2/*右端にある指定文字を除去する*/, "\r\n"));	//"\n"だけの指定でも構わないのだが、安全のために"\r"も含める事にした。
				}
				fclose(fp);
			} while(++optind < argc);
		} else {
			while(getline(&glbuf, &glcap, stdin) != EOF) {
				//getline()は行末の改行文字を削除せずにそのまま格納しますが、ここで取り除いておく事にしました。
				//そうしないと、「123<TAB><CR>」の方が「123<CR>」よりも上にソートされてしまう問題が生じるからです。
				//<TAB>よりも<CR>の方が文字コードが大きいからです。
				//予め<CR>を削除しておけば、「123<TAB>」と「123」の比較になり、「123」「123<TAB>」の順になります。
				obstack_ptr_grow(&o, strtrim(glbuf, 2/*右端にある指定文字を除去する*/, "\r\n"));	//"\n"だけの指定でも構わないのだが、安全のために"\r"も含める事にした。
			}
		}
		free(glbuf);
		n_lines = obstack_object_size(&o) / sizeof(char*);	//┬この処理順序は必須です。
		lines   = obstack_finish(&o);				//┘
		//ここで'obstack_free(&o,NULL)'しないように注意せよ。もしそうするとlinesも開放されてしまうからです。
	}
	{
		int i;
		qsort(lines, n_lines, sizeof(char*), compar);
		for(i = 0; i < n_lines; i++) {
			fputs(lines[i], stdout);
			fputc('\n',     stdout);	//読み込み時に行末の改行文字を削除しているので、出力時には明示的に改行する必要が有る。
		}
	}
	return EXIT_SUCCESS;
}
/*--------------------------------------------------------------------------*/
__declspec(noreturn) static void usage() {
	fprintf(stderr, "natsort.exe - \"自然順\"アルゴリズムでテキストファイルをソートする (%s)\n", VERSION);
	fprintf(stderr, "Copyright (C) 2015-2016 Naoyuki Sawa\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "USAGE:\n");
	fprintf(stderr, "  natsort.exe [filename ...]\n");
	fprintf(stderr, "  ・filenameを指定しなければ、標準入力から読み込みます。\n");
	fprintf(stderr, "  ・filenameは、ワイルドカードによる複数指定も可能です。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "OPTIONS:\n");
	fprintf(stderr, "  -i 大文字小文字を区別せずに比較します。\n");
	fprintf(stderr, "  -r 比較の結果を逆順にします。\n");
	fprintf(stderr, "  -h 使い方を表示します。\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "DESCRIPTION:\n");
	fprintf(stderr, "  自然順文字列比較の詳細は、strnatcmp,strnatcasecmpの実装を参照して下さい。\n");
	exit(EXIT_FAILURE);
}
