/*
 * fswadfs.c: implemenation of WAD (DOOM) filesystem.
 * Original WAD file format: Copyright (C) Id Software.
 */

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

#ifdef	CONFIG_VFS_WADFS

/*
 * TODO: current driver doesn't handle duplicate names !
 */
 
#if		BYTE_ORDER != LITTLE_ENDIAN
#error	WADFS code must be rewritten
#endif	/* BYTE_ORDER != LITTLE_ENDIAN */

typedef struct dwadfile_s
{
	u32_t	filepos;
	u32_t	filelen;
	char_t	name[8];
}	dwadfile_t, *dwadfile_p;

typedef struct vfs_wadfs_s
{
	vfs_dev_t	vtbl;
    file_t		dhandle;
    mutex_t		dhandle_lock;
	u32_t		num_ino;
	char		volume_name[_MAX_PATH];
	u8_t		*base; /* used if io stream support mmap */
	dwadfile_t	*dir;
}	vfs_wadfs_t, *vfs_wadfs_p;

/*
 * WADFS methods
 */
V_LOCAL	file_t	V_CALL
wadfs_create_port(vfs_dev_t *self,u32_t inode)
{
    dwadfile_t *dirp;
	io_section_p handle;
	if(inode >= ((vfs_wadfs_t*)self)->num_ino)
    	return NULL;
    dirp = ((vfs_wadfs_t*)self)->dir + inode;
	/* Allocate memory and copy resource */
    handle = ioAllocSection(sizeof(io_section_t)+dirp->filelen,NULL,
    	dirp->filelen);
	handle->base = (u8_t*)(handle+1);
	mutexLock(((vfs_wadfs_t*)self)->dhandle_lock);
    fileSeek(((vfs_wadfs_t*)self)->dhandle,dirp->filepos,SEEK_SET);
	fileRead(((vfs_wadfs_t*)self)->dhandle,handle->base,dirp->filelen);
	mutexUnlock(((vfs_wadfs_t*)self)->dhandle_lock);
	return (file_t)handle;
}

V_LOCAL	file_t	V_CALL
wadfs_mapped_create_port(vfs_dev_t *self,u32_t inode)
{
    dwadfile_t *dirp;
	io_section_t *handle;
	/*
	 * Note: locks are needless.
	 */
	if(inode >= ((vfs_wadfs_t*)self)->num_ino)
    	return NULL;
    dirp=((vfs_wadfs_t*)self)->dir + inode;
	/* Allocate memory, set data pointer */
    handle = ioAllocSection(sizeof(io_section_t),
    	((vfs_wadfs_t*)self)->base + dirp->filepos,
        dirp->filelen);
    return (file_t)handle;
}

V_LOCAL	u32_t V_CALL
wadfs_get_inode_count(vfs_dev_t *self)
{
	return ((vfs_wadfs_t*)self)->num_ino;
}

V_LOCAL	vboolean	V_CALL
wadfs_get_file_name(vfs_dev_t *self,u32_t inode,char *name_ret)
{
	int i;
	if(inode >= ((vfs_wadfs_t*)self)->num_ino)
    	return false;
	strCopy(name_ret,((vfs_wadfs_t*)self)->dir[inode].name,9);
	for(i=0;i<8;i++)
		if((name_ret[i] >= 'A') && (name_ret[i] <= 'Z'))
			name_ret[i] += 'a' - 'A';
    return true;
}

/*
 * WADFS destructor
 * Note: we leave ref_count untouched !
 */
V_LOCAL	u32_t V_CALL
wadfs_destroy(vhandle_t handle)
{
	u32_t ref_count;
	vfs_wadfs_p self=(vfs_wadfs_p)handle;
	/*
	mutexLock(self->vtbl.vfs_lock);
	*/
	ref_count = self->vtbl.base.ref_count;
	/*
	mutexUnock(self->vtbl.vfs_lock);
	*/
	if(ref_count == 1)
	{
		if(self->dhandle != NULL)
			fileClose(self->dhandle);
		if(self->dhandle_lock != NULL)
			mutexDestroy(self->dhandle_lock);
		memFreeObject(self);
		return 0;
	}
	return ref_count;
}

/*
 * WADFS factory
 */
vfs_dev_p	V_CALL
vfsMountWad(char *volume_name)
{
	u8_t id[4];
	void *base;
    u32_t dtype;
    vfs_wadfs_p dev;
	file_t dhandle;
	char ext[_MAX_EXT];
	u32_t numlumps, infotableoffs, dirlen;

    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"wad"))
    	return NULL;
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountWad(): adding '%s' ...\n",volume_name);
#endif	/* CHECKED_BUILD */
	dhandle = lfsCreateFile(volume_name,filemode_read);
    if(dhandle == NULL)
    	return NULL;
	if(!fileControl(dhandle,pctl_get_type,&dtype) ||
    	!(dtype & PCTL_BLOCK_DEVICE))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,
			"vfsMountWad('%s'): bad file type.\n",volume_name);
#endif	/* CHECKED_BUILD */
    }
	fileRead(dhandle,id,4);
	if(id[1]!='W' || id[2]!='A' || id[3]!='D')
	{
		fileClose(dhandle);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"vfsMountWad('%s'): isn't WAD file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	numlumps = fileGetLittleLong(dhandle);
	dirlen = numlumps * sizeof(dwadfile_t);
	infotableoffs = fileGetLittleLong(dhandle);
	/* Try to mmap() entire file */
    if((dtype & PCTL_SUPPORT_MMAP) &&
    	fileControl(dhandle,pctl_map_ispace,&base))
	{
		dev=(vfs_wadfs_t*)vfsAlloc(sizeof(vfs_wadfs_t));
        dev->base = (u8_t*)base;
        /* FIXME - endian dependence */
        dev->dir=(dwadfile_t*)((char*)base+infotableoffs);
        dev->vtbl.create_file = wadfs_mapped_create_port;
	    dev->dhandle_lock = NULL;
	}
	else
    {
		dev=(vfs_wadfs_t*)vfsAlloc(sizeof(vfs_wadfs_t) + dirlen);
        dev->base = NULL;
	    dev->dir = (dwadfile_t*)(dev+1);
	    dev->dhandle_lock = mutexCreate();
        dev->vtbl.create_file = wadfs_create_port;
	    fileSeek(dhandle,infotableoffs,SEEK_SET);
        /* FIXME - endian dependence */
		if(fileRead(dhandle,dev->dir,dirlen) != dirlen)
		{
			fileClose(dhandle);
			memFreeObject(dev);
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_WARNING,
				"vfsMountWad('%s'): error reading WAD directory.\n",
        	    volume_name);
#endif		/* CHECKED_BUILD */
			return NULL;
		}
    }
	strCopy(dev->volume_name,volume_name,_MAX_PATH);
	dev->num_ino = numlumps;
	dev->dhandle = dhandle;
	dev->vtbl.get_inode_count = wadfs_get_inode_count;
	dev->vtbl.get_file_name = wadfs_get_file_name;
    dev->vtbl.base.destroy = wadfs_destroy;
	return (vfs_dev_p)dev;
}

#endif	/* CONFIG_VFS_WADFS */
