#include <../arch/arch.h>
#include <../source/vfs/iobase.h>
#include <../source/vfs/fbase.h>
#include <../source/vfs/mbase.h>

#define	READ_CACHE_SIZE			4096
#define	WRITE_CACHE_SIZE		4096

/* TODO convert to use vsize_t, vssize_t */

typedef struct	lfs_file_s
{
	ioport_base_t	vtbl;
	char			fname[_MAX_PATH];
	file_mode_t		fmode;
	file_type_t		ftype;
	vsize_t			fsize;
	vsize_t			transferred; /* todo */
	arch_file_t		fhandle;
	u8_fifo_t		cache;
	u8_t			*base;
}	lfs_file_t, *lfs_file_p;

#define	cache_fill(total,cache,fd) { \
		vsize_t l1, l2, nb = 0; \
		RBF_FREE_CHAIN_LENS(&(cache),l1,l2); \
		if(l1) nb += archFileRead(fd,RBF_FREE_CHAIN1(&(cache)),l1); \
		if(l2) nb += archFileRead(fd,RBF_FREE_CHAIN2(&(cache)),l2); \
		(total) += nb; \
		RBF_MOVE_HEAD(&(cache),nb); \
	}

#define	cache_flush(total,cache,fd) { \
		vsize_t l1, l2, nb = 0; \
		RBF_CHAIN_LENS(&(cache),l1,l2); \
		if(l1) nb += archFileWrite(fd,RBF_CHAIN1(&(cache)),l1); \
		if(l2) nb += archFileWrite(fd,RBF_CHAIN2(&(cache)),l2); \
		(total) += nb; \
		RBF_MOVE_TAIL(&(cache),nb); \
	}

#ifdef	ARCH_MAPPED_IO_SUPPORTED

typedef struct	vfs_mapped_file_s
{
	io_section_t	port;
	arch_file_t		fhandle;
	struct arch_filemap_s map;
}	vfs_mapped_file_t, *vfs_mapped_file_p;

#endif	/* ARCH_MAPPED_IO_SUPPORTED */

V_LOCAL void
lfs_flush_file_buffers(lfs_file_t *self)
{
	if(self->fmode != file_mode_read)
	{
		cache_flush(self->transferred,self->cache,self->fhandle);
		archFlushFileBuffers(self->fhandle);
	}
}

V_LOCAL vsize_t V_CALL
lfs_read(vhandle_t handle,void *buf,vsize_t len)
{
	vsize_t nb, cached;
	u8_t *ptr = (u8_t*)buf;
	lfs_file_t *self = (lfs_file_t*)handle;
	cached = RBF_LEN(&(self->cache));
	if(cached >= len)
	{
		if(len == 1)
		{
			RBF_POP_NOCHECK(&(self->cache),ptr)
			return 1;
		}
		rbfPopBuffer(&(self->cache),ptr,len);
		return len;
	}
	nb = rbfPopBuffer(&(self->cache),ptr,len);
	ptr += nb;
	len -= nb;
	if(len > READ_CACHE_SIZE)
	{
		nb += archFileRead(self->fhandle,ptr,len);
		self->transferred += nb;
		return nb;
	}
	cache_fill(self->transferred,self->cache,self->fhandle);
	nb += rbfPopBuffer(&(self->cache),ptr,len);
	return nb;
}

V_LOCAL vsize_t V_CALL
lfs_write(vhandle_t handle,void *buf,vsize_t len)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	if(len > self->cache.space_avail)
		cache_flush(self->transferred,self->cache,self->fhandle);
	if(len > self->cache.space_avail)
	{
		vsize_t nb = archFileWrite(self->fhandle,buf,len);
		self->transferred += nb;
		return nb;
	}
	else
	{
		rbfPushBuffer(&(self->cache),(u8_t*)buf,len);
		return len;
	}
}

V_LOCAL vsize_t V_CALL
lfs_tell(vhandle_t handle)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	if(self->fmode == file_mode_read)
		return archGetFilePointer(self->fhandle) - rbfCount(&self->cache);
	return archGetFilePointer(self->fhandle) + rbfCount(&self->cache);
}

V_LOCAL vsize_t V_CALL
lfs_seek(vhandle_t handle,vssize_t offset,int whence)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	if(self->fmode == file_mode_read)
	{
		/*
		 * Invalidate read cache
		 */
		vsize_t cached = rbfCount(&self->cache);
		if(whence == SEEK_CUR)
			offset -= cached;
		RBF_SET_EMPTY(&self->cache);
	}
	else
	{
		/*
		 * Flush write cache
		 */
		lfs_flush_file_buffers(self);
	}
	archSetFilePointer(self->fhandle,offset,whence);
	self->transferred = archGetFilePointer(self->fhandle);
	return self->transferred;
}

