#include <../source/TAL/objbase.h>
#include <../source/PAL/oshead.h>
#include <../source/PAL/fsops.h>

#ifdef	__UNIX__

vboolean	V_CALL
lfsGetDir(char *buffer,u32_t size)
{
	return getcwd(buffer,size)!=NULL;
}

vboolean	V_CALL
lfsSetDir(char *unix_dir_name,vboolean force_create)
{
	char cur_dir[_MAX_PATH];
	char *slash="/";
	vhandle_t dir_parser;
#ifdef		CHECKED_BUILD
	static char *caller="lfsSetDir(";
#endif		/* CHECKED_BUILD */
	getcwd(cur_dir,_MAX_PATH);
	/* Check if directory already exist */
	if(chdir(unix_dir_name)==0)
		return true;
	if(errno!=ENOENT)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s'%s'): chdir() failed: code %d.\n",
			caller,unix_dir_name,errno);
#endif		/* CHECKED_BUILD */
		return false;
	}
	if(!force_create)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s'%s'): path not exist.\n",
			caller,unix_dir_name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* Move along path as far as possible */
	if(unix_dir_name[0]==slash[0])
	{
		/* Absolute path */
		chdir(slash);
		unix_dir_name++;
	}
	dir_parser=tokParseString(unix_dir_name,slash);
	for(;tokStrict(dir_parser);)
	{
		if(tokLast(dir_parser)[0]==slash[0])
			continue;
		if(chdir(tokLast(dir_parser)))
		{
    		if(mkdir(tokLast(dir_parser),0775))
    		{
#ifdef			CHECKED_BUILD
    			conlPrintf(LOG_ERROR,
    				"%s'%s'): mkdir('%s') failed (code %d).\n",
    				caller,unix_dir_name,tokLast(dir_parser),errno);
#endif			/* CHECKED_BUILD */
    			chdir(cur_dir);
                tokDestroy(dir_parser);
    			return false;
    		}
    		if(chdir(tokLast(dir_parser)))
    		{
    			/* Can't change to new directory ? */
    			chdir(cur_dir);
                tokDestroy(dir_parser);
    			return false;
    		}
		}
	}
	tokDestroy(dir_parser);
	return true;
}

#else	/* !__UNIX__ */

/*
 *	Converts Win32 path to unix form.
 *	Assumes both args are non-NULL and dest can contain at least
 *	_MAX_PATH charasters.
 */
vboolean	V_CALL
vfsNative2Unix(char *unix_path,char *win32_path)
{
	if(win32_path[1]==':')
	{
		/* drive present */
		unix_path[0]=unix_path[1]='/';
		unix_path[2]=win32_path[0];
		unix_path+=3;
		win32_path+=2;
	}
	for(;*win32_path;win32_path++,unix_path++)
	{
		if(*win32_path=='\\')
			*unix_path='/';
		else
			*unix_path=*win32_path;
	}
	*unix_path=0;
	return true;
}

/*
 *	Converts unix path to Win32 form.
 *	Assumes both args are non-NULL and dest can contain at least
 *	_MAX_PATH charasters.
 */
vboolean	V_CALL
vfsUnix2Native(char *win32_path,char *unix_path)
{
	/* BUGBUG: we cant handle one-letter servers in UNC names !!! */
	if(unix_path[0]=='/' && unix_path[1]=='/' && 
		(unix_path[3]=='/' || !unix_path[3]))
	{
		/* drive present */
		win32_path[0]=unix_path[2];
		win32_path[1]=':';
		unix_path+=3;
		win32_path+=2;
	}
	for(;*unix_path;win32_path++,unix_path++)
	{
		if(*unix_path=='/')
			*win32_path='\\';
		else
			*win32_path=*unix_path;
	}
	*win32_path=0;
	return true;
}

/*
 *	Returns current directory in POSIX form: c:\winnt -> //c/winnt
 */
vboolean	V_CALL
lfsGetDir(char *buffer,u32_t size)
{
	char os_dir[_MAX_PATH],unix_dir[_MAX_PATH];
	if(GetCurrentDirectory(_MAX_PATH,os_dir))
	{
		vfsNative2Unix(unix_dir,os_dir);
		strCopy(buffer,unix_dir,size);
		return true;
	}
	return false;
}

