//--------------------------------------------------------------------------------------
//
// X-BASIC'
//	外部関数：ファイル系
//
//--------------------------------------------------------------------------------------
// 外部関数の構造を持っている

#include <unistd.h>		// unlink	これを一番最初にしないとなぜかエラーが出る
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "fdef.h"
#include "XB.h"
#import "FileControl2.h"
#import "libs.h"
#import "ZipArchive.h"

#include "support.h"

//---------------------------------------------------------------------------------------------

#define	FILE_MAX	15		// 同時にオープンできるファイル数
static FILE *fps[FILE_MAX];	// オープンしているファイルのポインター

//---------------------------------------------------------------------------------------------
// 関数実体
//---------------------------------------------------------------------------------------------

// この外部関数のバージョンを返す
static RET_FUNC _F_version(SWord numFAC,FAC fac[])
{
	printFunc();
	fac[0].dl.cp[LL]=VERSION_XBFILEFUNC;
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------

// BASICに渡されるのはno+1である(0=NULL相当にするため)
// 故に0,1～FILE_MAXはあり得る番号
static FILE *getFp(int no)
{
	if (no==0) {
		setExternalFuncErrmes("このファイルは開かれていません","This file is not open.");
		return (NULL);
	}
	if (no<0||no>FILE_MAX) {
		setExternalFuncErrmes("ファイル番号の値が異常です","File no is illegal.");
		return (NULL);
	}
	FILE *fp=fps[no-1];
	if (fp==NULL) {
		setExternalFuncErrmes("このファイルは開かれていません","This file was not open."); // か、すでに閉じられたか
		return (NULL);
	}
	DBG_printf1("fp=%p\n",fp);
	return (fp);
}

// noはgetFp()で正しいことを保証しておくこと
static void closeFp(int no)
{
	fps[no-1]=NULL;
}

static int setFp(void)
{
	// 未使用の場所を探す
	int i;
	for (i=0;i<FILE_MAX;i++) {
		if (fps[i]==NULL) {
			return (i+1); // 空いてた場所の番号＋１を返す
		}
	}
	// どこも空いてない
	setExternalFuncErrmes("これ以上ファイルは開けません","Can't open more file(s).");
	return (0);
}

//---------------------------------------------------------------------------------------------
// ファイルアクセス関数
// 出来るだけ内部ではエラーを発生させず、呼び出し側にて判定させるようにしている

// int fp=(fname;str,mode;str)
// iOS拡張を含む
static RET_FUNC _F_fopen(SWord numFAC,FAC fac[])
{
	printFunc();
	// 先に、まだファイルが開くことができるかどうかを調べる
	int no=setFp();
	if (no==0) return (RET_FUNC_ERR); // これ以上開けない
	
	char *fname=bstrWithUTF8(fac[0].dl.bp[LL]); // UTF8化
	if (*fname==0) { // v3.5
		if (operationStatus.ferror==ERR_ON2) {
			// errnoが設定されている
			setExternalFuncErrmes("ファイル名が異常です","Filename is illegal.");
			return (RET_FUNC_ERR);
		}
		no=errorCodeFopenInRun; // 0/-1 v3.10
		goto FILE_RET;
	}
	
	char *mode =fac[1].dl.cp[LL]; // 小文字のみ有効だけど、大文字にも対応してる;ここには全角文字は来ないので変換対象外
	DBG_printf2("変換前fopen(\x22%s\x22,\x22%s\x22)\n",fname,mode);
	// オープンモードとして有効なのは先頭の1文字または2文字目までで、以降は無視される
	char *fmode;
	// モード指定文字はどうも大文字も有効になっているようである
	//											対象ディレクトリ
	// "r"	"r"		read only					iTunes→本体
	// "rw"	"r+"	read & wrie					iTunes
	// "ra"	"a+"	read & append		iOS拡張	iTunes
	// "w"	"a"		write & append				iTunes
	// "a"	"a"		write & append		iOS拡張	iTunes
	// "c"	"w"		creat						iTunes
	BOOL freadOnly=NO;
	switch (*mode) {
		case 'r':
		case 'R':
			switch (*(mode+1)) {
				case 'w': // "rw"	読み書き両用
				case 'W':
					fmode="r+"; // 読み書き、先頭から
					break;
				case 'a': // "ra"	iOS拡張
				case 'A':
					fmode="a+"; // 読み書き、最後から
					break;
				default:
					// "r"のみ	読み込み専用
					fmode="r"; // 読み出し、先頭から
					freadOnly=YES;
				break;
			}
			break;
		
		case 'a':	// iOS拡張
		case 'A':
		case 'w':	// 書き込み専用
		case 'W':
			fmode="a"; // 追記、最後から
			break;
			
		case 'c':	// create & read 新規作成
		case 'C':
			fmode="w+"; // 読み書き新規、最初から
			break;
	
		default:
			// モード異常
			setExternalFuncErrmes("ファイルオープンモードの指定が異常です","Illegal file open mode.");
			return (RET_FUNC_ERR);
	}
	char *cfname=makeFullPathForSaveDataWithCString(fname);
	DBG_printf2("変換後fopen(\x22%s\x22,\x22%s\x22)\n",cfname,fmode);
	FILE *fp=fopen(cfname,fmode);
	if (freadOnly) {
		// "r"の時のみ、iTunes側になかったときは内部で調べる
		if (fp==NULL) {
			cfname=makeFullPathForResourceWithCString(fname);
			DBG_printf2("変換後fopen(\x22%s\x22,\x22%s\x22)\n",cfname,fmode);
			fp=fopen(cfname,fmode);
		}
	} else {
	}
	nslog(@"fname=%s",cfname);
	DBG_printf1("fp=%p\n",fp);
	if (fp==NULL) { // エラー
		if (operationStatus.ferror==ERR_ON2) { // v2.6
			// errnoが設定されている
			setExternalFuncErrmes("ファイルがオープン出来ません","File can't open.");
			return (RET_FUNC_ERR);
		}
		no=errorCodeFopenInRun; // 0/-1 v3.10
	} else {
		fps[no-1]=fp; // 登録
	}
FILE_RET:;
	// fac[]に入れて返す
	fac[0].dl.l[LL]=no; // ファイル番号
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// v3.10
// int fopenError(fp;int)
static RET_FUNC _F_fopenError(SWord numFAC,FAC fac[])
{
	printFunc();
	int no=(int)fac[0].dl.l[LL];
	int ret=BASIC_NO;
	if (no<=errorCodeFopenInRun) ret=BASIC_YES; // 将来のため、<=で判定しておく
	fac[0].dl.l[LL]=ret; // 判定結果
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// fclose(fp;int)
static RET_FUNC _F_fclose(SWord numFAC,FAC fac[])
{
	printFunc();
	int no=(int)fac[0].dl.l[LL];
	FILE *fp=getFp(no);
	if (fp==NULL) return(RET_FUNC_ERR);
	int ret=0; // BASIC_NO; v2.5:定数名は変わるが値は同じ
	if (fclose(fp)==EOF) { // エラー v2.5:定数名は変わるが値は同じ
		ret=-1; // BASIC_YES; 
		// errnoが設定されている
	}
	closeFp(no); // エラーが出ていても閉じた相当にする
	
	// fac[]に入れて返す
	fac[0].dl.l[LL]=ret;
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// 開いている全ファイルを閉じる
static int fcloseallSub(void)
{
	DBG_Func();
	int ret=0; // BASIC_NO; // エラーなしの印 v2.5:定数名は変わるが値は同じ
	int i;
	for (i=0;i<FILE_MAX;i++) {
		if (fps[i]!=NULL) {
			if (fclose(fps[i])==EOF) {
				ret=-1; // BASIC_YES; // エラーの印 v2.5:定数名は変わるが値は同じ
				// errnoが設定されている
			}
			fps[i]=NULL; // 使ってない印
		}
	}
	return (ret); // どこかにエラーがあったかどうか
}

// int fcloseall(void)
static RET_FUNC _F_fcloseall(SWord numFAC,FAC fac[])
{
	printFunc();
	fac[0].dl.l[LL]=fcloseallSub();
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// SLong seek=ftell(fp;int)
// iOS拡張
static RET_FUNC _F_ftell(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[0].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);

	fac[0].dl.l[LL]=(ULong)ftell(fp); // -1ならエラーが発生している;long ftell()
	
	// fac[]に入れて返す
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// void frewind(fp;int)
// iOS拡張
static RET_FUNC _F_frewind(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[0].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);

	rewind(fp); // エラーは発生しないが、errnoは消される
	
	// 念のためfac[]に入れて返す
	fac[0].dl.l[LL]=0; // 念のため
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int fseek(fp;int,offstt;int,mode;int)
static RET_FUNC _F_fseek(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[0].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	SLong offset=fac[1].dl.l[LL];
	int    mode=(int)fac[2].dl.l[LL];
	switch (mode) {
		default:
			setExternalFuncErrmes("基点指定が間違っています","Illegal seek base point.");
			return (RET_FUNC_ERR);
		case 0: // 先頭
			mode=SEEK_SET;
			break;
		case 1: // 現在位置
			mode=SEEK_CUR;
			break;
		case 2: // 終端
			mode=SEEK_END;
			break;
	}
	fac[0].dl.l[LL]=fseek(fp,offset,mode); // -1ならエラーが発生している
	
	// fac[]に入れて返す
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int fgetc(fp;int)
static RET_FUNC _F_fgetc(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[0].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	
	// fac[]に入れて返す
	fac[0].dl.l[LL]=fgetc(fp); // EOFの時は-1が帰る
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int fputc(ch;char,fp;int)
static RET_FUNC _F_fputc(SWord numFAC,FAC fac[])
{
	printFunc();
	int c=(int)fac[0].dl.l[LL];
	FILE *fp=getFp((int)fac[1].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	
	// fac[]に入れて返す
	fac[0].dl.l[LL]=fputc(c,fp); // 書き込んだ文字そのもの(UByte)、またはEOFの時は-1が帰る
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int feof(fp;int)
static RET_FUNC _F_feof(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[0].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);

	// fac[]に入れて返す
	fac[0].dl.l[LL]=(feof(fp)? BASIC_YES:BASIC_NO); // Cのfeof()はBASICとリターン値が異なる
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int ferror(fp;int)
static RET_FUNC _F_ferror(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[0].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	
	// fac[]に入れて返す
	fac[0].dl.l[LL]=(ferror(fp)? BASIC_YES:BASIC_NO); // Cのferror()はBASICとリターン値が異なる
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int fread(int1次元配列,要素数,fp;int)
// 配列はchar/int/float(拡張配列は来ない)
// リターン値は読み込んだ要素数
static RET_FUNC _F_fread(SWord numFAC,FAC fac[])
{
	printFunc();
	
	SWord size1;
	uwYouso youso;
	UByte *bp=(UByte *)getDim1FromFAC(fac,0,&size1,&youso);
	if (bp==NULL) { // 1次元でない
		return (RET_FUNC_ERR);
	}
	int cnt=(int)fac[1].dl.l[LL];
	if (youso<cnt) {
		#if 0
		setExternalFuncErrmes("配列の要素数が足りません","Array's elements are not enough.");
		return (RET_FUNC_ERR);
		#else
		// あるだけ書き込む
		youso=cnt;
		#endif
	}
	FILE *fp=getFp((int)fac[2].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	// fac[]に入れて返す
	DBG_printf3("buff=%p,sz1=%d,cnt=%d\n",bp,size1,cnt);
	fac[0].dl.l[LL]=(ULong)fread(bp,size1,cnt,fp);	// size_t fread();64bitではsize_t=unsigned long
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int fwrite(int1次元配列,要素数,fp;int)
// 配列はchar/int/float(拡張配列は来ない)
// リターン値は書き込んだ要素数
static RET_FUNC _F_fwrite(SWord numFAC,FAC fac[])
{
	printFunc();
	SWord size1;
	uwYouso youso;
	UByte *bp=(UByte *)getDim1FromFAC(fac,0,&size1,&youso);
	if (bp==NULL) { // 1次元でない
		return (RET_FUNC_ERR);
	}
	int cnt=(int)fac[1].dl.l[LL];
	if (youso<cnt) {
		#if 0
		setExternalFuncErrmes("配列の要素数が足りません","Array's elements are not enough.");
		return (RET_FUNC_ERR);
		#else
		// あるだけ書き込む
		youso=cnt;
		#endif
	}
	FILE *fp=getFp((int)fac[2].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	// fac[]に入れて返す
	DBG_printf3("buff=%p,sz1=%d,cnt=%d\n",bp,size1,cnt);
	fac[0].dl.l[LL]=(ULong)fwrite(bp,size1,cnt,fp); // size_t fwrite();64bitではsize_t=unsigned long
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int fwrites(str,fp;int)
static RET_FUNC _F_fwrites(SWord numFAC,FAC fac[])
{
	printFunc();
	char *str=fac[0].dl.cp[LL];
	FILE *fp =getFp((int)fac[1].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
	// fac[]に入れて返す
	// fputs()を使うと書き込みバイト数が返せないのでfwriteを使う
	fac[0].dl.l[LL]=(ULong)fwrite(str,SZ_CHAR,strlen(str),fp);
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int freads(str,fp;int)
static RET_FUNC _F_freads(SWord numFAC,FAC fac[])
{
	printFunc();
	FILE *fp=getFp((int)fac[1].dl.l[LL]);
	if (fp==NULL) return (RET_FUNC_ERR);
#if 0	
	char *str=fac[0].dl.cp[LL];
	char *p=fgets(str,operationStatus.sz_str,fp);
	fac[0].dl.l[LL]=(p==NULL? -1:strlen(p));
#else
	UByte *str=fac[0].dl.bp[LL];
	fac[0].dl.l[LL]=fgets2(str,operationStatus.sz_str,fp);
#endif
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//----------------------------------------------------------------------------------
// ファイルそのものの操作関数

// int fdelete(fname;str)
static RET_FUNC _F_fdelete(SWord numFAC,FAC fac[])
{
	printFunc();
	UByte *fname=fac[0].dl.bp[LL];
	nslog(@"fname=%@",@((char *)fname));
	fac[0].dl.l[LL]=unlink(makeFullPathForSaveDataWithCString(bstrWithUTF8(fname))); // UTF8化 // NULLファイル名はここが処理してくれる
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int frename(oldname;str,newname;str)
static RET_FUNC _F_frename(SWord numFAC,FAC fac[])
{
	printFunc();
	const char *oldname=(const char *)makeFullPathForSaveDataWithCString(bstrWithUTF8no(fac[0].dl.bp[LL],0)); // UTF8化
	const char *newname=(const char *)makeFullPathForSaveDataWithCString(bstrWithUTF8no(fac[1].dl.bp[LL],1)); // UTF8化
	fac[0].dl.l[LL]=rename(oldname,newname); // NULLファイル名はここが処理してくれる
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int filelength(fname;str)
// iOS拡張
static RET_FUNC _F_filelength(SWord numFAC,FAC fac[])
{
	printFunc();
	char *fname=bstrWithUTF8(fac[0].dl.bp[LL]); // UTF8化

	struct stat statInfo;	// off_t st_size;64bitで変化
	// 検索順はiTunes→内蔵
	int ret=stat((const char *)makeFullPathForSaveDataWithCString(fname),&statInfo); // -1=エラー // NULLファイル名はここが処理してくれる
	if  (ret==-1) {
		ret=stat((const char *)makeFullPathForResourceWithCString(fname),&statInfo); // -1=エラー // NULLファイル名はここが処理してくれる
	}
	fac[0].dl.l[LL]=(ULong)(ret<0? 0:statInfo.st_size);
	
	// fac[]に入れて返す
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int filecopy(srcname;str,desname;str)
// iOS拡張
static RET_FUNC _F_filecopy(SWord numFAC,FAC fac[])
{
	printFunc();
	@autoreleasepool {
	// ファイル名のNSString化
	// ARCにすると変数宣言をまたいでgotoできなくなるので、スコープ内で使う変数の宣言を先頭で行う必要がある。
	NSString *srcname,*desname,*srcPath,*desPath;
	//
	BOOL ret;
	UByte *bp;
	bp=fac[0].dl.bp[LL];
	if (*bp==0) {
		nslog(@"これからはコピー出来ない");
		ret=NO;
		goto filecopy_end;
	}
	// NSString *
	srcname=@(bstrWithUTF8no(bp,0)); // UTF8化
	//
	bp=fac[1].dl.bp[LL];
	if (*bp==0) {
		nslog(@"これにはコピー出来ない");
		ret=NO;
		goto filecopy_end;
	}
	// NSString *
	desname=@(bstrWithUTF8no(bp,1)); // UTF8化
	
	// srcが内蔵かユーザー領域かを判定する
	// NSString *
	srcPath=makeFullPathForResource(srcname,@""); // 内蔵をチェック
	if (srcPath==nil) {
		srcPath=makeFullPathForSaveData(srcname); // ユーザー領域
	}
	// desの最後が'/'の場合、同名コピーにおけるファイル名の省略とみなす
	//NSString *
	desPath=desname;
	int len=(int)[desname length];
	if ([desname characterAtIndex:len-1]=='/') {
		NSString *fname=[srcname lastPathComponent]; // srcのファイル名＋拡張子
		desPath=[NSString stringWithFormat:@"%@/%@",desname,fname];
	}
	//
	ret=fileCopy(srcPath, makeFullPathForSaveData(desPath));
filecopy_end:
	// ret : YES=正常終了 , NO=エラー
	fac[0].dl.l[LL]=(ret==YES? 0:-1); // 他の関数と合わせるため
	fac[0].facType=FACTYPE_RETURN;
	}
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------
// ディレクトリ操作関数

static UByte *getdir(FAC fac[],int i)
{
	char *cp=fac[i].dl.cp[LL];
	if (*cp==0) {
		nslog(@"NULLは対象にできない");
		return (NULL);
	}
	char *dir=(char *)tmp;
	strcpy(dir,cp);
	// facは文字列本体のアドレスなので、その中を変更すれば元の文字列も変更されてしまう
	// そのため、ここではtmpにコピーする
	// dirの最後が'/'の場合、自動的に外す
	size_t len=strlen(dir);	// size_t
	if (dir[len-1]=='/') {
		dir[len-1]='\0';
	}
	return ((UByte *)dir);
}

// int mkdir(dir;str)
// iOS拡張
static RET_FUNC _F_mkdir(SWord numFAC,FAC fac[])
{
	printFunc();
	UByte *dir=getdir(fac,0);
	if (dir==NULL) {
		fac[0].dl.l[LL]=-1; // エラー
	} else {
		nslog(@"dir=%s",dir);
		char *path=makeFullPathForSaveDataWithCString(bstrWithUTF8(dir)); // UTF8化付き
		nslog(@"フルパスdir=%s",path);
		fac[0].dl.l[LL]=mkdir(path,0777);
	}
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int rmdir(dir;str)
// iOS拡張
static RET_FUNC _F_rmdir(SWord numFAC,FAC fac[])
{
	printFunc();
	UByte *dir=getdir(fac,0);
	if (dir==NULL) {
		fac[0].dl.l[LL]=-1; // エラー
	} else {
		nslog(@"dir=%s",dir);
		char *path=makeFullPathForSaveDataWithCString(bstrWithUTF8(dir)); // UTF8化付き
		nslog(@"フルパスdir=%s",path);
		fac[0].dl.l[LL]=rmdir(path);
	}
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

// int files(pathes;文字列配列[,basedir;str][,ext;str][,fhave;int][,mode;int][,freg;int][,farea;int])
// iOS拡張
static RET_FUNC _F_files(SWord numFAC,FAC fac[])
{
	printFunc();
	@autoreleasepool {
	// 引数取得
		int i=0;
		//
		SWord size1;
		uwYouso youso;
		UByte *bp=(UByte *)getDim1FromFAC(fac,i++,&size1,&youso);
		if (bp==NULL) { // 1次元でない
			return (RET_FUNC_ERR);
		}
		// ユーザーエリアのディレクトリ指定
		// システム領域にはディレクトリはないので常にルートからになる
		NSString *basedir=@"";
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			basedir=@(bstrWithUTF8no(fac[i].dl.bp[LL],0));// UTF8化
		}
		i++;
		NSString *ext=@""; // 拡張子指定なし
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			ext=@(bstrWithUTF8no(fac[i].dl.bp[LL],1)); // UTF8化
		}
		i++;
		int fhave=YES;
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			fhave=basic2cYESNO(fac[i].dl.l[LL]);
		}
		i++;
		int mode=FILES_ALL;
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			mode=(int)fac[i].dl.l[LL];
		}
		i++;
		int freg=NO; // 正規表現を使うかどうか
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			freg=basic2cYESNO(fac[i].dl.l[LL]);
		}
		i++;
		int farea=FILES_FAREA_USER; // 検索エリア指定
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			farea=(int)fac[i].dl.l[LL];
		}
		i++;

		nslog(@"user basedir=%@",basedir);
		nslog(@"ext=%@",ext);
		nslog(@"fhave=%d,mode=$%04x,freg=%d",fhave,mode,freg);

		// 処理順
		if (farea&FILES_FAREA_USER) {
			// (1)files in ユーザー領域
			nslog(@"files in ユーザー領域");
			NSString *rootDir=makeFullPathForSaveData(basedir);
			nslog(@"user rootDir=%@",rootDir);
			NSArray *pathlist=files(rootDir,ext,fhave,mode,freg);
			nslog(@"pathlist=%@",pathlist);
			
			// (2)NSString → C文字列変換しながら配列に格納
			// ただし、前のrootDir分は外す
			NSInteger len=[rootDir length]+1; // 後ろの/も外す
			i=0;
			for (NSString *path in pathlist) {
				NSString *sdir=[path substringFromIndex:len]; // subdir/fname または subdir/
				nslog(@"sdir=%@",sdir);
				char *path1=nsStringTocString(sdir);
				if (strlen(path1)>=operationStatus.sz_str) {
					nslog(@"長すぎて入れられない → 無視する");
				} else {
					strcpy((char *)bp,path1);
					bp+=operationStatus.sz_str; // 次の文字列領域へ
					if (++i>=youso) break; // 要素数いっぱい
				}
			}
		}

		if (farea&FILES_FAREA_SYSTEM) {
			// (3)files in システム領域
			nslog(@"files in システム領域");
			NSString *rootDir=getSystemPath();
			nslog(@"system rootDir=%@",rootDir);
			NSArray *pathlist=files(rootDir,ext,fhave,mode,freg);
			
			// (3)NSString → C文字列変換しながら配列に格納
			// ただし、前のmakeFullPathForResource()は外す
			NSInteger len=[rootDir length]+1; // 後ろの/も外す
			i=0;
			for (NSString *path in pathlist) {
				NSString *sdir=[path substringFromIndex:len]; // subdir/fname または subdir/
				nslog(@"sdir=%@",sdir);
				char *path1=nsStringTocString(sdir);
				if (strlen(path1)>=operationStatus.sz_str) {
					nslog(@"長すぎて入れられない → 無視する");
				} else {
					strcpy((char *)bp,path1);
					bp+=operationStatus.sz_str; // 次の文字列領域へ
					if (++i>=youso) break; // 要素数いっぱい
				}
			}
		}
		//
		fac[0].dl.l[LL]=i; // 個数を返す
	//
	}
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------

// int zipExpand(zipFile;str)
// iOS拡張
static RET_FUNC _F_zipExpand(SWord numFAC,FAC fac[])
{
	printFunc();
	@autoreleasepool {
		UByte *bp=fac[0].dl.bp[LL];
		if (*bp==0) { // ""のとき
			nslog(@"NULLファイル名");
			fac[0].dl.l[LL]=-3; // エラー
		}
		NSString *zipfname=@(bstrWithUTF8no(bp,0)); // UTF8化
		do { // ループではなく、エラー時のジャンプのため
			nslog(@"ZIPファイル→解凍して格納");
			// MacOS標準上でフォルダを圧縮すると
			// __MACOSX\フォルダ名\._ファイル群.ext		(サイズから見て)実体ではない
			// フォルダ名\ファイル群.ext				ファイル実体
			// という構成で圧縮されてしまう。
			// （muCommanderやWindows上の圧縮ではこのようにはならない。）
			// __MACOSX内は.から始まるので隠しファイルとなり表示されないが、残ってしまい無駄になる
			// また、展開時にフォルダ名\.DS_Storeというファイルも作られてしまう(__MACOSX内にリンクがあるからだと思う)
			// しかし、フォルダ名を特定できない(するのが面倒な)ので、このファイルを消すことはできない
			NSString *zippath=makeFullPathForSaveData(zipfname);
			nslog(@"zippath=%@",zippath);
			ZipArchive *zip = [[ZipArchive alloc] init];
			if (![zip UnzipOpenFile:zippath]) {
				// 見つからない
				nslog(@"見つからない:%@",zippath);
				fac[0].dl.l[LL]=-1; // エラー
				break;
			}
			NSString *extendPath=getDocumentsPath();
			nslog(@"解凍フォルダ=%@",extendPath);
			BOOL ret = [zip UnzipFileTo:extendPath overWrite:YES]; // 解凍
			if (NO == ret) {
				// エラー処理
				nslog(@"展開エラー");
				fac[0].dl.l[LL]=-2; // エラー
				break;
			}
			[zip UnzipCloseFile];

			// __MACOSX\を削除する
			// 存在しないこともあるので、エラーは無視する
			[[NSFileManager defaultManager]removeItemAtPath:makeFullPathForSaveData(@"__MACOSX") error:NULL];
			// zipの時はその中のどれを起動すれば良いのかわからない(.basがないこともあり得る)ので自動表示しない
			//
			fac[0].dl.l[LL]=0; // 正常終了
		} while (0);
	} // @autorelease
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------

// int zipArchive(zipFile;str,fname;str[,password;str])
// iOS拡張
static RET_FUNC _F_zipArchive(SWord numFAC,FAC fac[])
{
	printFunc();
	@autoreleasepool {
		int i=0;
		// zipファイル名
		UByte *bp=fac[i].dl.bp[LL];
		if (*bp==0) { // ""のとき
			nslog(@"NULLファイル名(zip)");
			fac[0].dl.l[LL]=-3; // エラー
		}
		NSString *zipfname=@(bstrWithUTF8no(bp,0)); // UTF8化
		i++;
		
		// 圧縮するファイル名
		bp=fac[i].dl.bp[LL];
		if (*bp==0) { // ""のとき
			nslog(@"NULLファイル名");
			fac[0].dl.l[LL]=-4; // エラー
		}
		NSString *fname=@(bstrWithUTF8no(bp,1)); // UTF8化
		i++;
		
		// パスワード
		char *password=NULL;
		if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
			char *cp=fac[i].dl.cp[LL];
			if (*cp!=0) { // ""のとき
				nslog(@"パスワード設定あり:%@",@(cp));
				password=cp;
			}
		}
#if 1
		// Zipライブラリのバグによりパスワードをかけると解凍できなくなるので現在は封印
		password=NULL;
#endif
		// eMailSend;Mail.mも同様の処理なので、必要なら参照のこと(全く別個に作ったけど)
		BOOL fcreate=YES;
		int ret; // BOOLも含む

		NSString *zippath=makeFullPathForSaveData(zipfname);
		nslog(@"zippath=%@",zippath);
		ZipArchive *zip = [[ZipArchive alloc] init];
		
		do { // ループではなく、エラー時のジャンプのため
			if (isFileExist(zippath)) { // 既存ファイルの時
				nslog(@"既存ファイルがある=追加");
				if (password==NULL) {
					ret=[zip appendZipFile2:zippath];
				} else {
					ret=[zip appendZipFile2:zippath Password:@(password)];
				}
				fcreate=NO;
			} else {
				nslog(@"既存ファイルはない=新規作成");
				if (password==NULL) {
					ret=[zip CreateZipFile2:zippath];
				} else {
					ret=[zip CreateZipFile2:zippath Password:@(password)];
				}
			}
			if (!ret) {
				// ファイルが作れない
				nslog(@"作成できない:%@",zippath);
				ret=-1; // エラー
				break;
			}
			
#ifdef DEBUG
			nslog(@"デバッグ中はリソースファイルも圧縮可能");
			// デバッグ中はリソースファイルも可能
			fname=getResourceOrSavedataPath(fname);
#else
			// 本番に圧縮できるのはユーザーファイルのみ(リソース内は無理)
			fname=makeFullPathForSaveData(fname);
#endif
			nslog(@"圧縮対象ファイル=%@",fname);
			if (!isFileExist(fname)) {
				nslog(@"ファイルが存在しない");
				ret=-5; // エラー
				break;
			}
			
			// フォルダ構成を入れないためファイル名のみで追加する
			if ([zip addFileToZipWithFilenameOnly:zipfname newname:fname]==NO) {
				nslog(@"圧縮エラー");
				ret=-2; // エラー
				break;
			}
			[zip CloseZipFile2];
			//
			ret=0; // 正常終了
		} while (0);
		if (ret<0) {
			// 新規作成でエラーが発生した時はアーカイブファイルを消す(ヘッダーのみのファイルができているので)
			if (fcreate) fileDelete(zippath);
		}
		fac[0].dl.l[LL]=ret;
	} // @autorelease
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------

// int loadFromURL(fname;str,url;str[,flocal;int])
// iOS拡張
static RET_FUNC _F_loadFromURL(SWord numFAC,FAC fac[])
{
	printFunc();
	@autoreleasepool {
		do { // ループではなく、エラー時のジャンプのため
			int i=0;
			UByte *bp;
			bp=fac[i].dl.bp[LL];
			if (*bp==0) {
				nslog(@"ファイル名エラー");
				fac[0].dl.l[LL]=-3; // エラー
				break;
			}
			i++;
			NSString *fname=@(bstrWithUTF8no(bp,0)); // UTF8化

			bp=fac[i].dl.bp[LL];
			if (*bp==0) {
				nslog(@"URL名エラー");
				fac[0].dl.l[LL]=-4; // エラー
				break;
			}
			i++;
			NSString *urls=@(bstrWithUTF8no(bp,1)); // UTF8化
			NSURL *url=[NSURL URLWithString:[urls stringByAppendingPathComponent:fname]];
			nslog(@"読み込みurl=%@",url);

			int flocal=YES;
			if (fac[i].facType!=VOID_PARAM) { // 引数が省略されていない時
				flocal=basic2cYESNO(fac[i].dl.l[LL]);
			}
			i++;
			nslog(@"ファイルが既にローカルにある時どうするか:flocal=%d",flocal);
			//
			NSString *path=makeFullPathForSaveData(fname);
			nslog(@"書き込み先path=%@",path);
			//
			if (flocal) {
				// ローカル存在チェック
				NSFileManager *fileManager = [NSFileManager defaultManager];
				// ファイルの存在を確認
				if ([fileManager fileExistsAtPath:path]) {
					// すでにあるとき
					nslog(@"すでに存在するので何もしない");
					fac[0].dl.l[LL]=0; // OK
					break;
				}
			}
			NSError *error;
			NSData *data=[NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
			if (error!=nil) {
				nslog(@"URLから読み込めない:error=%@",error);
				fac[0].dl.l[LL]=-2; // エラー
				break;
			}
			nslog(@"size=%tu",[data length]);
			if (![data writeToFile:path atomically:YES]) {
				nslog(@"ファイルに書き込めない");
				fac[0].dl.l[LL]=-2; // エラー
				break;
			}
			// 正常終了
			fac[0].dl.l[LL]=0;
		} while (0);
	}
	fac[0].facType=FACTYPE_RETURN;
	return (RET_FUNC_OK); // OK
}

//---------------------------------------------------------------------------------------------
// インフォメーションテーブル (5.2.1章)
//---------------------------------------------------------------------------------------------

// 関数名テーブル(トークンテーブル)
static const char *fileTokens[]={
	"fopen",
	"fopenError",	// v3.10
	"fclose",
	"fcloseall",
	"feof",
	"ferror",	// v2.3
	"fgetc",
	"fdelete",
	"frename",
	"fwrites",
	"freads",
	"fputc",
	"fread",
	"fwrite",
	"fseek",
	// iOS版追加
	"ftell",
	"frewind",
	"filelength",
	"filecopy",
	"mkdir",
	"rmdir",
	"files",
	"zipExpand",	// v2.90
	"zipArchive",	// v3.50
	"loadFromURL",	// v2.90

	// バージョン
	"versionXBfilefunc$",
	// end of table
	NULL,
};

// 実行アドレステーブル
static FUNC_EXEC	fileFuncExec[]={
	// Cの標準関数名と重複させないよう_F_を付ける
	{_F_fopen		},
	{_F_fopenError	},
	{_F_fclose		},
	{_F_fcloseall	},
	{_F_feof		},
	{_F_ferror		},	// V2.3
	{_F_fgetc		},
	{_F_fdelete		},
	{_F_frename		},
	{_F_fwrites		},
	{_F_freads		},
	{_F_fputc		},
	{_F_fread		},
	{_F_fwrite		},
	{_F_fseek		},
	// iOS版追加
	{_F_ftell		},
	{_F_frewind		},
	{_F_filelength	},
	{_F_filecopy	},
	{_F_mkdir		},
	{_F_rmdir		},
	{_F_files		},
	{_F_zipExpand	},
	{_F_zipArchive	},
	{_F_loadFromURL	},
	
	// バージョン
	{_F_version},
	// end of table
	{NULL},
};

//------------------------------
// パラメーターテーブル
// (1)種類別定義
// 1関数の引数は最大MAX_PARAM個

//------------------------------

static const uwPARAMTYPE p_fopen[]={
	STR_VAL,
	STR_VAL,
	INT_RET,
};
#define	p_frename	p_fopen
#define p_filecopy	p_fopen

static const uwPARAMTYPE p_fclose[]={
	INT_VAL,
	INT_RET,
};
#define	p_feof			p_fclose
#define	p_ferror		p_fclose	// v2.3
#define	p_fgetc			p_fclose
#define p_fopenError	p_fclose	// v3.10

static const uwPARAMTYPE p_fcloseall[]={
	INT_RET,
};

static const uwPARAMTYPE p_fputc[]={
	CHAR_VAL,
	INT_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_fread[]={
	ARRAY1_FIC,
	INT_VAL,
	INT_VAL,
	INT_RET,
};
#define	p_fwrite	p_fread

static const uwPARAMTYPE p_freads[]={
	STR_VP,
	INT_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_fwrites[]={
	STR_VAL,
	INT_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_fseek[]={
	INT_VAL,
	INT_VAL,
	INT_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_fdelete[]={
	STR_VAL,
	INT_RET,
};
#define	p_filelength	p_fdelete
#define p_mkdir			p_fdelete
#define p_rmdir			p_fdelete

static const uwPARAMTYPE p_ftell[]={
	INT_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_frewind[]={
	INT_VAL,
	VOID_RET,
};

static const uwPARAMTYPE p_files[]={
	ARRAY1_STR,
	STR_OMT,
	STR_OMT,
	INT_OMT,
	INT_OMT,
	INT_OMT,
	INT_OMT,	// v2.9
	INT_RET,
};

static const uwPARAMTYPE p_zipExpand[]={
	STR_VAL,
	INT_RET,
};

static const uwPARAMTYPE p_zipArchive[]={ // v3.50
	STR_VAL,	// zipfname
	STR_VAL,	// fname
	STR_OMT,	// password
	INT_RET,
};

static const uwPARAMTYPE p_loadFromURL[]={
	STR_VAL,
	STR_VAL,
	INT_OMT,
	INT_RET,
};
	
static const uwPARAMTYPE p_version[]={
	STR_RET,
};

//------------------------------
// パラメーターテーブル
// (2)関数別定義

const
uwPARAMTYPE *fileParams[]={
	p_fopen		,
	p_fopenError,	// v3.10
	p_fclose	,
	p_fcloseall	,
	p_feof		,
	p_ferror	,	// v2.3
	p_fgetc		,
	p_fdelete	,
	p_frename	,
	p_fwrites	,
	p_freads	,
	p_fputc		,
	p_fread		,
	p_fwrite	,
	p_fseek		,
	// iOS版追加
	p_ftell		,
	p_frewind	,
	p_filelength,
	p_filecopy	,
	p_mkdir		,
	p_rmdir		,
	p_files		,
	p_zipExpand	,	// v2.9
	p_zipArchive,	// v3.50
	p_loadFromURL,	// v2.9
	
	// バージョン
	p_version	,
	// end of table
	NULL,
};

//-------------

// この中の関数のrun時処理
static void _Frun(void)
{
	printFunc();
	int i;
	for (i=0;i<FILE_MAX;i++) {
		fps[i]=NULL;
	}
}

// この中の関数のend時処理
static void _Fend(void)
{
	printFunc();
	fcloseallSub();
}

// インフォメーションテーブル
const
INFORMATION_TABLE	InfoFileFuncs={
	#ifdef FUNC_DEBUG
	"File functions",	// "ファイル関数群",
	#endif
	returnOnly,	// Finit
	_Frun,		// Frun
	_Fend,		// Fend
	returnOnly,	// Fexit
	returnOnly,	// Fbreak
	NULL,		// FctrlD;未使用
	NULL,		// 予約1
	NULL,		// 予約2
	//
	fileTokens,
	fileParams,
	fileFuncExec
};

//---------------------------------------------------------------------------------------------
