/*
 *	clipdire.c
 *
 *	POSIX準拠のディレクトリ制御ルーチン
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2016 Naoyuki Sawa
 *
 *	* Wed Jan 26 00:13:38 JST 2011 Naoyuki Sawa
 *	- 1st リリース。
 *	- P/ECE開発環境、および、EPSONのS1C33開発環境には、POSIX準拠のdirent.hヘッダファイルが含まれていません。
 *	  P/ECE APIのpceFileFindOpen(),pceFileFindNext(),pceFileFindClose()を直接使えば充分なので、無くても困らないのですが、
 *	  Webや書籍のサンプルプログラム等をコンパイルする時に必要になる場合に備えて、用意しておくことにしました。
 *	  具体的には、「プログラミング言語Lua公式解説書」のサンプルプログラムをコンパイルするために用意しました。
 *	- P/ECEファイルシステムには、サブディレクトリが有りません。
 *	  opendir()関数の引数 dirname は無視し、常にルートディレクトリを開く実装としました。
 *	* Thu Jan 27 00:39:00 JST 2011 Naoyuki Sawa
 *	- rewinddir(),telldir(),seekdir()を追加しました。
 *	- scandir(),alphasort(),versionsort()の宣言だけを追加しました。未実装です。今後、必要になった際に、実装する予定です。
 *	* Sat Feb 05 01:53:09 JST 2011 Naoyuki Sawa
 *	- scandir(),alphasort()を実装しました。
 *	* Wed May 21 15:29:53 JST 2014 Naoyuki Sawa
 *	- Windows版のopendir(),readdir(),closedir()を実装しました。
 *	* Mon Aug 11 19:48:16 JST 2014 Naoyuki Sawa
 *	- versionsort(),strverscmp()を実装しました。
 *	* Thu Jan 08 21:00:51 JST 2015 Naoyuki Sawa
 *	- Visual C++ 6.0でビルドする時のために、strverscmp()の宣言を追加しました。
 *	  P/ECEでビルドする時は、include/string.hでstrverscmp()が宣言されているので問題有りませんが、
 *	  Visual C++ 6.0でビルドする時は、Visual C++ 6.0のstring.hにはstrverscmp()が含まれていないので、versionsort()からstrverscmp()への前方参照が警告になるためです。
 *	  ここでstrverscmp()を宣言することにより、Visual C++ 6.0でビルドする時も警告が出なくなりました。
 *	  尚、P/ECEでビルドする時もここで宣言されていても害は無いのですが、最小限とするために、P/ECEでビルドする時はここでは宣言しないことにしました。
 *	* Tue Sep 06 21:06:46 JST 2016 Naoyuki Sawa
 *	- 64ビット対応のために、VS2015では_findfirst()が返した検索ハンドルを格納する変数の型を(long)⇒(intptr_t)に変更しました。
 *	  VC++6.0の頃は(long)だったのですが、VS2015までの間のどこかで(intptr_t)に仕様変更になっていたようです。
 *	  32ビットならばどちらでも同じですが、64ビットの場合は正しく(intptr_t)で扱わないと_findnext()が異常終了します。
 *	- 尚、VC++6.0は32ビット専用なので(intptr_t)に統一しても構わないのですが、VC++6.0の_findfirst()の仕様は(long)である事を強調するために、敢えて条件コンパイルする事にしました。
 */
#include "clip.h"

#ifdef  _MSC_VER
// * Thu Jan 08 21:00:51 JST 2015 Naoyuki Sawa
// - Visual C++ 6.0でビルドする時のために、strverscmp()の宣言を追加しました。
//   P/ECEでビルドする時は、include/string.hでstrverscmp()が宣言されているので問題有りませんが、
//   Visual C++ 6.0でビルドする時は、Visual C++ 6.0のstring.hにはstrverscmp()が含まれていないので、versionsort()からstrverscmp()への前方参照が警告になるためです。
//   ここでstrverscmp()を宣言することにより、Visual C++ 6.0でビルドする時も警告が出なくなりました。
//   尚、P/ECEでビルドする時もここで宣言されていても害は無いのですが、最小限とするために、P/ECEでビルドする時はここでは宣言しないことにしました。
int strverscmp(const char* s1, const char* s2);
#endif//_MSC_VER

/****************************************************************************
 *	P/ECE版
 ****************************************************************************/