/*
 *	Changes current directory, optionally creates one or more directories.
 */
vboolean	V_CALL
lfsSetDir(char *unix_dir_name,vboolean force_create)
{
	char *ptr;
	char store;
	char os_dir[_MAX_PATH];
	char cur_dir[_MAX_PATH];
	DWORD errcode;
	vhandle_t dir_parser;
#ifdef		CHECKED_BUILD
	static char *caller="lfsSetDir(";
#endif		/* CHECKED_BUILD */
	GetCurrentDirectory(_MAX_PATH,cur_dir);
	vfsUnix2Native(os_dir,unix_dir_name);
	SetLastError(0);
	/* Check if directory already exist */
	if(SetCurrentDirectory(os_dir))
		return true;
	errcode=GetLastError();
	if(errcode!=ERROR_PATH_NOT_FOUND && errcode!=ERROR_FILE_NOT_FOUND)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s'%s'): SetCurrentDirectory() failed: code %d.\n",
			caller,unix_dir_name,errcode);
#endif		/* CHECKED_BUILD */
		return false;
	}
	if(!force_create)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s'%s'): path not exist.\n",
			caller,unix_dir_name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* Move along path as far as possible */
	if(os_dir[1]==':')
	{
		/* Absolute path with drive (dir may or may not present) */
		ptr=os_dir+3;
		store=*ptr;
		*ptr=0;
		if(!SetCurrentDirectory(os_dir))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"%s'%s'): unable to set drive '%c' (code %d).\n",
				caller,unix_dir_name,os_dir[0],GetLastError());
#endif		/* CHECKED_BUILD */
			SetCurrentDirectory(cur_dir);
			return false;
		}
		*ptr=store;
		if(!os_dir[2])
			/* No directory was specified */
			return true;
	}
	else
		if(os_dir[0]=='\\')
		{
			/* Absolute path without drive */
			ptr=os_dir+1;
			*ptr=0;
			if(!SetCurrentDirectory(os_dir))
			{
				SetCurrentDirectory(cur_dir);
				return false;
			}
			*ptr='\\';
		}
		else
			/* Relative path */
			ptr=os_dir;
	dir_parser=tokParseString(ptr,"\\");
	for(;tokStrict(dir_parser);)
	{
		if(tokLast(dir_parser)[0]=='\\')
			continue;
		if(!SetCurrentDirectory(tokLast(dir_parser)))
		{
    		if(!CreateDirectory(tokLast(dir_parser),NULL))
    		{
#ifdef			CHECKED_BUILD
    			conlPrintf(LOG_ERROR,
    				"%s'%s'): CreateDirectory('%s') failed (code %d).\n",
    				caller,unix_dir_name,tokLast(dir_parser),GetLastError());
#endif			/* CHECKED_BUILD */
    			SetCurrentDirectory(cur_dir);
                tokDestroy(dir_parser);
    			return false;
    		}
    		if(!SetCurrentDirectory(tokLast(dir_parser)))
    		{
    			/* Can't change to new directory ? */
    			SetCurrentDirectory(cur_dir);
                tokDestroy(dir_parser);
    			return false;
    		}
		}
	}
	tokDestroy(dir_parser);
	return true;
}

#endif	/* !RIGHT_SLASHED */

/*
 *	Parses full path, splits directory part and name part.
 *	'dir_ret' and 'name_ret' must be al least _MAX_PATH long.
 */
void	V_CALL
vfsSplitDir(char *path,char *dir_ret,char *name_ret)
{
	char *end;
	int pl;
	if(!path || !*path)
	{
		if(dir_ret)
			*dir_ret=0;
		if(name_ret)
			*name_ret=0;
		return;
	}
	pl=strlen(path);
	for(end=&path[pl-1];end>path && *end!='/';end--);
	if(*end=='/')	/*	skip last slash */
		end++;
	if(name_ret)
		strCopy(name_ret,end,_MAX_PATH);
	if(dir_ret)
	{
		int dir_len=end-path;
		if(dir_len)
		{
			if(dir_len>=_MAX_PATH)
				dir_len=_MAX_PATH-1;
			memCopy(dir_ret,path,dir_len);
			dir_ret[dir_len]=0;
		}
		else
			*dir_ret=0;
	}
}

