#include <../arch/arch.h>
#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>
#include <../source/vfs/fsops.h>
#include <../source/vfs/iobase.h>
#include <../source/vfs/mbase.h>
#include <../source/vfs/fsbase.h>
#include <../source/vfs/fbase.h>
#include <../source/vfs/fbase.h>
#include <../source/vfs/fswrap.h>

/* TODO convert to use vsize_t, vssize_t */

vhandle_t	V_CALL
libvOpenRead(char *name)
{
	if(archFileExists(name))
    	return lfsCreateFile(name,file_mode_read);
	else
		return vfsCreateFile(name);
}

vhandle_t	V_GLOBAL
fileOpenRead(char *name)
{
	vhandle_t in = libvOpenRead(name);
#ifdef	CONFIG_COMPRESSION_ZLIB
	if(in == LIBV_INVALID_HANDLE)
	{
		char gz_name[_MAX_PATH];
		strCopy(gz_name,name,_MAX_PATH);
		strCat(gz_name,".gz",_MAX_PATH - strlen(gz_name) - 1);
		in = libvOpenRead(gz_name);
		if(in != LIBV_INVALID_HANDLE)
		{
			vhandle_t decompress = gzConvertToDecompress(in);
			if(decompress != LIBV_INVALID_HANDLE)
				return decompress;
			fileClose(in);
			in = LIBV_INVALID_HANDLE;
		}
	}
#endif	/* CONFIG_COMPRESSION_ZLIB */
	return in;
}

void	V_CALL
vfsReplaceExt(char *dst,char *name,char *ext)
{
	char fname[_MAX_PATH];
	vfsSplitName(name,fname,NULL);
    vfsMakePath(dst,NULL,fname,ext);
}

void	V_GLOBAL
fsEnumFiles(void (V_CALL *dir_callback)(char *full_unix_style_dir_name),
	void (V_CALL *file_callback)(char *full_unix_style_file_name),
	vboolean recursive)
{
	lfsEnumFiles(dir_callback,file_callback,recursive);
}

void	V_GLOBAL
fsSplitPath(char *src,char *dir,char *name,char *ext)
{
	vfsSplitPath(src,dir,name,ext);
}

void	V_GLOBAL
fsMakePath(char *dest_path,char *dir,char *name,char *ext)
{
	vfsMakePath(dest_path,dir,name,ext);
}

void	V_GLOBAL
fsReplaceExt(char *dst,char *name,char *ext)
{
	char fname[_MAX_PATH];
	vfsSplitName(name,fname,NULL);
    vfsMakePath(dst,NULL,fname,ext);
}

vhandle_t	V_GLOBAL
fileOpenWrite(char *name)
{
	return lfsCreateFile(name,file_mode_write);
}

vhandle_t	V_GLOBAL
fileOpenAppend(char *name)
{
	return lfsCreateFile(name,file_mode_append);
}

#define		COPY_BUF_LEN		(1024 * 512)

/*
 * Bugs: of !overwite, dst_name must _not_ be LibV device name.
 */
vboolean	V_GLOBAL
fileCopy(char *dst_name,char *src_name,vboolean overwrite)
{
	CALLER_NAME(fileCopy)
	u8_t buf[COPY_BUF_LEN];
	vsize_t len, count, rest, rdc, wrc;
	vhandle_t in ,out;
	if(!dst_name || !*dst_name || !src_name || !*src_name)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	if(!archFileExists(src_name))
	{
		ERROR_GENERIC_2ARG("file not exists: ",src_name);
		return false;
	}
	if(archFileExists(dst_name))
	{
		if(!overwrite) /* fixme: it does not handle libV device names ! */
		{
			ERROR_GENERIC_2ARG("file already exists: ",dst_name);
			return false;
		}
	}
	in = fileOpenRead(src_name);
	if(in == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC_2ARG("unable to read file: ",src_name);
		return false;
	}
	out = fileOpenWrite(dst_name);
	if(out == LIBV_INVALID_HANDLE)
	{
		ERROR_GENERIC_2ARG("unable to write file: ",dst_name);
		fileClose(in);
		return false;
	}
	rdc = wrc = 0;
	len = fileSize(in);
	count = len / COPY_BUF_LEN;
	rest = len - count * COPY_BUF_LEN;
	while(count)
	{
		rdc += fileRead(in,buf,COPY_BUF_LEN);
		wrc += fileWrite(out,buf,COPY_BUF_LEN);
		count--;
	}
	if(rest)
	{
		rdc += fileRead(in,buf,rest);
		wrc += fileWrite(out,buf,rest);
	}
	fileClose(in);
	fileClose(out);
	if(rdc == wrc && rdc == len)
		return true;
#ifdef	CHECKED_BUILD
	if(rdc != len)
		ERROR_GENERIC_2ARG("error reading: ",src_name);
	if(wrc != len)
		ERROR_GENERIC_2ARG("error writing: ",dst_name);
#endif	/* CHECKED_BUILD */
	return false;
}