V_LOCAL u32_t V_CALL
lfs_destroy(vhandle_t handle)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	UNREF_EXIT(&self->vtbl);
	lfs_flush_file_buffers(self);
	rbfClear(&(self->cache));
	archFileClose(self->fhandle);
	if(self->base != NULL) /* it is NOT a mmap()'ed block */
		memFree(self->base);
	memFreeObject(self);
	return 0;
}

/*
 * Helper routine to read entire file into memory.
 * Preserves current file pointer (NOT for character devices).
 * Returns memAlloc()'ed block on success, NULL on error.
 */
#define		MAX_BCACHE_SIZE			(1024*1024*16)

V_LOCAL u8_p
lfs_cache_all_data(lfs_file_t *self)
{
	u32_t pos;
	u8_t *blk_buf;
	if(self->ftype == file_type_chr_dev)
	{
		/*
		 * Must NOT be large (allocated on stack)
		 */
#define	CHR_DEV_READ_BUF_SIZE		1024
		u8_t buf[CHR_DEV_READ_BUF_SIZE];
		u32_t len;
		u8_stack_t storage;
		if(self->fmode == file_mode_read)
			return NULL;
		u8sInit(&storage);
		while(u8sCount(&storage) < MAX_BCACHE_SIZE)
		{
			len = archFileRead(self->fhandle,buf,CHR_DEV_READ_BUF_SIZE);
			if(!len)
				break;
			u8sPushBuffer(&storage,buf,len);
		}
		if(!u8sCount(&storage))
			return NULL;
#ifdef	CHECKED_BUILD
		if(u8sCount(&storage) > MAX_BCACHE_SIZE)
			conlPuts(LOG_WARNING,
				"lfs_cache_all_data() read more than MAX_BCACHE_SIZE "
				"from character device.\n");
#endif	/* CHECKED_BUILD */
		u8sTrim(&storage);
		return u8sGetStorage(&storage);
	}
	/*
	 * Block device / regular file
	 */
	if(self->fsize > MAX_BCACHE_SIZE)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"lfs_cache_all_data(): file size > MAX_BCACHE_SIZE.\n");
#endif	/* CHECKED_BUILD */
		return NULL;
	}
	pos = archGetFilePointer(self->fhandle);
	blk_buf = (u8_t*)memAlloc(self->fsize);
	if(blk_buf == NULL)
		return NULL;
	archSetFilePointer(self->fhandle,0,SEEK_SET);
	archFileRead(self->fhandle,blk_buf,self->fsize);
	archSetFilePointer(self->fhandle,pos,SEEK_SET);
	return blk_buf;
}

V_LOCAL	int V_CALL
lfs_ioctl(vhandle_t handle,unsigned code,void *parameters)
{
	lfs_file_t *self = (lfs_file_t*)handle;
	switch(code)
	{
		case pctl_get_name:
			if(parameters)
			{
				strCopy((char*)parameters,self->fname,_MAX_PATH);
				return 0;
			}
			return -1;
		case pctl_get_type:
			if(!parameters)
				return -1;
			if(self->ftype == file_type_reg 
				|| self->ftype == file_type_blk_dev)
			   	*((u32_t*)parameters) = PCTL_BLOCK_DEVICE;
			else
			   	*((u32_t*)parameters)=0;
			return 0;
		case pctl_map_ispace:
			if(!parameters)
				return -1;
#ifdef	ARCH_MAPPED_IO_SUPPORTED
			/*
			 * FIXME: what should we do if mmap() requested on special file ?
			 */
#endif	/* ARCH_MAPPED_IO_SUPPORTED */
			if(!self->base)
				self->base = lfs_cache_all_data(self);
			*((u8_t**)parameters) = self->base;
			return (self->base != NULL) ? 0 : -1;
		case pctl_duplicate_handle:
		{
			vhandle_t newhandle;
			if(!parameters)
				return -1;
			if(self->fmode != file_mode_read)
				return -1;
			newhandle = lfsCreateFile(self->fname,file_mode_read);
			if(newhandle == LIBV_INVALID_HANDLE)
				return -1;
			*((vhandle_t*)parameters) = newhandle;
			return 0;
		}
		case pctl_get_size:
			if(!parameters)
				return -1;
			if(self->fmode == file_mode_write)
			{
				*((vsize_t*)parameters) = 
					self->transferred + rbfCount(&(self->cache));
			}
			else
				*((vsize_t*)parameters) = (vsize_t)(self->fsize);
			return 0;
		case pctl_is_eof:
			if(!parameters)
				return -1;
			if(self->fmode == file_mode_write)
			{
				*((vboolean*)parameters) = true;
				return 0;
			}
			if(self->ftype == file_type_reg)
			{
				vsize_t total = self->transferred - rbfCount(&(self->cache));
				*((vboolean*)parameters) = (total == self->fsize) ? true : false;
				return 0;
			}
			if(self->ftype == file_type_chr_dev)
			{
				*((vboolean*)parameters) = false; /* no eof for RO chardevs */
				return 0;
			}
			/* Unknown type of file */
			return -1;
		case pctl_flush:
			lfs_flush_file_buffers(self);
			return 0;
		default:
			break;
	}
	return -1;
}