/*
 *	Splits name from extension.
 */
void	V_CALL
vfsSplitName(char *full_name,char *name_ret,char *ext_ret)
{
	char *end;
	int nl,ext_len;
	if(!full_name || !*full_name)
	{
		if(name_ret)
			*name_ret=0;
		if(ext_ret)
			*ext_ret=0;
	}
	nl=strlen(full_name);
	for(end=&full_name[nl-1];end>full_name && *end!='.';end--);
	if(end==full_name)
	{
		if(ext_ret)
			*ext_ret=0;
	}
	else
	{
		if(ext_ret)
			strCopy(ext_ret,end+1,_MAX_EXT);
	}
	if(name_ret)
	{
		ext_len=strlen(end+1);
		nl=nl-ext_len-1;
		if(nl>=_MAX_FNAME)
			nl=_MAX_FNAME-1;
		memCopy(name_ret,full_name,nl);
		name_ret[nl]=0;
	}
}

/*
 *	Splits full path.
 */
void	V_CALL
vfsSplitPath(char *full_path,char *dir_ret,char *name_ret,char *ext_ret)
{
	char full_name[_MAX_PATH];
	if(!full_path || full_path[0]==0)
    {
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vfsSplitPath('%s'): incorrect parameter.\n",dbs(full_path));
#endif	/* CHECKED_BUILD */
        if(dir_ret)
        	*dir_ret=0;
        if(name_ret)
        	*name_ret=0;
        if(ext_ret)
			*ext_ret=0;
		return;
    }
	vfsSplitDir(full_path,dir_ret,full_name);
	vfsSplitName(full_name,name_ret,ext_ret);
}

/*
 *	Makes full path from parts (some/all parts can be NULL).
 */
void	V_CALL
vfsMakePath(char *path_ret,char *dir,char *name, char *ext)
{
	int avail;
	if(!path_ret)
    {
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"fsMakePath(NULL,'%s','%s','%s'): incorrect dest path.\n",
			dbs(dir),dbs(name),dbs(ext));
#endif	/* CHECKED_BUILD */
		return;
	}
	*path_ret=0;
	avail=_MAX_PATH-1;
	if(dir && *dir)
	{
		strCopy(path_ret,dir,avail);
		strAppendSlash(path_ret);
		avail=_MAX_PATH-1-strlen(path_ret);
	}
	if(name && *name)
	{
		strCat(path_ret,name,avail);
		avail=_MAX_PATH-1-strlen(path_ret);
	}
	if(ext && *ext)
	{
		if(*ext!='.')
		{
			if(avail>0)
			{
				int len=_MAX_PATH-1-avail;
				path_ret[len++]='.';
				path_ret[len]=0;
				avail--;
			}
		}
		strCat(path_ret,ext,avail);
	}
}

#ifdef	__UNIX__

/*
 *	Use POSIX opendir() / readdir() / closedir().
 */

void	V_CALL
fs_scan_dir_r(
	char *root_dir,
	void (V_CALL *dir_callback)(char *unix_path),
	void (V_CALL *file_callback)(char *unix_file_name),
	vboolean recursive)
{
	DIR		*dir;
	struct	dirent *ent;
	struct	stat st;
    char 	fname[_MAX_PATH];
	int		root_len;
#ifdef	__UNIX__
	/* Disallow recursive seach in /proc.. and /dev... */
	if(recursive && (!strncmp(root_dir,"/proc",5) || 
		!strncmp(root_dir,"/dev",4)))
		return;
#endif	/* __UNIX__ */
	dir = opendir(root_dir);
	if(!dir)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"fsScan(): unable to opendir('%s').\n",root_dir);
#endif	/* CHECKED_BUILD */
		return;
	}
	strCopy(fname,root_dir,_MAX_PATH);
	root_len=strlen(root_dir);
	for(ent=readdir(dir);ent;ent=readdir(dir))
	{
		if(ent->d_name[0]=='.')	/* Skip hidden files */
			continue;
		fname[root_len]=0;
		strCat(fname,ent->d_name,_MAX_PATH-strlen(fname));
		if(stat(fname,&st)==-1)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"fsScan(): stat('%s') failed.\n",fname);
