#include <../source/TAL/objbase.h>
#include <../source/PAL/fsops.h>
#include <../source/vfs/drivers/fslist.h>
#include <ctype.h>

/*
 * TODO
 * provide means for 'connecting' vfs trees for multiple processes
 * consider using hash table for every tree / one fs
 * consider using inode sorting for read-only fs
 */

/*
 *	Allocates memory for leaf and stores passed fields
 */
V_LOCAL vfs_leaf_p
vfsAllocLeaf(char *leaf_name,vfs_dev_t *volume,u32_t inode)
{
	u32_t n_size;
	vfs_leaf_p leaf;
	n_size=strlen(leaf_name)+1;
	leaf=(vfs_leaf_t*)memAlloc(sizeof(vfs_leaf_t)+INT_ALIGN(n_size));
	leaf->name=(char_p)(leaf+1);
	memCopy(leaf->name,leaf_name,n_size);
	leaf->volume=volume;
	leaf->inode=inode;
	return leaf;
}

/*
 *	Allocates empty node.
 */
V_LOCAL	vfs_node_p
vfsAllocNode(char *node_name)
{
	vfs_node_p node;
	u32_t n_size=strlen(node_name)+1;
	node=(vfs_node_p)memAlloc(sizeof(vfs_node_t)+INT_ALIGN(n_size));
	node->name=(char_p)(node+1);
	memCopy(node->name,node_name,n_size);
	vpsInit(&node->files);
	vpsInit(&node->volumes);
	return node;
}

/*
 *	Removes all leafs and memFree()'s node.
 */
V_LOCAL	void	V_CALL
vfsDeleteNode(/*vfs_node_p*/void *node)
{
	u32_t i,nleafs;
	vfs_leaf_t **leafs;
	nleafs=vpsCount(&((vfs_node_t*)node)->files);
	leafs=(vfs_leaf_t**)vpsGetStorage(&((vfs_node_t*)node)->files);
	for(i=0;i<nleafs;i++)
		if(leafs[i])
			memFree(leafs[i]);
	vpsClear(&((vfs_node_t*)node)->files);
	vpsClear(&((vfs_node_t*)node)->volumes);
	memFree(node);
}

/*
 *	Searches in directory for file with given name.
 *	Search is CASE - SENSITIVE !
 */
V_LOCAL	vfs_leaf_p
vfsFindLeafInNode(vfs_node_t *dir,char *name)
{
	u32_t i,nleafs;
	vfs_leaf_t **leafs;
	nleafs=vpsCount(&dir->files);
	leafs=(vfs_leaf_t**)vpsGetStorage(&dir->files);
	for(i=0;i<nleafs;i++)
		if(!strcmp(name,leafs[i]->name))
			break;
	return i==nleafs ? NULL : leafs[i];
}

/*
 *	Finds child with given name.
 */
V_LOCAL ntree_p
vfsFindChildNode(ntree_p parent,char *node_name)
{
	u32_t i,nc=vpsCount(&parent->children);
	ntree_t **cnodes=(ntree_t**)vpsGetStorage(&parent->children);
	for(i=0;i<nc;i++)
		if(!strcmp(((vfs_node_t*)cnodes[i]->data)->name,node_name))
			break;
	if(i==nc)
		return NULL;
	return cnodes[i];
}

/*
 *	Converts relative path to absolute path.
 *	Looks too complex for such simple task ... :(
 
 * FIXME
 
 */
