/*
 *	fbase.c: implementation of base file object.
 */
#ifdef	__UNIX__
/*
 * We use stdio routines, but still need system headers
 * for stat() and strerror().
 */
#include <../source/PAL/oshead.h>
#endif	/* __UNIX__ */

#include <../source/TAL/objbase.h>
#include <../source/vfs/iobase.h>
#include <../source/vfs/fbase.h>
#include <../source/vfs/mbase.h>
#include <../source/PAL/mmap.h>
#include <../source/PAL/fsops.h>

/*
 * BIG TODO: convert to use functions from 'arch' subtree.
 */
typedef struct	lfs_file_s
{
	ioport_base_t	vtbl;
	char			fname[_MAX_PATH];
	filemode_t		fmode;
	u32_t			fsize;
	FILE			*fhandle;
	u8_t			*base;
}	lfs_file_t, *lfs_file_p;

typedef struct	vfs_mapped_file_s
{
	io_section_t	port;
	mmap_t			map;
}	vfs_mapped_file_t, *vfs_mapped_file_p;

V_LOCAL u32_t V_CALL lfs_read(vhandle_t handle,void *buf,u32_t len)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	return fread(buf,1,len,self->fhandle);
}

V_LOCAL u32_t V_CALL lfs_write(vhandle_t handle,void *buf,u32_t len)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	return fwrite(buf,1,len,self->fhandle);
}

V_LOCAL u32_t V_CALL lfs_tell(vhandle_t handle)
{
	long int retval;
	lfs_file_t *self = (lfs_file_t*)handle;
	retval = ftell(self->fhandle);
	return (retval != -1) ? (u32_t)retval : INVALID_IO_OFFSET;
}

V_LOCAL u32_t V_CALL lfs_seek(vhandle_t handle,s32_t offset,int whence)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	if(fseek(self->fhandle,offset,whence))
		return INVALID_IO_OFFSET;
	return ftell(self->fhandle);
}

V_LOCAL u32_t V_CALL lfs_destroy(vhandle_t handle)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	if(--self->vtbl.base.ref_count)
    	return self->vtbl.base.ref_count;
	if(self->fhandle != stdout && self->fhandle != stdin)
		fclose(self->fhandle);
	if(self->base)
		memFree(self->base);
	memFreeObject(self);
	return 0;
}

V_LOCAL u32_t V_CALL lfs_destroy_mapped(vhandle_t handle)
{
	vfs_mapped_file_t *self = (vfs_mapped_file_t*)handle;
	if(--self->port.vtbl.base.ref_count)
    	return self->port.vtbl.base.ref_count;
	if(self->map)
		mmapClose(self->map);
	memFreeObject(self);
	return 0;
}

V_LOCAL	int V_CALL lfs_ioctl(vhandle_t handle,int code,void *parameters)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	switch(code)
    {
		case pctl_get_name:
			puts("1");
			if(parameters)
			{
				strCopy((char*)parameters,self->fname,_MAX_PATH);
				puts("2");
				return 0;
			}
			puts("3");
			return -1;
    	case pctl_get_type:
        	if(!parameters)
            	return -1;
			if(self->vtbl.seek == lfs_seek)
               	*((u32_t*)parameters)=PCTL_BLOCK_DEVICE;
			else
               	*((u32_t*)parameters)=0;
            return 0;
		case pctl_map_ispace:
        	if(!parameters)
            	return -1;
			if(!self->base)
			{
				long int pos = ftell(self->fhandle);
				self->base = (u8_t*)memAlloc(self->fsize);
				if(!self->base)
					return -1;
				fseek(self->fhandle,0,SEEK_SET);
				fread(self->base,self->fsize,1,self->fhandle);
				fseek(self->fhandle,pos,SEEK_SET);
			}
			*((u8_t**)parameters) = self->base;
			return 0;
		case pctl_duplicate_handle:
		{
			vhandle_t newhandle;
        	if(!parameters)
            	return -1;
			if(self->fmode != filemode_read)
				return -1;
			newhandle = lfsCreateFile(self->fname,filemode_read);
			if(newhandle == LIBV_INVALID_HANDLE)
				return -1;
			*((vhandle_t*)parameters) = newhandle;
			return 0;
		}
		default:
			break;
	}
	return -1;
}

/*
 * Factory function for memory-mapped files.
 * TODO: convert to use archFileXXX() functions
 */
vhandle_t V_LOCAL
lfsMapFile(char *file_name)
{
	mmap_t map;
	vfs_mapped_file_p port;
	map = mmapOpenRead(file_name);
	if(map == LIBV_INVALID_HANDLE)
		return LIBV_INVALID_HANDLE;
	port = (vfs_mapped_file_p)ioAllocNamedSection(
		sizeof(vfs_mapped_file_t),mmapBase(map),mmapSize(map),
		file_name);
	port->port.vtbl.base.destroy=lfs_destroy_mapped;
	port->map = map;
	return port;
}