#ifdef  PIECE
/*--------------------------------------------------------------------------*/
DIR* opendir(const char* dirname) {
	FILEINFO* pfi = malloc(sizeof(FILEINFO));
	if(!pfi) DIE();
	pceFileFindOpen(pfi);	/* 常に0(正常終了) */
	return (DIR*)pfi;
}
/*--------------------------------------------------------------------------*/
struct dirent* readdir(DIR* dirp) {
	FILEINFO* pfi = (FILEINFO*)dirp;
	if(!pceFileFindNext(pfi)) return NULL;	/* 0(ファイルが見つからない) */
	return (struct dirent*)pfi->filename;	/* 1(ファイルが見つかった) */
}
/*--------------------------------------------------------------------------*/
int closedir(DIR* dirp) {
	FILEINFO* pfi = (FILEINFO*)dirp;
	pceFileFindClose(pfi);	/* 常に0(正常終了) */
	free(pfi);
	return 0;
}
/*--------------------------------------------------------------------------*/
void rewinddir(DIR* dirp) {
	FILEINFO* pfi = (FILEINFO*)dirp;
	pceFileFindClose(pfi);	/* 常に0(正常終了) */
	pceFileFindOpen(pfi);	/* 常に0(正常終了) */
}
/*--------------------------------------------------------------------------*/
/*{{C:\usr\PIECE\sysdev\pcekn\file.cより*/
typedef struct tagWORKS {
	DIRECTORY *pdir;
} WORKS;
/*}}C:\usr\PIECE\sysdev\pcekn\file.cより*/
/*--------------------------------------------------------------------------*/
long telldir(DIR* dirp) {
	FILEINFO* pfi = (FILEINFO*)dirp;
	WORKS* pw = (WORKS*)pfi->works;
	return (long)pw->pdir;
}
/*--------------------------------------------------------------------------*/
void seekdir(DIR* dirp, long offset) {
	FILEINFO* pfi = (FILEINFO*)dirp;
	WORKS* pw = (WORKS*)pfi->works;
	pw->pdir = (DIRECTORY*)offset;
}
/*--------------------------------------------------------------------------*/
#endif/*PIECE*/

/****************************************************************************
 *	Windows版
 ****************************************************************************/
#ifdef  _MSC_VER
/*--------------------------------------------------------------------------*/
struct __dirstream {
	int count;			/* readdir()が呼ばれた回数 */
//{{2016/09/06変更:64ビット対応のために、VS2015では_findfirst()が返した検索ハンドルを格納する変数の型を(long)⇒(intptr_t)に変更しました。
//	int handle;			/* find系関数の検索ハンドル */
//↓2016/09/06変更:64ビット対応のために、VS2015では_findfirst()が返した検索ハンドルを格納する変数の型を(long)⇒(intptr_t)に変更しました。
    #if     (_MSC_VER <= 1200)
	int handle;			/* find系関数の検索ハンドル */		//VC++6.0では、_findfirst()が返した検索ハンドルを格納する変数の型は(long)です。
    #else //(_MSC_VER <= 1200)
	intptr_t handle;		/* find系関数の検索ハンドル */		//VS2015では、_findfirst()が返した検索ハンドルを格納する変数の型は(intptr_t)です。VS.NET〜VS2013は不明ですが、仮にVS2015と同じ(intptr_t)としました。
    #endif//(_MSC_VER <= 1200)
//}}2016/09/06変更:64ビット対応のために、VS2015では_findfirst()が返した検索ハンドルを格納する変数の型を(long)⇒(intptr_t)に変更しました。
	struct _finddata_t fileinfo;	/* find系関数のファイル情報バッファ */
};
/*--------------------------------------------------------------------------*/
DIR* opendir(const char* dirname) {
	char filespec[_MAX_PATH];
	DIR* dirp = calloc(1, sizeof(DIR));
	if(!dirp) { DIE(); }
	_makepath(filespec, NULL, dirname, "*", NULL);	//┬どちらでも良い。「*」でも「*.*」でも、全てのファイル名に一致する。
//	_makepath(filespec, NULL, dirname, "*", "*");	//┘具体的には、「*」も拡張子の有るファイル名に一致するし、「*.*」も拡張子の無いファイル名に一致する。
	dirp->handle = _findfirst(filespec, &dirp->fileinfo);			/* 1個目の一致ファイル */
	if(dirp->handle == -1) { DIE(); }
	return dirp;
}
/*--------------------------------------------------------------------------*/
struct dirent* readdir(DIR* dirp) {
	if(dirp->count++) {
		if(_findnext(dirp->handle, &dirp->fileinfo)) { return NULL; }	/* 2個目以降の一致ファイル */
	}
	return (struct dirent*)dirp->fileinfo.name;
}
/*--------------------------------------------------------------------------*/
int closedir(DIR* dirp) {
	if(_findclose(dirp->handle)) { DIE(); }
	free(dirp);
	return 0;
}
/*--------------------------------------------------------------------------*/
//void rewinddir(DIR* dirp);		/* Windows版は未実装 */
/*--------------------------------------------------------------------------*/
//long telldir(DIR* dirp);		/* Windows版は未実装 */
/*--------------------------------------------------------------------------*/
//void seekdir(DIR* dirp, long offset);	/* Windows版は未実装 */
/*--------------------------------------------------------------------------*/
#endif/*_MSC_VER*/

