/*
 *	getopt.c
 *
 *	オプション解析
 *
 *	* Sun Mar 10 19:53:04 JST 2013 Naoyuki Sawa
 *	- 1st リリース。
 *	- POSIX標準のgetopt()互換関数を実装しました。
 *	- GNU拡張のgetopt_long()とgetopt_long_only()は、宣言のみ行いました。
 *	  今回は、未実装です。今後、必要に応じて実装する予定です。
 *	- UNIXでは、getopt()は<unistd.h>,getopt_long()とgetopt_long_only()は<getopt.h>で宣言されています。
 *	  しかし、P/ECEとWindowsには<unistd.h>が無いので、今回はすべて<getopt.h>で宣言することにしました。
 *	- getopt()を、P/ECEのアプリケーションで使用することは、ほぼ無いと思います。
 *	  主に、Windows側のツールプログラムから使用する目的で、用意しました。
 */
#include "clip.h"

/****************************************************************************
 *	グローバル変数
 ****************************************************************************/

char* optarg;
int   optind = 1;
int   optopt;
int   opterr = 1;
int   optreset;

/****************************************************************************
 *	getopt
 ****************************************************************************/

//□「GETOPT(3)」(http://www.unix.com/man-page/FreeBSD/3/getopt/)より引用:
//│The getopt() function incrementally parses a command line argument list argv and returns the next known option character.
//│An option character is known if it has been specified in the string of accepted option characters, optstring.
//│
//│The option string optstring may contain the following elements:
//│ individual characters, and characters followed by a colon to indicate an option argument is to follow.
//│For example, an option string "x" recognizes an option "-x", and an option string "x:" recognizes an option and argument "-x argument".
//│It does not matter to getopt() if a following argument has leading white space.
//│
//│On return from getopt(), optarg points to an option argument, if it is anticipated,
//│ and the variable optind contains the index to the next argv argument for a subsequent call to getopt().
//│The variable optopt saves the last known option character returned by getopt().
//│
//│The variables opterr and optind are both initialized to 1.
//│The optind variable may be set to another value before a set of calls to getopt() in order to skip over more or less argv entries.
//│
//│In order to use getopt() to evaluate multiple sets of arguments, or to evaluate a single set of arguments multiple times,
//│ the variable optreset must be set to 1 before the second and each additional set of calls to getopt(), and the variable optind must be reinitialized.
//│
//│The getopt() function returns -1 when the argument list is exhausted.
//│The interpretation of options in the argument list may be cancelled by the option '--' (double dash)
//│ which causes getopt() to signal the end of argument processing and return -1.
//│When all options have been processed (i.e., up to the first non-option argument), getopt() returns -1.

int getopt(int argc, char* const argv[], const char* optstring) {
	static char* scanptr = ""; /* 走査ポインタ */
	//
	int optstring_first_character; /* optstringの先頭文字 */
	/* optstringの先頭文字を取得し、それが':'だったならば、optstringが':'の後を指すようにします。 */
	if((optstring_first_character = *optstring) == ':') { optstring++; }
	/* optresetがセットされていたら… */
	if(optreset != 0) {
		/* optresetをクリアします。 */
		optreset = 0;
		/* 走査ポインタを初期化します。 */
		scanptr = "";
	}
	for(;;) {
		/* オプション文字を取得します。 */
		optopt = *scanptr++;
		/* オプション文字が有れば、抜けます */
		if(optopt != '\0') { break; }
		/* optindが引数リストを超えていたら、オプション解析を終了します。 */
		if((unsigned)optind >= (unsigned)argc) { goto L_END; }
		/* 走査ポインタを取得します。 */
		scanptr = argv[optind];
		/* 引数要素の1文字目が'-'でなければ、オプション解析を終了します。 */
		if(*scanptr++ != '-') { goto L_END; }
		/* 走査ポインタが終端ならば、optindを次へ進めておきます。(単独"-"を無視し、無限ループを回避するため) */
		if(*scanptr == '\0') { optind++; }
	}
	/* "--"に相当するオプション指定ならば… */
	if(optopt == '-') {
		/* optindを次へ進めます。 */
		optind++;
		/* オプション解析を終了します。 */
		goto L_END;
	}
	/* 走査ポインタが終端ならば、optindを次へ進めておきます。 */
	if(*scanptr == '\0') { optind++; }
	/* optstringから、オプション文字を検索します。 */
	optstring = strchr(optstring, optopt);
	/* optstringの中に、オプション文字が無ければ… */
	if(optstring == NULL) {
		/* opterrがセットされていたら、エラーメッセージを表示します。 */
		if(opterr != 0) { die("illegal option '%c'", optopt); }
		/* '?'を返します。 */
		return '?';
	}
	/* オプション引数が必要なオプション文字だったならば… */
	if(*(optstring + 1) == ':') {
		/* 走査ポインタが終端だったならば… */
		if(*scanptr == '\0') {
			/* optindが引数リストを超えていたら… */
			if((unsigned)optind >= (unsigned)argc) {
				/* optstringの先頭文字が':'だったならば、エラーメッセージを表示せずに':'を返します。 */
				if(optstring_first_character == ':') { return ':'; }
				/* opterrがセットされていたら、エラーメッセージを表示します。 */
				if(opterr != 0) { die("option '%c' requires an argument", optopt); }
				/* '?'を返します。 */
				return '?';
			}
			/* 走査ポインタを取得します。 */
			scanptr = argv[optind];
		}
		/* 引数要素の残り文字列を、オプション引数とします。 */
		optarg = scanptr;
		/* optindを次へ進めます。 */
		optind++;
		/* 走査ポインタを初期化します。 */
		scanptr = "";
	}
	/* オプション文字を返します。 */
	return optopt;
L_END:
	/* 走査ポインタを初期化します。 */
	scanptr = "";
	/* -1を返します。 */
	return -1;
}