/*
 * Creates IO port for disk file
 * TODO: convert to use archFileXXX() functions
 */
vhandle_t V_CALL
lfsCreateFile(char *name,filemode_t mode)
{
	vhandle_t mapped;
	FILE *handle;
	char *fmode;
	lfs_file_t *f;
#ifndef	RIGHT_SLASHED
   	char os_name_buf[_MAX_PATH];
   	char *os_name; /* will be set to os_name_buf later */
#else	/* RIGHT_SLASHED */
	char *os_name=name;
#endif	/* RIGHT_SLASHED */
	if(!name)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"lfsCreateFile(NULL): incorrect parameter.\n");
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
#ifdef	__UNIX__
	/*
	 * Under UNIX, disallow to open special files
	 */
	if(mode == filemode_read)
	{
		struct stat st;
		if(stat(name,&st) == -1)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"lfsCreateFile('%s'): stat() failed (%s).\n",
				name,strerror(errno));
#endif		/* CHECKED_BUILD */
			return LIBV_INVALID_HANDLE;
		}
		if(!(st.st_mode & S_IFREG))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"lfsCreateFile('%s'): isn't regular file\n",name);
#endif		/* CHECKED_BUILD */
			return LIBV_INVALID_HANDLE;
		}
	}
#endif	/* __UNIX__ */
	if(mode == filemode_read)
	{
		/* Try to map file */
		mapped = lfsMapFile(name);
		if(mapped != LIBV_INVALID_HANDLE)
			return mapped;
		/* mmap() failed; try to use stdio */
		fmode="rb";
	}
	else
	{
		char dir[_MAX_DIR];
		switch(mode)
		{
			case filemode_append:	fmode="ab"; break;
			case filemode_write:	fmode="wb"; break;
			default:				fmode=NULL; break;
		}
		if(!fmode)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"lfsCreateFile('%s','%d'): unknown mode '%d'.\n",
				name,(int)mode,(int)mode);
#endif	/* CHECKED_BUILD */
			return LIBV_INVALID_HANDLE;
		}
		/* Check for target directory */
		fsSplitPath(name,dir,NULL,NULL);
		if(dir[0])
        {
        	char curdir[_MAX_DIR];
            lfsGetDir(curdir,_MAX_DIR);
			if(!lfsSetDir(dir,true))
			{
#ifdef			CHECKED_BUILD
				conlPrintf(LOG_ERROR,"lfsCreateFile('%s'): unable to "
					"create or set directory '%s'.\n",name,dir);
#endif			/* CHECKED_BUILD */
				return LIBV_INVALID_HANDLE;
			}
			lfsSetDir(curdir,false);
		}
	}
#ifndef	RIGHT_SLASHED
	/* We are going to call NT; convert unix name to dos name */
	os_name = os_name_buf;
   	vfsUnix2Native(os_name,name);
#endif	/* RIGHT_SLASHED */
	if(strcasecmp(os_name,"CON") && strcmp(os_name,"/dev/console"))
    {
		handle=fopen(os_name,fmode);
		if(!handle)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"lfsCreateFile(): fopen('%s','%c') failed.\n",
				os_name,fmode[0]);
#endif		/* CHECKED_BUILD */
			return LIBV_INVALID_HANDLE;
		}
	}
	else
    {
    	if(mode == filemode_read)
	    	handle=stdin;
		else
        	handle=stdout;
	}
	f=(lfs_file_t*)ioAllocPort(sizeof(lfs_file_t));
	f->fhandle = handle;
	strCopy(f->fname,name,_MAX_PATH);
	f->fmode = mode;
	if(mode==filemode_read)
	{
		f->vtbl.read=lfs_read;
		fseek(handle,0,SEEK_END);
		f->fsize=(u32_t)ftell(handle);
		rewind(handle);
		if(f->fsize!=(u32_t)-1)
		{
			/* block device */
			f->vtbl.seek=lfs_seek;
			f->vtbl.tell=lfs_tell;
		}
	}
	else
	{
		f->vtbl.write=lfs_write;
		f->vtbl.seek=lfs_seek;
		f->vtbl.tell=lfs_tell;
		f->fsize=0;
	}
	f->vtbl.base.destroy=lfs_destroy;
	f->vtbl.ioctl=lfs_ioctl;
	return (file_t)f;
}

#ifdef	CHECKED_BUILD

/*
 *	Quiet version of lfsCreateFile()
 */
file_t	V_CALL
lfsCreateFileQuiet(char *name,filemode_t mode)
{
	u32_t ll = logUpperLevel(LOG_MUTE);
	file_t file = lfsCreateFile(name,mode);
    logUpperLevel(ll);
    return file;
}

#endif	/* CHECKED_BUILD */
