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

/* TODO convert to use vsize_t, vssize_t */

/*
 * FIXME: move it somewhere else !
 */
#ifdef	CHECKED_BUILD
static char *libv_wrong_param = "incorrect parameter(s).";
#endif	/* CHECKED_BUILD */

vboolean	V_CALL
lfsGetDir(char *buffer,u32_t size)
{
	if(!buffer || !size)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"lfsGetDir('%s',%d): %s\n",
			strSafe(buffer),size,libv_wrong_param);
#endif	/* CHECKED_BUILD */
		return false;
	}
	return archGetCurrentDirectory(buffer,size);
}

vboolean	V_CALL
lfsSetDir(char *right_slashed_dir,vboolean force_creation)
{
	vboolean dir_set, dir_exists;
	char cur_dir[_MAX_PATH];
	char dir_buf[_MAX_PATH], *dir_name = dir_buf;
	char *slash="/";
	vhandle_t dir_parser;
#ifdef		CHECKED_BUILD
	static char *caller="lfsSetDir";
#endif		/* CHECKED_BUILD */
	if(!right_slashed_dir)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s(): %s\n",caller,libv_wrong_param);
#endif	/* CHECKED_BUILD */
		return false;
	}
	strCopy(dir_name,right_slashed_dir,_MAX_PATH);
	if(dir_name[0])
	{
		char *endptr = dir_name + strlen(dir_name) - 1;
		if(*endptr == slash[0])
			*endptr = 0;
	}
	else
		return true;
	if(dir_name[0] == '.' && !dir_name[1])
		return true;
	if(!archGetCurrentDirectory(cur_dir,_MAX_PATH))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(%s): archGetCurrentDirectory() failed.\n",caller,dir_name);
#endif	/* CHECKED_BUILD */
	}
	/*
	 * Try to set 'dir_name' current
	 */
	if(archSetCurrentDirectory(dir_name,&dir_exists))
		return true;
	if(dir_exists)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s(%s): directory exist "
			"but can't be made current.\n",caller,dir_name);
#endif		/* CHECKED_BUILD */
		return false;
	}
	if(!force_creation)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s(%s): directory not exists.\n",
			caller,dir_name);
#endif	/* CHECKED_BUILD */
		return false;
	}
	/*
	 * Move along path as far as possible
	 */
	if(dir_name[0] == slash[0])
	{
		/*
		 * Absolute path
		 */
		archSetCurrentDirectory(slash,NULL); /* can't fail */
		dir_name ++;
	}
	dir_parser = lexParseString(dir_name,slash);
	lexDisable(dir_parser,LEX_DEFAULT_DELIMITERS);
	while(lexNext(dir_parser))
	{
		if(lexLastWas(dir_parser,slash))
			continue;
		dir_set = archSetCurrentDirectory(lexLast(dir_parser),&dir_exists);
		if(dir_set)
			continue;
		if(dir_exists) /* exist but can't be made current: EPERM ? */
			break;
		archMakeDirectory(lexLast(dir_parser));
		if(!archSetCurrentDirectory(lexLast(dir_parser),NULL))
			break;
	}
	dir_set = (lexLast(dir_parser) == NULL) ? true : false;
	lexDestroy(dir_parser);
	if(!dir_set)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s(%s): unable to create path.\n",caller,right_slashed_dir);
#endif	/* CHECKED_BUILD */
		archSetCurrentDirectory(cur_dir,NULL);
	}
	return dir_set;
}

/*
 * 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);
	/* go backward to last path delimiter */
	for(end = &path[pl-1];end > path && *end!='/';end--);
	
	if(*end=='/')	/* skip last path delimiter */
		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);
	/* go backward to last dot */
	for(end = &full_name[nl-1];end>full_name && *end!='.';end--);
	/* if there are no more chars - full_name is a file name, ext is empty */
	if(end == full_name && *end != '.')
	{
		if(ext_ret)
			*ext_ret=0;
		if(name_ret)
			strCopy(name_ret,full_name,_MAX_FNAME);
		return;
	}
	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;
		if(nl)
			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)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vfsSplitPath('%s'): incorrect parameter.\n",strSafe(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",
			strSafe(dir),strSafe(name),strSafe(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);
	}
}

void	V_CALL
lfsScanDirectory_r(
	char *from_dir,
	void (V_CALL *dir_callback)(char *unix_path),
	void (V_CALL *file_callback)(char *unix_file_name),
	vboolean recursive)
{
#ifdef	CHECKED_BUILD
	static char *procname = "lfsScanDirectory_r(";
#endif	/* CHECKED_BUILD */
	struct arch_dir_s dir;
	char 	fname[_MAX_PATH];
	int		root_len;
	vboolean ok;
	/*
	 * Disallow recursive seach in /proc/.. and /dev/...
	 */
	if(recursive && (!strncmp(from_dir,"/proc",5) || 
		!strncmp(from_dir,"/dev",4)))
		return;
	ok = archFindFirst(from_dir,&dir);
	if(!ok)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s): archFindFirst(%s) failed.\n",
			procname,from_dir);
#endif	/* CHECKED_BUILD */
		return;
	}
	strCopy(fname,from_dir,_MAX_PATH);
	root_len = strlen(from_dir);
	for(;ok;ok = archFindNext(&dir))
	{
		if(dir.name[0]=='.')	/* Skip "..", "." and 'hidden' entries */
			continue;
		fname[root_len] = 0;
		strCat(fname,dir.name,_MAX_PATH-strlen(fname));
		if(dir.is_dir)
		{
			/*
			 * Current entry is a directory
			 */
			strCat(fname,"/",_MAX_PATH-strlen(fname));
			if(dir_callback)
				dir_callback(fname);
			if(recursive)
			{
				if(archSetCurrentDirectory(fname,NULL))
				{
					lfsScanDirectory_r(fname,dir_callback,file_callback,recursive);
					archSetCurrentDirectory("..",NULL);
				}
#ifdef	CHECKED_BUILD
				else
				{
					conlPrintf(LOG_ERROR,
						"%s): archSetCurrentDirectory(%s) failed.\n",
						procname,fname);
				}
#endif	/* CHECKED_BUILD */
			}
		}
		else if(dir.ftype == file_type_reg)
		{
			/*
			 * Current entry is a file
			 */
			if(file_callback)
			{
				strCopy(fname,from_dir,_MAX_PATH);
				strCat(fname,dir.name,_MAX_PATH-strlen(fname));
				file_callback(fname);
			}
		}
	}
	archFindClose(&dir);
}

/*
 * Enumerates contents of current directory (and all subdirs
 * if recursive == true). For every file, calls file_callback(),
 * for every dir - dir_callback().
 */
void	V_CALL
lfsEnumFiles(
	void (V_CALL *dir_callback)(char *dir_name),
	void (V_CALL *file_callback)(char *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;
	}
	archGetCurrentDirectory(root_dir,_MAX_PATH);
	strAppendSlash(root_dir);
	if(dir_callback)
		dir_callback(root_dir);
	lfsScanDirectory_r(root_dir,dir_callback,file_callback,recursive);
	archSetCurrentDirectory(root_dir,NULL);
}