void	V_GLOBAL
fsInit(void)
{
	vfsInit();
}

void	V_GLOBAL
fsShutdown()
{
	vfsShutdown();
}

char_t	V_GLOBAL
fileGetc(vhandle_t file)
{
	char_t val;
	fileRead(file,&val,1);
	return val;
}

vboolean V_GLOBAL
filePutc(vhandle_t file,int c)
{
	u8_t ch = (u8_t)c;
	return fileWrite(file,&ch,1) ? true : false;
}

vboolean V_GLOBAL
filePuts(vhandle_t file,char *str)
{
	CALLER_NAME(filePuts)
	int len, wrlen;
	if(!str)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	len = strlen(str);
	wrlen = fileWrite(file,str,len);
	if(filePutc(file,0x0A))
		wrlen++;
	return ((len + 1) == wrlen) ? true : false;
}

/*
 * filePrintf
 */
vboolean	V_GLOBAL_VA
filePrintf(vhandle_t file,const char *fmt,...)
{
	CALLER_NAME(filePrintf)
	va_list marker;
	char *ptr;
	char fmt_buffer[MAX_FORMAT_BUFFER];
	u32_t len, wrlen;
	if(!fmt)
	{
		ERROR_GENERIC(error_incorrect_parameters);
		return false;
	}
	ptr=fmt_buffer;
	va_start(marker,fmt);
#ifdef	CONFIG_HAVE_SNPRINTF
	len = vsnprintf(ptr,MAX_FORMAT_BUFFER,fmt,marker);
#else
	len = vsprintf(ptr,fmt,marker);
#endif
	va_end(marker);
    if(len >= MAX_FORMAT_BUFFER)
    {
    	ERROR_GENERIC("too many args; heap destoyed");
		return false;
    }
	wrlen = fileWrite(file,ptr,len);
	return (len == wrlen) ? true : false;
}

/*
 *	Returns word stored in file in little-endian order.
 */
u16_t	V_GLOBAL
fileGetLittleShort(vhandle_t file)
{
	u16_t w;
	fileRead(file,&w,2);
#if __BYTE_ORDER == __BIG_ENDIAN
	w = LE2ME_SHORT(w);
#endif	/* __BIG_ENDIAN */
	return w;
}

/*
 *	Stores word in file in little-endian order.
 */
void	V_GLOBAL
filePutLittleShort(vhandle_t file,u16_t w)
{
#if __BYTE_ORDER == __BIG_ENDIAN
	w = ME2LE_SHORT(w);
#endif	/* __BIG_ENDIAN */
	fileWrite(file,&w,2);
}

/*
 *	Returns double word stored in file in little-endian order.
 */
u32_t	V_GLOBAL
fileGetLittleLong(vhandle_t file)
{
	u32_t dw;
	fileRead(file,&dw,4);
#if __BYTE_ORDER == __BIG_ENDIAN
	dw = LE2ME_LONG(dw);
#endif	/* __BIG_ENDIAN */
	return dw;
}

/*
 *	Stores dword in little-endian order.
 */
void	V_GLOBAL
filePutLittleLong(vhandle_t file,u32_t dw)
{
#if __BYTE_ORDER == __BIG_ENDIAN
	dw = ME2LE_LONG(dw);
#endif	/* __BIG_ENDIAN */
	fileWrite(file,&dw,4);
}