V_LOCAL	vboolean
lfsEnsurePathAbsolute(char *dest,char *src)
{
	u32_t i;
#ifndef	__UNIX__
	char upath[_MAX_PATH];
	if(src[1]==':')
	{
		vfsNative2Unix(upath,src);
		src=upath;
	}
#endif	/* !__UNIX__ */
	if(src[0]!='/')
	{
		/*
         * Split current path and walk throw all dirs,
         * expanding dots and doubledots
         */
		vhandle_t pp;
		char *strptr, *dname;
		char curpath[_MAX_PATH];
		vp_stack_t dirs=TYPED_STACK_EMPTY_INITIALIZER;
    	u8_stack_t path=TYPED_STACK_EMPTY_INITIALIZER;
		lfsGetDir(curpath,_MAX_PATH);
#ifdef	__UNIX__
		pp=tokParseString(curpath,"/");
#else	/* !__UNIX__ */
		pp=tokParseString(curpath+3,"/"); /* skip drive part */
#endif	/* !__UNIX__ */
		/* Parse current path */
		while(tokStrict(pp))
			if(tokLast(pp)[0]!='/')
				vpsPush(&dirs,strDup(tokLast(pp)));
		/* Parse source path */
		tokRestart(pp,src,"/");
    	while(tokStrict(pp))
    	{
    		dname=tokLast(pp);
    		if(dname[0]=='/' && dname[1]==0) /* Skip delimiter */
    			continue;
    		if(dname[0]=='.' && dname[1]==0) /* Dummy reference */
    			continue;
    		if(dname[0]=='.' && dname[1]=='.' && dname[2]==0)
    		{
    			/* move to up to root */
    			if(vpsPop(&dirs,(void**)&strptr))
    			{
	   				memFree(strptr);
    				continue;
    			}
   				/* Error: malformed path */
   				tokDestroy(pp);
   				vpsClear(&dirs);
#ifdef	CHECKED_BUILD
				conlPrintf(LOG_ERROR,"lfsEnsurePathAbsolute(): %s "
					"points higher than root.\n",src);
#endif	/* CHECKED_BUILD */
   				return false;
    		}
    		/* Add next dir to list */
    		vpsPush(&dirs,strDup(dname));
    	}
    	tokDestroy(pp);
    	/* Put all together */
#ifndef	__UNIX__
    	/* store drive */
    	u8sPushBuffer(&path,(u8_t*)curpath,3);
#endif	/* __UNIX__ */
		/* Get all strings in FIFO order */
    	for(i=0;i<vpsCount(&dirs);i++)
    	{
        	strptr=(char*)vpsGet(&dirs,i);
    		u8sPush(&path,'/');
    		u8sPushBuffer(&path,(u8_t*)strptr,strlen(strptr));
			memFree(strptr);
    	}
    	vpsClear(&dirs);
    	u8sPush(&path,0);
    	strCopy(dest,(char*)u8sGetStorage(&path),_MAX_PATH);
    	u8sClear(&path);
	}
	else
		strCopy(dest,src,_MAX_PATH);
	return true;
}

/*
 *	Finds given directory, optionally creates one ore more
 *	directories. Under NT absolute 'dir_name' MUST
 *	contain drive letter (for example, "//c/ ... ").
 */
ntree_p V_CALL
vfsFindNode(ntree_t *root,char *dir_name,vboolean force_creation)
{
	vhandle_t pp;		/* path parser */
	ntree_t *node, *child=NULL;
#ifndef	__UNIX__
	char drive[4];
#endif	/* !__UNIX__ */
	char udir[_MAX_PATH];
	if(!lfsEnsurePathAbsolute(udir,dir_name))
		return NULL;
	dir_name=udir;
#ifndef	__UNIX__
	/* Find drive */
	drive[0]=dir_name[2];
	if(*drive >= 'a' && *drive <= 'z')
		*drive = (char)(*drive + 'A' - 'a');		/* make drive uppercase */
	drive[1]=':';
	drive[2]=0;
	node=vfsFindChildNode(root,drive);
	if(!node)
	{
		if(!force_creation)
			return NULL;
		/* Drive not exist: create it and set 'root' to it's root */
		node=treeAllocNode(vfsAllocNode(drive),root);
		root=treeAllocNode(vfsAllocNode("/"),node);
	}
	else
		root=vfsFindChildNode(node,"/");
	if(strlen(dir_name)<=4)
		return root;
	pp=tokParseString(dir_name+4,"/");
#else	/* __UNIX__ */
	if(strlen(dir_name)<=1)
		return root;
	pp=tokParseString(dir_name+1,"/");
#endif	/* __UNIX__ */
	/*	Parse path */
	node=root;
	if(force_creation)
	{
		while(tokStrict(pp))
		{
			if(tokLast(pp)[0]=='/')
				continue;
			child=vfsFindChildNode(node,tokLast(pp));
			if(!child)
				child=treeAllocNode(vfsAllocNode(tokLast(pp)),node);
			node=child;
		}
	}
	else
	{
		while(tokStrict(pp))
		{
			if(tokLast(pp)[0]=='/')
				continue;
			child=vfsFindChildNode(node,tokLast(pp));
			if(!child)
				break;
			node=child;
		}
	}
	tokDestroy(pp);
	return child;
}

