/*
 *	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側のツールプログラムから使用する目的で、用意しました。
 *	* Fri Nov 20 21:44:18 JST 2015 Naoyuki Sawa
 *	- GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
 */
#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.

// * Fri Nov 20 21:44:18 JST 2015 Naoyuki Sawa
// - GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
//   □「Linux Programmer's Manual - GETOPT」(http://linuxjm.osdn.jp/html/LDP_man-pages/man3/getopt.3.html)より引用:
//   │optstringは受け付けるオプション文字からなる文字列である。														┐
//   │文字のあとにコロン(:)が置かれている場合は、オプションには引き数が必要であることを示す。											├この動作は既に対応していた。
//   │このときgetopt()は、現在注目しているargv要素で、オプション文字に引き続くテキストへのポインターか、あるいは次のargv要素のテキストへのポインターをoptargに代入する。	┘
//   │2個連続してコロンが置かれている場合は、そのオプションは引き数をとってもとらなくてもよい。										┐
//   │現在のargv要素にテキストがあれば(つまり、"-oarg"のように、オプション名自身と同じワード内にテキストがある場合)、それがoptargに返される。					├この動作を本日追加対応した。
//   │なければoptargは0に設定される。これはGNUによる拡張である。														┘

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 = "";
	}
    //{{2015/11/20追加:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
	/* オプション引数をクリアしておきます。 */
	optarg = NULL;
    //}}2015/11/20追加:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
	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 '?';
	}
    //{{2015/11/20コメント変更:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
    //	/* オプション引数が必要なオプション文字だったならば… */
    //↓2015/11/20コメント変更:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
	/* オプション引数が必要なオプション文字,又は,オプション引数を取っても取らなくても良いオプション文字だったならば… */
    //}}2015/11/20コメント変更:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
	if(*(optstring + 1) == ':') {
		/* 走査ポインタが終端だったならば… */
		if(*scanptr == '\0') {
    //{{2015/11/20追加:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
			/* オプション引数を取っても取らなくても良いオプション文字だったならば… */
			if(*(optstring + 2) == ':') {
				/* オプション文字を返します。 */
				return optopt;
			}
    //}}2015/11/20追加:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
			/* 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
    //{{2015/11/20追加:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
	//〓〓〓'オプション引数を取っても取らなくても良いオプション文字'の例〓〓〓
	test("test8 -a -aone -a two", "a::");
	//□結果                ~~~'オプション引数を取っても取らなくても良いオプション文字'の場合、空白を挟むとオプション引数と見なされない事に注意せよ。
	//│a a (null)
	//│a a one
	//│a a (null)
	//│[4] two
    //}}2015/11/20追加:GNU拡張の'オプション引数を取っても取らなくても良いオプション文字'に対応しました。
	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); /* 未実装 */