#ifdef	CONFIG_COMPRESSION_ZLIB

/*
 *	In - memory decompression and wrapper for gzip archives
 *
 *	Note: this code based on 'gzio.c'.
 *	gzio.c: Copyright (C) 1995-1998 Jean-loup Gailly.
 *	For conditions of distribution and use, see copyright notice in zlib.h
 */

#include <../support/zlib/src/zlib.h>
#include <../support/zlib/src/zutil.h>

#ifndef Z_BUFSIZE
#define Z_BUFSIZE	0xFFFF
#endif

V_LOCAL	int gz_magic[2] = { 0x1f, 0x8b }; /* gzip magic header */

/* gzip flag byte */
#define ASCII_FLAG   0x01       /* bit 0 set: file probably ascii text */
#define HEAD_CRC     0x02       /* bit 1 set: header CRC present */
#define EXTRA_FIELD  0x04       /* bit 2 set: extra field present */
#define ORIG_NAME    0x08       /* bit 3 set: original file name present */
#define COMMENT      0x10       /* bit 4 set: file comment present */
#define RESERVED     0xE0       /* bits 5..7: reserved */

#include <../source/PAL/argsused.h>
voidpf zcalloc(voidpf opaque,unsigned items,unsigned size)
{
	voidpf block;
	block = memAlloc(items * size);
	memZero(block,items * size);
	return block;
}

#include <../source/PAL/argsused.h>
void  zcfree(voidpf opaque,voidpf ptr)
{
	memFree(ptr);
}

typedef struct gz_decompress_struct_s
{
	z_stream	stream;
	vhandle_t		infile;				/* .gz file */
	Byte 		outbuf[Z_BUFSIZE];	/* output buffer */
	u8_stack_t	data;
	u32_t		crc;
	int			err;
}	gz_decompress_struct;

V_LOCAL Byte get_byte(gz_decompress_struct *s)
{
	if(s->stream.avail_in)
	{
		--s->stream.avail_in;
		return *(s->stream.next_in)++;
	}
	s->err = Z_DATA_ERROR;
	return 0;
}

/*
 *	Flushes all pending output if necessary
 */
V_LOCAL	void_p	gz_finish(gz_decompress_struct *s,vboolean keepdata)
{
	if(s->stream.state != NULL)
	{
		inflateEnd(&(s->stream));
		s->stream.state = NULL;
	}
	if(keepdata)
	{
		if(s->infile != LIBV_INVALID_HANDLE)
		{
			fileClose(s->infile);
			s->infile = LIBV_INVALID_HANDLE;
		}
		u8sTrim(&(s->data));
		return u8sGetStorage(&(s->data));
	}
	u8sClear(&(s->data));
	return NULL;
}

V_LOCAL u32_t	V_CALL
gzport_destroy(vhandle_t handle)
{
	io_section_p port = (io_section_p)handle;
	UNREF_EXIT(&port->vtbl);
	/*
	 * Free memory previously allocated by u8sPushBuffer()'s
	 */
	memFree(port->base);
	memFreeObject(port);
	return 0;
}

/*
 * gzConvertToDecompress() based on zlib routines.
 * Authors of zlib: Jean-loup Gailly and Mark Adler.
 */