/*	----------------------------------------------------------------------
 *	VFS tree is n-way tree with data types of vfs_node_p. Root node
 *	contains either normal dirs (UNIX), or drive names like 'c:' (NT).
 *	----------------------------------------------------------------------
 */

vboolean	V_CALL
vfsAddNode(ntree_p root,char *full_path,vfs_dev_t *volume,u32_t inode)
{
	ntree_t *node;
	vfs_leaf_t *leaf;
	vfs_node_t *dirp;
	char dir[_MAX_PATH], name[_MAX_PATH];
	vfsSplitDir(full_path,dir,name);
	node=vfsFindNode(root,dir,true);
	dirp=(vfs_node_t*)node->data;
	leaf=vfsFindLeafInNode(dirp,name);
	if(!leaf)
	{
		leaf=vfsAllocLeaf(name,volume,inode);
		vpsPush(&dirp->files,leaf);
	}
	else
	{
		u32_t volinfo=0;
		vfs_dev_t *lastv=NULL;
		/*
		 *	WARNING new entry hides old one
		 */
		strCopy(leaf->name,name,_MAX_PATH);
		leaf->inode=inode;
		/* Remove reference to volume from directory volume list */
		vpsFind(&dirp->volumes,leaf->volume,&volinfo);
		vpsPop(&dirp->volumes,(void**)&lastv);
		if(volinfo<vpsCount(&dirp->volumes))
			vpsSet(&dirp->volumes,volinfo,lastv);
		/*	Set new node volume information */
		leaf->volume=volume;
	}
	/*	Store volume information in directory volume list */
	if(!vpsFind(&dirp->volumes,volume,NULL))
		vpsPush(&dirp->volumes,volume);
	return true;
}

vboolean	V_CALL
vfsGetNodeData(ntree_p root,char *full_path,vfs_dev_t **volume_ret,u32_t *inode_ret)
{
	ntree_t *node;
	vfs_leaf_t *leaf;
	char dir[_MAX_PATH], name[_MAX_PATH];
	vfsSplitDir(full_path,dir,name);
	node=vfsFindNode(root,dir,false);
	if(!node)
		return false;
	leaf=vfsFindLeafInNode((vfs_node_t*)node->data,name);
	if(!leaf)
		return false;
	if(volume_ret)
		*volume_ret=leaf->volume;
	if(inode_ret)
		*inode_ret=leaf->inode;
	return true;
}

/*	----------------------------------------------------------------------
 *	Mounting and unmounting ROMFS'
 */
/*	User-defined filesystem recognizers. */
vp_stack_t	vfs_mount_procs=TYPED_STACK_EMPTY_INITIALIZER;
/*
 *	The VFS tree.
 */
V_LOCAL		vfs_node_t	default_root_node=
{
	"/",
    TYPED_STACK_EMPTY_INITIALIZER,
    TYPED_STACK_EMPTY_INITIALIZER
};
V_LOCAL		ntree_t		empty_vfs_tree=
{
	&default_root_node,
	NULL,
	TYPED_STACK_EMPTY_INITIALIZER
};
ntree_p		vfs_root=&empty_vfs_tree;
vp_stack_t	vfs_volumes=TYPED_STACK_EMPTY_INITIALIZER;

/*
 *	Deletes all VFS tree nodes except root (root node wasn't
 *	dynamically allocated).
 */
V_LOCAL	void
vfsTruncateTree(void)
{
	u32_t ind,count;
	ntree_p *ch;
	ch=(ntree_p*)vpsGetStorage(&vfs_root->children);
	count=vpsCount(&vfs_root->children);
	for(ind=0;ind<count;ind++)
		treeDeleteAllNodes(ch[ind],vfsDeleteNode);
	vpsClear(&vfs_root->children);
	return;
}