/*---------------------------------------------------------------------------*/
#if 0
/* getopt()のテスト */
static void test(const char* cmdline, const char* optstring) {
	int c;
	int argc = 0;
	char* argv[10];
	char* tmp = strdup(cmdline);
	char* ptr = strtok(tmp, " ");
	while(ptr != NULL) { argv[argc++] = ptr; ptr = strtok(NULL, " "); }
	printf("--- %s ---\n", argv[0]);
	while((c = getopt(argc, argv, optstring)) != -1) {
		ptr = strchr(optstring, c);
		printf("%c %c %s\n", c, optopt, ((ptr != NULL) && (*(ptr + 1) == ':')) ? optarg : "");
	}
	while(optind < argc) { printf("[%d] %s\n", optind, argv[optind]); optind++; }
	free(tmp);
	optreset = optind = 1; //次の引数セットの評価のために、再初期化を指示しておく
}
int main() {
	//〓〓〓オプションを個別に指定する例〓〓〓
	test("test1 -a -b one two", "ab");
	//□結果
	//│a a
	//│b b
	//│[3] one
	//│[4] two
	//〓〓〓オプションをまとめて指定する例〓〓〓
	test("test2 -ab one two", "ab");
	//□結果
	//│a a
	//│b b
	//│[2] one
	//│[3] two
	//〓〓〓"--"でオプション解析を終了する例〓〓〓
	test("test3 -a -- -b one two", "ab");
	//□結果
	//│a a
	//│[3] -b
	//│[4] one
	//│[5] two
	//〓〓〓空白無しでオプション引数を付与する例〓〓〓
	test("test4 -a -barg one two", "ab:");
	//□結果
	//│a a
	//│b b arg
	//│[3] one
	//│[4] two
	//〓〓〓空白有りでオプション引数を付与する例〓〓〓
	test("test5 -a -b arg one two", "ab:");
	//□結果
	//│a a
	//│b b arg
	//│[4] one
	//│[5] two
	//〓〓〓オプション引数が不足している時の動作例(1)〓〓〓
	test("test6 -a", "a:");
	//□結果
	//│option 'a' requires an argument
	//│? a
	//〓〓〓オプション引数が不足している時の動作例(2)〓〓〓
	test("test7 -a", ":a:");
	//□結果
	//│: a
	return 0;
}
#endif

/****************************************************************************
 *	getopt_long
 ****************************************************************************/

int getopt_long(int argc, char* const argv[], const char* optstring, const struct option* longopts, int* longindex); /* 未実装 */

/****************************************************************************
 *	getopt_long_only
 ****************************************************************************/

int getopt_long_only(int argc, char* const argv[], const char* optstring, const struct option* longopts, int* longindex); /* 未実装 */
