/*
 * fspakfs.c: implemenation of PAK filesystem.
 * Original PAK file format: Copyright (C) Id Software.
 */
#include <../source/TAL/objbase.h>
#include <../source/vfs/drivers/fslist.h>

#ifdef	CONFIG_VFS_PAKFS


#if		BYTE_ORDER != LITTLE_ENDIAN
#error	PAKFS code must be rewritten
#endif	/* BYTE_ORDER != LITTLE_ENDIAN */

typedef struct dpackfile_s
{
	char_t	name[56];
	s32_t	filepos, filelen;
}	dpackfile_t, *dpackfile_p;

typedef struct dpackheader_s
{
	u8_t		ident[4];
	u32_t		dirofs;
	u32_t		dirlen;
}	dpackheader_t, *dpackheader_p;

typedef struct vfs_pakfs_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 */
	dpackfile_t	*dir;
}	vfs_pakfs_t, *vfs_pakfs_p;

/*
 *	PAKFS methods
 */
V_LOCAL	file_t	V_CALL
pakfs_create_port(vfs_dev_t *self,u32_t inode)
{
    dpackfile_t *dirp;
	io_section_p handle;
	if(inode>=((vfs_pakfs_t*)self)->num_ino)
    	return NULL;
    dirp=((vfs_pakfs_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_pakfs_t*)self)->dhandle_lock);
    fileSeek(((vfs_pakfs_t*)self)->dhandle,dirp->filepos,SEEK_SET);
	fileRead(((vfs_pakfs_t*)self)->dhandle,handle->base,dirp->filelen);
	mutexUnlock(((vfs_pakfs_t*)self)->dhandle_lock);
	return (file_t)handle;
}

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

V_LOCAL	u32_t V_CALL
pakfs_get_inode_count(vfs_dev_t *self)
{
	return ((vfs_pakfs_t*)self)->num_ino;
}

V_LOCAL	vboolean	V_CALL
pakfs_get_file_name(vfs_dev_t *self,u32_t inode,char *name_ret)
{
	if(inode>=((vfs_pakfs_t*)self)->num_ino)
    	return false;
	strCopy(name_ret,((vfs_pakfs_t*)self)->dir[inode].name,_MAX_PATH);
    return true;
}

/*
 * PAKFS destructor
 * Note: we leave ref_count untouched !
 */
V_LOCAL	u32_t V_CALL
pakfs_destroy(vhandle_t handle)
{
	u32_t ref_count;
	vfs_pakfs_p self=(vfs_pakfs_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;
}

/*
 *	PAKFS factory
 */
vfs_dev_p	V_CALL
vfsMountPak(char *volume_name)
{
	void *base;
    u32_t dtype;
    vfs_pakfs_p dev;
	file_t dhandle;
	char ext[_MAX_EXT];
	dpackheader_t head={{0,0,0,0},0,0};
    fsSplitPath(volume_name,NULL,NULL,ext);
    if(strcasecmp(ext,"pak"))
    	return NULL;
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vfsMountPak(): 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,
			"vfsMountPak('%s'): bad file type.\n",volume_name);
#endif	/* CHECKED_BUILD */
    }
    fileRead(dhandle,head.ident,4);
    head.dirofs=fileGetLittleLong(dhandle);
    head.dirlen=fileGetLittleLong(dhandle);
	if(memcmp(head.ident,"PACK",4))
	{
		fileClose(dhandle);
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"vfsMountPak('%s'): isn't PAK file.\n",
			volume_name);
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	/* Try to mmap() entire file */
    if((dtype & PCTL_SUPPORT_MMAP) &&
    	fileControl(dhandle,pctl_map_ispace,&base))
	{
		dev=(vfs_pakfs_t*)vfsAlloc(sizeof(vfs_pakfs_t));
        dev->base = (u8_t*)base;
        /* FIXME - endian dependence */
        dev->dir=(dpackfile_t*)((char*)base+head.dirofs);
        dev->vtbl.create_file=pakfs_mapped_create_port;
	    dev->dhandle_lock = NULL;
	}
	else
    {
		dev=(vfs_pakfs_t*)vfsAlloc(sizeof(vfs_pakfs_t)+head.dirlen);
        dev->base=NULL;
	    dev->dir=(dpackfile_t*)(dev+1);
	    dev->dhandle_lock=mutexCreate();
        dev->vtbl.create_file=pakfs_create_port;
	    fileSeek(dhandle,head.dirofs,SEEK_SET);
        /* FIXME - endian dependence */
		if(fileRead(dhandle,dev->dir,head.dirlen)!=head.dirlen)
		{
			fileClose(dhandle);
			memFreeObject(dev);
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_WARNING,
				"vfsMountPak('%s'): error reading PAK directory.\n",
        	    volume_name);
#endif		/* CHECKED_BUILD */
			return NULL;
		}
    }
	strCopy(dev->volume_name,volume_name,_MAX_PATH);
	dev->num_ino=head.dirlen/sizeof(dpackfile_t);
	dev->dhandle=dhandle;
	dev->vtbl.get_inode_count=pakfs_get_inode_count;
	dev->vtbl.get_file_name=pakfs_get_file_name;
    dev->vtbl.base.destroy=pakfs_destroy;
	return (vfs_dev_p)dev;
}

#endif	/* CONFIG_VFS_PAKFS */