#endif	/* CHECKED_BUILD */
			continue;
		}
		st.st_mode &= S_IFMT; /* apply format mask */
		if(st.st_mode == S_IFDIR)
		{
			/* 'fname' is a name of the directory */
			strCat(fname,"/",_MAX_PATH-strlen(fname));
			if(dir_callback)
				dir_callback(fname);
			if(recursive)
            {
            	chdir(fname);
				fs_scan_dir_r(fname,dir_callback,file_callback,recursive);
                chdir("..");
			}
#if 0		/* Must be enabled for correct recursuion in '/dev..' */
			{
				off_t off;
				off=telldir(dir);
				/*	ў╧╧┬▌┼, ╦╔╥╔╠╠╔▐┼╙╦╔┼ ╦╧══┼╬╘┴╥╔╔ ╠╒▐█┼ ╬┼ ╔╙╨╧╠╪┌╧╫┴╘╪
				 *	Avoid recursion in virtual subdirs of /dev/fd/ and 
				 *	/proc/ directories.
				 *	Я часа три пытался понять почему код рекурсивно
				 *	входит в каталог /dev/fd/3; на выходе-то из программы
				 *	каталога НЕТ !!!
				 */
				closedir(dir);
				fs_scan_dir_r(fname,dir_callback,file_callback,recursive);
				dir=opendir(root_dir);
				if((int)off>0)
					seekdir(dir,off);
				else
					break;
			}
#endif
		}
		else if(st.st_mode == S_IFREG)
		{
			if(file_callback)
			{
				strCopy(fname,root_dir,_MAX_PATH);
				strCat(fname,ent->d_name,_MAX_PATH-strlen(fname));
				file_callback(fname);
			}
		}
	}
	closedir(dir);
}

#else	/* !__UNIX__ */

/*
 *	Use Win32 API for file enumeration.
 */
void	V_LOCAL
fs_scan_dir_r(
	char *root_dir,	/* unix form */
	void (V_CALL *dir_callback)(char *unix_path),
	void (V_CALL *file_callback)(char *unix_file_name),
	vboolean recursive)
{
	HANDLE sh;
	WIN32_FIND_DATA ffd;
	BOOL find_ok;
    char fname[_MAX_PATH];
	int root_len;
	sh=FindFirstFile("*.*",&ffd);
	if(sh==INVALID_HANDLE_VALUE)	/* never will happen ? */
		return;
	strCopy(fname,root_dir,_MAX_PATH);
	root_len=strlen(fname);
	find_ok=TRUE;
	while(find_ok)
	{
		if(ffd.cFileName[0]=='.')
		{
			find_ok=FindNextFile(sh,&ffd);
			continue;
		}
		fname[root_len]=0; /* un-strCat() name */
		strCat(fname,ffd.cFileName,_MAX_PATH-strlen(fname));
		if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			strAppendSlash(fname);
			if(dir_callback)
				dir_callback(fname);
			if(recursive)
			{
				if(lfsSetDir(fname,false))
                {
					fs_scan_dir_r(fname,dir_callback,file_callback,recursive);
                    lfsSetDir("..",false);
				}
#ifdef			CHECKED_BUILD
				else
				{
					conlPrintf(LOG_ERROR,
						"lfsEnumFiles(): SetCurrentDirectory(%s) failed "
						"(rc %d).\n",ffd.cFileName,GetLastError());
				}
#endif			/* CHECKED_BUILD */
			}
		}
		else
			file_callback(fname);
		find_ok=FindNextFile(sh,&ffd);
	}
	FindClose(sh);
}

#endif	/* !__UNIX__ */

/*
 *	Enumeration of files in given directory and all subdirs.
 */

void	V_CALL
lfsEnumFiles(
	void (V_CALL *dir_callback)(char *unix_path),
	void (V_CALL *file_callback)(char *unix_file_name),
	vboolean recursive)
{
	char root_dir[_MAX_PATH];
	if(!dir_callback && !file_callback)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"lfsEnumFiles(): nothing to do.\n");
#endif	/* CHECKED_BUILD */
		return;
	}
	lfsGetDir(root_dir,_MAX_PATH);
	strAppendSlash(root_dir);
	if(dir_callback)
		dir_callback(root_dir);
	fs_scan_dir_r(root_dir,dir_callback,file_callback,recursive);
	lfsSetDir(root_dir,false);
}