/****************************************************************************
 *	P/ECE版,Windows版(共通)
 ****************************************************************************/
int scandir(const char* dirname, struct dirent*** namelist, int (*filter)(const struct dirent*), int (*compar)(const void* /* struct dirent** */, const void* /* struct dirent** */)) {
	int i = 0;
	DIR* d;
	struct dirent* e;
	/* エントリ配列を初期化します。 */
	*namelist = NULL;
	/* ディレクトリを開きます。 */
	if((d = opendir(dirname)) == NULL) {
		DIE();
	}
	/* ディレクトリの各エントリについて… */
	while((e = readdir(d)) != NULL) {
		/* フィルタ関数が指定されていないか、又は、フィルタ関数によって選択されたら… */
		if((filter == NULL) || (filter(e) != 0)) {
			/* エントリ配列を拡張します。 */
			if((*namelist = realloc(*namelist, sizeof(struct dirent*) * (i + 1))) == NULL) {
				DIE();
			}
			/* エントリを複製するための、メモリを確保します。 */
			if(((*namelist)[i] = malloc(sizeof(struct dirent))) == NULL) {
				DIE();
			}
			memcpy((*namelist)[i++], e, sizeof(struct dirent));
		}
	}
	/* ディレクトリを閉じます。 */
	if(closedir(d) != 0) {
		DIE();
	}
	/* エントリ比較関数が指定されていたら、エントリ比較関数を使ってソートします。 */
	if(compar != NULL) {
		qsort(*namelist, i, sizeof(struct dirent*), compar);
	}
	/* 選択されたエントリ数を返します。
	 * - 選択されたエントリ数が 0 の場合は、*namelist=NULL のままです。
	 *   呼び出し側が、free(*namelist) を行いますが、free(NULL) は安全なので問題ありません。
	 */
	return i;
}
/*--------------------------------------------------------------------------*/
int alphasort(const void* /* const struct dirent** */ a, const void* /* const struct dirent** */ b) {
	return strcmp((*(const struct dirent**)a)->d_name, (*(const struct dirent**)b)->d_name);
}
/*--------------------------------------------------------------------------*/
int versionsort(const void* /* const struct dirent** */ a, const void* /* const struct dirent** */ b) {
	return strverscmp((*(const struct dirent**)a)->d_name, (*(const struct dirent**)b)->d_name);
}

/****************************************************************************
 *	標準Cライブラリの補助 (P/ECE版,Windows版(共通))
 ****************************************************************************/
/* 2つのバージョン文字列を比較する。
 * [in,out]
 *	□http://linuxjm.sourceforge.jp/html/LDP_man-pages/man3/strverscmp.3.html」より引用:
 *	│両方の文字列が等しい場合、0を返す。
 *	│それ以外の場合、その直前までは両方の文字列が等しく、その直後のバイトで両者に違いがあるような、バイトの境界を探す。
 *	│見つかったバイト境界を含む数字列(数字だけの文字列)の最長一致検索を行う。(数字列は境界から始まっていても、境界で終わっていてもよい)
 *	│2つの文字列から得られた数字列の一方または両方が空であれば、strcmp()が返した結果を関数の返り値として返す。
 *	│すなわち、バイト値を比較した結果を返す。
 *	│それ以外の(数字列が両方とも空でない)場合、両方の数字列を数字順で比較する。
 *	│このとき、一つ以上の0が先頭にある数字列は、前に小数点がついているものと解釈される。(先頭に0が多くある数字列ほど前に来ることになる)
 *	│この結果、順序は次のようになる:000,00,01,010,09,0,1,9,10
 *	                                        ~~     ~~ ~
 *	※「0(=0.0)」が、「01(=0.01)」や「09(=0.09」よりも、大きいと見なす点が、直観に反するが、そういう仕様であるようだ。
 *	  「0」一文字の場合に限り、「前に小数点がついて“いない”ものと解釈される」ようである。(だとしても釈然としないが…)
 * [note]
 *	- strverscmp()は、GNUによる拡張である。
 *	- strverscmp()は、汎用の文字列比較関数であり、scandir()専用ではない。
 *	  しかし、scandir()以外で使用することはほぼ無いと思うので、scandir()と一緒に当モジュールで実装することにした。
 *	  もしstrverscmp()を単独で使用する場合は、当モジュールの他の関数もリンクする無駄が生じるが、滅多に無いだろう。
 */