V_LOCAL	vboolean
vfsCallMountProc(vfs_mount_proc_t mnt,char *volume_name)
{
	vfs_dev_t *fs;
	int i,n_inodes;
	char node_name[_MAX_PATH];
	fs=mnt(volume_name);
	if(fs)
	{
		vpsPush(&vfs_volumes,fs);
		n_inodes=fs->get_inode_count(fs);
		for(i=0;i<n_inodes;i++)
		{
			fs->get_file_name(fs,i,node_name);
			vfsAddNode(vfs_root,node_name,fs,i);
		}
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_TRACE,
			"vfsCallMountProc(): mounted '%s' (%d inodes).\n",
			volume_name,n_inodes);
#endif	/* CHECKED_BUILD */
		return true;
	}
	return false;
}

#ifdef	_MSC_VER
#pragma warning(disable:4054)	/* Cast between data and function pointers */
#pragma warning(disable:4055)
#endif	/* _MSC_VER */

void	V_CALL
vfsRegisterMountProcedure(vfs_mount_proc_t mount_proc)
{
	vpsPush(&vfs_mount_procs,(void*)mount_proc);
}

void	V_CALL
vfsMountVolume(char *volume_name)
{
	u32_t nm,i;
	vfs_mount_proc_t mnt;
	
    for(i=0;def_mnt_proc[i];i++)
		if(vfsCallMountProc(def_mnt_proc[i],volume_name))
        	return; /* mounted as build-in FS */

	nm=vpsCount(&vfs_mount_procs);
	for(i=0;i<nm;i++)
	{
		mnt=(vfs_mount_proc_t)vpsGet(&vfs_mount_procs,i);
		if(mnt)
        	if(vfsCallMountProc(mnt,volume_name))
            	return; /* mounted as client-defined FS */
	}
}

#ifdef	_MSC_VER
#pragma warning(default:4054)
#pragma warning(default:4055)
#endif	/* _MSC_VER */

void	V_CALL
vfsInit()
{
	if(!libvSafeThread())
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"fsInit(): can't mount virtual filesystems, "
			"other threads already spawned.\n");
#endif	/* CHECKED_BUILD */
		return;
	}
	lfsEnumFiles(NULL,vfsMountVolume,true);
}

void	V_CALL
vfsShutdown(void)
{
	u32_t i,nfs;
	vfs_dev_t **fs;
	if(!libvSafeThread())
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"fsShutdown(): can't unmount filesystems, "
			"other threads still running.\n");
#endif	/* CHECKED_BUILD */
		return;
	}
	nfs=vpsCount(&vfs_volumes);
	fs=(vfs_dev_t**)vpsGetStorage(&vfs_volumes);
	for(i=0;i<nfs;i++)
		if(fs[i])
			vfsDestroy(fs[i]);
	vpsClear(&vfs_volumes);
	vpsClear(&vfs_mount_procs);
	vfsTruncateTree();
}

/*
 * The gate
 */
file_t	V_CALL
vfsCreateFile(char *path)
{
	u32_t inode;
	vfs_dev_t *volume;
	char abspath[_MAX_PATH];
	/* Make absolute path */
	lfsEnsurePathAbsolute(abspath,path);
	/* Parse path, find node in tree */
	if(vfsGetNodeData(vfs_root,abspath,&volume,&inode))
		/* If node exists, tell fs driver to create file */
		return volume->create_file(volume,inode);
	return NULL;
}

/*	----------------------------------------------------------------------
 *	Enumeration functions
 */

/*
 *	Stores path from 'root' to given file. Size of 'path_ret' 
 *	must be al least _MAX_PATH.
 */
vboolean	V_CALL
vfsGetNodePath(char *path_ret,ntree_t *node)
{
	char trail;
	vfs_node_t *dir;
	u8_stack_t dest=TYPED_STACK_EMPTY_INITIALIZER;
	vp_stack_t names=TYPED_STACK_EMPTY_INITIALIZER;
	/* store node list: from current up to root */
	while(node)
	{
		vpsPush(&names,node->data);
		node=node->parent;
	}
	while(vpsPop(&names,(void_p*)&dir))
	{
        if(u8sCount(&dest))
        	trail=(char)u8sGet(&dest,u8sCount(&dest)-1);
		else
        	trail=0;
    	if(trail!='/' && dir->name[0]!='/')
			u8sPush(&dest,'/');
		u8sPushBuffer(&dest,(u8_t*)dir->name,(u32_t)strlen(dir->name));
	}
	vpsClear(&names);
    u8sPush(&dest,0);
	strCopy(path_ret,(char*)u8sGetStorage(&dest),_MAX_PATH);
	u8sClear(&dest);
	if(path_ret[0]==0)
		return false;
#ifndef	__UNIX__
	/* Correct drive part ("/c:/" -> "//c/") */
	path_ret[2]=path_ret[1];
	path_ret[1]='/';
#endif	/* !__UNIX__ */
	return true;
}