vhandle_t	V_GLOBAL
gzConvertToDecompress(vhandle_t in)
{
	CALLER_NAME(gzConvertToDecompress)
#define	MAX_DECOMPRESSED_LEN		(16 * 1024 * 1024)
	int c;
	int err;
	int method;                   /* method byte */
	int flags;                    /* flags byte */
	uInt len;
	Byte *out_head;
	io_section_p port;
	gz_decompress_struct gz_decompress, *s = &gz_decompress;
	memZero(s,sizeof(gz_decompress));
	s->stream.zalloc = zcalloc;
	s->stream.zfree = zcfree;
	s->stream.opaque = NULL;
	s->stream.next_out = s->outbuf;
	s->stream.avail_out = Z_BUFSIZE;
	s->crc = crc32 (0L, Z_NULL, 0);
	/*
	 * mmap input file (or load it into memory) 
	 */
	s->infile=in;
	if(!fileControl(s->infile,pctl_map_ispace,(void**)&(s->stream.next_in)))
	{
		ERROR_GENERIC("unable to mmap() input file");
		gz_finish(s,false);
		return LIBV_INVALID_HANDLE;
	}
	s->stream.avail_in = fileSize(s->infile);
	/*
	 * Read in gzip header
	 */
	if(gz_magic[0] != get_byte(s) || gz_magic[1] != get_byte(s))
	{
		ERROR_GENERIC("bad archive, magic wrong");
		gz_finish(s,false);
		return LIBV_INVALID_HANDLE;
	}
	method = get_byte (s);
	flags = get_byte (s);
	if(method != Z_DEFLATED || flags & RESERVED)
	{
		ERROR_GENERIC("unknown compression method");
		gz_finish(s,false);
		return LIBV_INVALID_HANDLE;
	}
	/* Discard time, xflags and OS code: */
	for(len = 0; len < 6; len++)
		get_byte(s);
	if(flags & EXTRA_FIELD)
	{
		/* skip the extra field */
		len = (uInt) get_byte (s);
		len += ((uInt) get_byte (s)) << 8;
		/* len is garbage if EOF but the loop below will quit anyway */
		while(len--)
			get_byte(s);
	}
	if(flags & ORIG_NAME)
	{
		/* skip the original file name */
		for(c = get_byte(s);c;c = get_byte(s));
	}
	if(flags & COMMENT)
	{
		/* skip the .gz file comment */
		for(c = get_byte(s);c;c = get_byte(s));
	}
	if(flags & HEAD_CRC)
	{
		/* skip the header crc */
		get_byte(s);
		get_byte(s);
	}
	if(s->err == Z_DATA_ERROR)
	{
		ERROR_GENERIC("unable to read gzip header");
		gz_finish(s,false);
		return LIBV_INVALID_HANDLE;
	}
	err = inflateInit2 (&(s->stream), -MAX_WBITS);
	/*
	 * windowBits is passed < 0 to tell that there is no zlib header.
	 * Note that in this case inflate *requires* an extra "dummy" byte
	 * after the compressed stream in order to complete decompression and
	 * return Z_STREAM_END. Here the gzip CRC32 ensures that 4 bytes are
	 * present after the compressed stream.
	 */
	if(err != Z_OK)
	{
		ERROR_GENERIC("inflateInit2() failed");
		gz_finish(s,false);
		return LIBV_INVALID_HANDLE;
	}
	/*
	 *	Decompress all data
	 */
	while(s->err != Z_STREAM_END)
	{
		len = s->stream.total_out;
		out_head = s->stream.next_out;
		s->err = inflate(&(s->stream),Z_NO_FLUSH);
		if(s->err == Z_DATA_ERROR)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"%s(): inflate() failed after decompressing %u bytes.\n",
				caller_name,s->stream.total_out);
#endif		/* CHECKED_BUILD */
			gz_finish(s,false);
			return LIBV_INVALID_HANDLE;
		}
		len = s->stream.total_out - len;
		if(len)
		{
			if((u8sCount(&(s->data))+len) > MAX_DECOMPRESSED_LEN)
			{
				ERROR_GENERIC("archive loo big to decompress in memory");
				gz_finish(s,false);
				return LIBV_INVALID_HANDLE;
			}
			u8sPushBuffer(&(s->data),out_head,len);
		}
		if(!s->stream.avail_out)
		{
			s->stream.avail_out = Z_BUFSIZE;
			s->stream.next_out = s->outbuf;
		}
	}
	/*
	 * FIXME ???
	 */
	/*
	garbage with small .gz files after inflate(...,Z_FINISH) !
	inflateReset(&(s->stream));
	*/
	len = s->stream.total_out;
	out_head = s->stream.next_out;
	s->err = inflate(&(s->stream),Z_FINISH);
	len = s->stream.total_out - len;
	if(len)
		u8sPushBuffer(&(s->data),out_head,len);
	/* Free unused memory, create section */
	port = ioAllocSection(sizeof(io_section_t),
		gz_finish(s,true),u8sCount(&(s->data)));
	port->vtbl.base.destroy = gzport_destroy;
	return port;
}
	
#endif	/* CONFIG_COMPRESSION_ZLIB */