int strverscmp(const char* s1, const char* s2) {	//⇒宣言はstring.h
	int c1, c2, f1, f2, n1, n2;
	const char *p1, *p2, *q1, *q2;
	p1 = p2 = q1 = q2 = NULL;
	for(;;) {
		c1 = *s1++;
		c2 = *s2++;
		q1 = isdigit(c1) ? (p1 ? p1 : (p1 = s1 - 1)) : NULL;	//┬数字列開始の切り替え。
		q2 = isdigit(c2) ? (p2 ? p2 : (p2 = s2 - 1)) : NULL;	//┘
		if(c1 -= c2) {						//相違箇所を発見したら…
			if(p1 && p2) {					//二つの文字列から得られた数字列の両方が空でなければ…
				f1 = strspn(p1, "0");			//┬先頭の0の数を求める。
				f2 = strspn(p2, "0");			//┘
				n1 = strspn(p1, "0123456789");		//┬数字列の桁数を求める。
				n2 = strspn(p2, "0123456789");		//┘
				if(n1 == 1) { f1 = 0; }			//┬※直観に反する仕様に対応するため
				if(n2 == 1) { f2 = 0; }			//┘
				if(f1 || f2) {				//一方,又は,両方の先頭に0が有る場合…
					if(f2 -= f1) { return f2; }	//先頭の0が多い方が小さいと見なす。両方の先頭の0の数が同じならば、相違箇所の文字比較とする。
				} else {				//両方の先頭に0が無い場合…
					if(n1 -= n2) { return n1; }	//数字列の桁数が多い方が大きいと見なす。数字列の桁数が同じならば、相違箇所の文字比較とする。
				}
			}
			break;						//相違箇所の文字比較とする。
		}
		if(!c2) { break; }					//両方の文字列が終端ならば、相違箇所の文字比較とする。(=0)
		p1 = q1;						//┬数字列終了の切り替え。
		p2 = q2;						//┘
	}
	return c1;							//相違箇所の文字比較とする。
}
//□テストスイート
//	static int compar_strcmp(    const char** a, const char** b) { return strcmp(    *a, *b); }
//	static int compar_strverscmp(const char** a, const char** b) { return strverscmp(*a, *b); }
//	void TestSuite_strverscmp() {
//		static const char* const org[9]={"000","00","01","010","09","0","1","9","10"};
//		       const char*       tmp[9];
//		memcpy((void*)tmp, org, sizeof org);
//		qsort((void*)tmp, 9, sizeof(char*), compar_strcmp);
//		assert(memcmp(tmp, org, sizeof org) != 0);
//		qsort((void*)tmp, 9, sizeof(char*), compar_strverscmp);
//		assert(memcmp(tmp, org, sizeof org) == 0);
//		//
//		assert(strverscmp ("", "") == 0);
//		assert(strverscmp ("a", "a") == 0);
//		assert(strverscmp ("a", "b") < 0);
//		assert(strverscmp ("b", "a") > 0);
//		assert(strverscmp ("000", "00") < 0);
//		assert(strverscmp ("00", "000") > 0);
//		assert(strverscmp ("a0", "a") > 0);
//		assert(strverscmp ("00", "01") < 0);
//		assert(strverscmp ("01", "010") < 0);
//		assert(strverscmp ("010", "09") < 0);
//		assert(strverscmp ("09", "0") < 0);
//		assert(strverscmp ("9", "10") < 0);
//		assert(strverscmp ("0a", "0") > 0);
//	}