#ifdef	ARCH_MAPPED_IO_SUPPORTED

V_LOCAL u32_t V_CALL
lfs_destroy_mapped(vhandle_t handle)
{
	vfs_mapped_file_t *self = (vfs_mapped_file_t*)handle;
	UNREF_EXIT(&self->port.vtbl);
	archUnmap(&self->map);
	archFileClose(self->fhandle);
	memFreeObject(self);
	return 0;
}

/*
 * Factory function for memory-mapped files.
 */
vhandle_t	V_LOCAL
lfsMapFileReadOnly(char *file_name,arch_file_t fhandle)
{
	void_p base;
	vsize_t len;
	vfs_mapped_file_p port;
	struct arch_filemap_s map;
	len = archGetFileSize(fhandle);
	base = archMapRead(fhandle,0,len,&map);
	if(base == NULL)
		return LIBV_INVALID_HANDLE;
	port = (vfs_mapped_file_p)ioAllocNamedSection(
		sizeof(vfs_mapped_file_t),base,len,file_name);
	port->port.vtbl.base.destroy = lfs_destroy_mapped;
	port->fhandle = fhandle;
	memCopy(&port->map,&map,sizeof(struct arch_filemap_s));
	return port;
}

#endif	/* ARCH_MAPPED_IO_SUPPORTED */

/*
 * Creates IO port for disk file.
 */
vhandle_t	V_CALL
lfsCreateFile(char *name,file_mode_t mode)
{
#ifdef	ARCH_MAPPED_IO_SUPPORTED
	vhandle_t mapped;
#endif	/* ARCH_MAPPED_IO_SUPPORTED */
	arch_file_t fhandle;
	file_type_t ftype;
	vsize_t fsize;
	lfs_file_p f;
	if(!name)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
			"lfsCreateFile(NULL): incorrect name.\n");
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	if(mode != file_mode_read)
	{
		char dir[_MAX_PATH];
		/* Check for target directory */
		fsSplitPath(name,dir,NULL,NULL);
		if(dir[0])
		{
			char curdir[_MAX_PATH];
			lfsGetDir(curdir,_MAX_DIR);
			if(!lfsSetDir(dir,true))
			{
#ifdef	CHECKED_BUILD
				conlPrintf(LOG_ERROR,"lfsCreateFile('%s'): unable to "
					"create/set directory '%s'.\n",name,dir);
#endif	/* CHECKED_BUILD */
				return LIBV_INVALID_HANDLE;
			}
			lfsSetDir(curdir,false);
		}
	}
	if(!strcmp(name,"CON"))
	{
		fhandle = archOpenConsole(mode);
		ftype = file_type_chr_dev;
	}
	else
		fhandle = archFileOpen(name,mode,&ftype);
	if(fhandle == ARCH_INVALID_FILE)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"lfsCreateFile('%s'): archFileOpen() failed.\n",name);
#endif	/* CHECKED_BUILD */
		return LIBV_INVALID_HANDLE;
	}
	fsize = archGetFileSize(fhandle);
#ifdef	ARCH_MAPPED_IO_SUPPORTED
	if(mode == file_mode_read && ftype == file_type_reg && fsize)
	{
		/* Try to map file */
		mapped = lfsMapFileReadOnly(name,fhandle);
		if(mapped != LIBV_INVALID_HANDLE)
			return mapped;
	}
#endif	/* ARCH_MAPPED_IO_SUPPORTED */
	f = (lfs_file_t*)ioAllocPort(sizeof(lfs_file_t));
	f->fhandle = fhandle;
	strCopy(f->fname,name,_MAX_PATH);
	f->fmode = mode;
	f->ftype = ftype;
	if(mode == file_mode_read)
	{
		rbfInit(&f->cache,READ_CACHE_SIZE);
		f->vtbl.read = lfs_read;
		f->fsize = fsize;
		if(ftype != file_type_chr_dev)
		{
			/* block device */
			f->vtbl.seek = lfs_seek;
			f->vtbl.tell = lfs_tell;
		}
	}
	else
	{
		rbfInit(&f->cache,WRITE_CACHE_SIZE);
		f->vtbl.write = lfs_write;
		f->fsize = 0;
		if(ftype != file_type_chr_dev)
		{
			f->vtbl.seek = lfs_seek;
			f->vtbl.tell = lfs_tell;
		}
	}
	f->vtbl.base.destroy = lfs_destroy;
	f->vtbl.ioctl = lfs_ioctl;
	return (vhandle_t)f;
}