vfs_enum_p	V_CALL
vfsOpenDir(char *dir_name)
{
	ntree_p node;
	vfs_node_t *dir;
	vfs_enum_t *dir_enum;
	node=vfsFindNode(vfs_root,dir_name,false);
	if(!node)
	{
		/* Error: directory not exists ! */
		return NULL;
	}
	dir=(vfs_node_t*)node->data;
	dir_enum=(vfs_enum_t*)memAlloc(sizeof(vfs_enum_t));
	dir_enum->node=node;
	/* Make path to node */
	vfsGetNodePath(dir_enum->node_path,node);
	dir_enum->num_nodes=vpsCount(&node->children);
	dir_enum->num_leafs=vpsCount(&dir->files);
	dir_enum->node_num=0;
	dir_enum->leaf_num=0;
	/*
	 *	TODO:	for(all fs in dir)
	 *				fs->addref();
	 */
	return dir_enum;
}

vboolean	V_CALL
vfsReadDir(vfs_enum_p dir_enum)
{
	ntree_p child;
	vfs_node_t *dir;
	vfs_leaf_t *leaf;
	if(dir_enum->leaf_num==dir_enum->num_leafs)
    {
		u8_stack_t c_name=TYPED_STACK_EMPTY_INITIALIZER;
		if(dir_enum->node_num==dir_enum->num_nodes)
			return false; /* Error: end of directory */
		/* Read next child node */
		child=(ntree_p)vpsGet(&dir_enum->node->children,
			dir_enum->node_num);
		dir=((vfs_node_t*)child->data);
		dir_enum->vfs_mode=vfs_mode_directory;
		dir_enum->vfs_dev=NULL;
		dir_enum->vfs_ino=0;
		u8sPushBuffer(&c_name,(u8_t*)dir_enum->node_path,
        	strlen(dir_enum->node_path));
		u8sPush(&c_name,'/');
		u8sPushBuffer(&c_name,(u8_t*)dir->name,strlen(dir->name));
		u8sPush(&c_name,0);
		strCopy(dir_enum->vfs_name,(char*)u8sGetStorage(&c_name),_MAX_PATH);
		u8sClear(&c_name);
		/* Advance directory pointer */
		dir_enum->node_num++;
	}
	else
	{
		/* Read next leaf from this node */
		dir=((vfs_node_t*)dir_enum->node->data);
		leaf=(vfs_leaf_t*)vpsGet(&dir->files,dir_enum->leaf_num);
		dir_enum->vfs_mode=vfs_mode_normal;
		dir_enum->vfs_dev=leaf->volume;
		dir_enum->vfs_ino=leaf->inode;
		/* Concat path and file name */
		vfsMakePath(dir_enum->vfs_name,dir_enum->node_path,leaf->name,NULL);
		/* Advance leaf pointer */
		dir_enum->leaf_num++;
	}
	return true;
}

vboolean	V_LOCAL
vfsSeekDirEnum(vfs_enum_p dir_enum,int offset,int limit)
{
	int off_node=offset-dir_enum->num_leafs;
	if(offset>limit)
		return false;
	if(off_node>=0)
	{
		dir_enum->leaf_num=(u32_t)dir_enum->num_leafs;
		dir_enum->node_num=(u32_t)off_node;
	}
	else
	{
		dir_enum->leaf_num=(u32_t)offset;
		dir_enum->node_num=0;
	}
	return true;
}

vboolean	V_CALL
vfsSeekDir(vfs_enum_p dir_enum,int offset,int whence)
{
	int total=(int)(dir_enum->num_nodes + dir_enum->num_leafs);
	int passed=(int)(dir_enum->leaf_num + dir_enum->node_num);
	switch(whence)
	{
		case SEEK_SET:
			return vfsSeekDirEnum(dir_enum,offset,total);
		case SEEK_CUR:
			return vfsSeekDirEnum(dir_enum,offset+passed,total);
		case SEEK_END:
			return vfsSeekDirEnum(dir_enum,offset+total,total);
	}
	return false;
}

int			V_CALL
vfsTellDir(vfs_enum_p dir_enum)
{
	return (int)(dir_enum->leaf_num + dir_enum->node_num);
}

void		V_CALL
vfsCloseDir(vfs_enum_p dir_enum)
{
	/*
	 *	TODO:	for(all fs in dir)
	 *				fs->release();
	 */
	memFree(dir_enum);
}

V_LOCAL	void
vfsEnumFiles_r(vfs_enum_t *dir,void (V_CALL *dcb)(char_p),
	void (V_CALL *fcb)(char_p),vboolean recursive)
{
	u32_t i,len;
	if(!dir)
		return;
	vfsSeekDir(dir,0,SEEK_END);
	len=vfsTellDir(dir);
	vfsSeekDir(dir,0,SEEK_SET);
	/* Optimize me ! */
	for(i=0;i<len;i++)
	{
		vfsReadDir(dir);
		if(dir->vfs_mode==vfs_mode_normal)
		{
			if(fcb)
				fcb(dir->vfs_name);
		}
		else
		{
			if(dcb)
				dcb(dir->vfs_name);
			if(recursive)
			{
				vfs_enum_p cd;
				cd=vfsOpenDir(dir->vfs_name);
				vfsEnumFiles_r(cd,dcb,fcb,true);
				vfsCloseDir(cd);
			}
		}
	}
}

#include <../source/PAL/argsused.h>
V_LOCAL	void	V_CALL	vfs_dummy_callback(char *vfs_name)
{
}

void	V_CALL
vfsEnumFiles(
	char *vfs_path,
	void (V_CALL *dir_callback)(char *dir_name),
	void (V_CALL *file_callback)(char *file_name),
	vboolean recursive)
{
	vfs_enum_p dir;
	char curpath[_MAX_PATH];
	if(!dir_callback && !file_callback)
		return;
	if(!vfs_path)
	{
		lfsGetDir(curpath,_MAX_PATH);
		vfs_path=curpath;
	}
	dir=vfsOpenDir(vfs_path);
	if(!dir)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_TRACE,
			"vfsEnumFiles(): path '%s' not found in VFS tree.\n",
			curpath);
#endif	/* CHECKED_BUILD */
		return;
	}
	if(!dir_callback)
		dir_callback=vfs_dummy_callback;
	else
    	dir_callback(dir->node_path);
	if(!file_callback)
		file_callback=vfs_dummy_callback;
    vfsEnumFiles_r(dir,dir_callback,file_callback,recursive);
	vfsCloseDir(dir);
}

#ifdef	VFS_TREE_DEBUG

V_LOCAL void
vfsDumpNode_r(FILE *out,ntree_t *node)
{
	u32_t i,len;
	vfs_node_t *dir;
	ntree_t **cnodes;
	vfs_leaf_t **files;
	char node_name[_MAX_PATH];
	if(!node)
		return;
	dir=(vfs_node_t*)node->data;
	vfsGetNodePath(node_name,node);
	fprintf(out,"DIR [%s]\n",node_name);
	len=vpsCount(&dir->files);
	files=(vfs_leaf_t**)vpsGetStorage(&dir->files);
	for(i=0;i<len;i++)
		fprintf(out,"%s\n",files[i]->name);
	len=vpsCount(&node->children);
	fprintf(out,"(VFS node '%s' has %d child nodes)\n",
		dir->name,(int)len);
	cnodes=(ntree_t**)vpsGetStorage(&node->children);
	for(i=0;i<len;i++)
		vfsDumpNode_r(out,cnodes[i]);
	return;
}

void	V_CALL
vfsDumpTree(FILE *out)
{
	if(!out)
		out=stdout;
	vfsDumpNode_r(out,vfs_root);
}

#endif	/* VFS_TREE_DEBUG */
