#include <../source/TAL/objbase.h>
#include <../source/PAL/dataman.h>
#include <../source/PAL/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>

vhandle_t	V_CALL
libvOpenRead(char *unix_file_name)
{
	vhandle_t ret;
    ret = lfsCreateFileQuiet(unix_file_name,filemode_read);
	if(ret)
		return ret;
	return vfsCreateFile(unix_file_name);
}

file_t	V_CALL
#ifndef	RIGHT_SLASHED
ufileOpenRead(char *name)
#else	/* RIGHT_SLASHED */
fileOpenRead(char *name)
#endif	/* RIGHT_SLASHED */
{
	file_t in = libvOpenRead(name);
#ifdef	CONFIG_COMPRESSION_ZLIB
	if(in == NULL)
	{
		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 != NULL)
		{
			file_t decompress = gzConvertToDecompress(in);
			if(decompress != NULL)
				return decompress;
			fileClose(in);
		}
	}
#endif	/* CONFIG_COMPRESSION_ZLIB */
	return in;
}

#ifndef	RIGHT_SLASHED

/*
 *	We implement two versions of IO functions: UNIX-style and NT-style.
 */
 
void	V_CALL
uvfsReplaceExt(char *dst,char *name,char *ext)
{
	char fname[_MAX_PATH];
	vfsSplitName(name,fname,NULL);
    vfsMakePath(dst,NULL,fname,ext);
}

void	V_CALL
nvfsReplaceExt(char *dst,char *name,char *ext)
{
	char uname[_MAX_PATH];
    vfsNative2Unix(uname,name);
	uvfsReplaceExt(dst,uname,ext);
    if(name)
	    vfsUnix2Native(name,uname);
}

void	V_CALL
nvfsSplitPath(char *src,char *dir,char *name,char *ext)
{
	char uname[_MAX_PATH];
    vfsNative2Unix(uname,src);
	vfsSplitPath(uname,dir,name,ext);
    /* convert parts back to DOS style */
    if(dir)
    {
        vfsUnix2Native(uname,dir);
        strCopy(dir,uname,_MAX_DIR);
	}
    if(name)
    {
        vfsUnix2Native(uname,name);
        strCopy(name,uname,_MAX_FNAME);
    }
    if(ext)
    {
        vfsUnix2Native(uname,ext);
        strCopy(ext,uname,_MAX_EXT);
	}
}

void	V_CALL
nvfsMakePath(char *dest_path,char *dir,char *name,char *ext)
{
	char uname[_MAX_PATH];
    char ud[_MAX_DIR], un[_MAX_FNAME], ue[_MAX_EXT];
    /* convert parts back to UNIX style */
    if(dir)
    {
        vfsNative2Unix(ud,dir);
        dir=ud;
	}
    if(name)
    {
        vfsNative2Unix(un,name);
        name=un;
    }
    if(ext)
    {
        vfsNative2Unix(ue,ext);
        ext=ue;
	}
	vfsMakePath(uname,dir,name,ext);
    vfsUnix2Native(dest_path,uname);
}

file_t	V_CALL
nfileOpenRead(char *name)
{
	char uname[_MAX_PATH];
    vfsNative2Unix(uname,name);
    return ufileOpenRead(uname);
}

file_t	V_CALL
ufileOpenWrite(char *name)
{
	return lfsCreateFile(name,filemode_write);
}

file_t	V_CALL
nfileOpenWrite(char *name)
{
	char uname[_MAX_PATH];
    vfsNative2Unix(uname,name);
	return lfsCreateFile(uname,filemode_write);
}

file_t	V_CALL
ufileOpenAppend(char *name)
{
	return lfsCreateFile(name,filemode_append);
}

file_t	V_CALL
nfileOpenAppend(char *name)
{
	char uname[_MAX_PATH];
    vfsNative2Unix(uname,name);
	return lfsCreateFile(uname,filemode_append);
}

vboolean	V_GLOBAL
fsNative2Unix(char *unix_name,char *dos_name)
{
	return vfsNative2Unix(unix_name,dos_name);
}

vboolean	V_GLOBAL
fsUnix2Native(char *dos_name,char *unix_name)
{
	return vfsUnix2Native(dos_name,unix_name);
}

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)
{
	GetThreadData()->file_apis->fsEnumFiles(dir_callback,
    	file_callback,recursive);
}

void	V_GLOBAL
fsSplitPath(char *src,char *dir,char *name,char *ext)
{
	GetThreadData()->file_apis->fsSplitPath(src,dir,name,ext);
}

void	V_GLOBAL
fsMakePath(char *dest_path,char *dir,char *name,char *ext)
{
	GetThreadData()->file_apis->fsMakePath(dest_path,
    	dir,name,ext);
}

void	V_GLOBAL
fsReplaceExt(char *dst,char *name,char *ext)
{
	GetThreadData()->file_apis->fsReplaceExt(dst,name,ext);
}

file_t	V_GLOBAL
fileOpenRead(char *name)
{
	return GetThreadData()->file_apis->fileOpenRead(name);
}

file_t	V_GLOBAL
fileOpenWrite(char *name)
{
	return GetThreadData()->file_apis->fileOpenWrite(name);
}

file_t	V_GLOBAL
fileOpenAppend(char *name)
{
	return GetThreadData()->file_apis->fileOpenAppend(name);
}

file_apis_t	unix_file_apis=
{
	lfsEnumFiles,
    vfsSplitPath,
    vfsMakePath,
    uvfsReplaceExt,
    ufileOpenRead,
    ufileOpenWrite,
    ufileOpenAppend
};

file_apis_t	native_file_apis=
{
	lfsEnumFiles,
    nvfsSplitPath,
    nvfsMakePath,
    nvfsReplaceExt,
    nfileOpenRead,
    nfileOpenWrite,
    nfileOpenAppend
};

vboolean	V_GLOBAL
fsAreApisUnix(void)
{
	if(GetThreadData()->file_apis==&unix_file_apis)
    	return true;
	else
    	return false;
}

void		V_GLOBAL
fsSetApisUnix(void)
{
	GetThreadData()->file_apis=&unix_file_apis;
}

void		V_GLOBAL
fsSetApisNative(void)
{
	GetThreadData()->file_apis=&native_file_apis;
}

#else	/* RIGHT_SLASHED */

/*
 *	OS uses same path format as LibV.
 */
 
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);
}

file_t	V_GLOBAL
fileOpenWrite(char *name)
{
	return lfsCreateFile(name,filemode_write);
}

file_t	V_GLOBAL
fileOpenAppend(char *name)
{
	return lfsCreateFile(name,filemode_append);
}

#endif	/* RIGHT_SLASHED */

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

void	V_GLOBAL
fsShutdown()
{
	vfsShutdown();
}

vboolean	V_GLOBAL
fileEof(file_t file)
{
	return fileTell(file)==fileSize(file);
}

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

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

vboolean V_GLOBAL
filePuts(file_t file,char *str)
{
	int len, wrlen;
	if(!str)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"filePuts(NULL): invalid parameter.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	len=strlen(str);
	wrlen=fileWrite(file,str,len) + (int)filePutc(file,0x0A);
	return ((len+1)==wrlen) ? true : false;
}

/*
 *	Try NOT to use it; use scanners instead.
 */
int		V_GLOBAL
fileGets(file_t file,char *buf,int n)
{
	char c;
	char *next;
	if(!buf || !n)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"fileGets(%X,%X,%d): invalid parameter(s).\n",file,buf,n);
#endif	/* CHECKED_BUILD */
		return 0;
	}
	for(;;)
	{
		c=(char)fileGetc(file);
		if(c!=0x0d && c!=0x0a)
			break;
		else
			/* reached EOF while inside a whitespace */
			if(fileEof(file))
				return 0;
	}
	/* 'c' can be last char in file; we copy it anyway */
	for(next=buf;(next-buf)<n-1;)
	{
		*next=c;
		next++;
		c=(char)fileGetc(file);
		if(c==0x0d || c==0x0a || fileEof(file))
			break;
	}
	*next=0;
	return next-buf;
}

/*
 *	filePrintf
 */
vboolean	V_GLOBAL_VA
filePrintf(file_t file,const char *fmt,...)
{
	va_list marker;
	char *ptr;
	char fmt_buffer[MAX_FORMAT_BUFFER];
	u32_t len, wrlen;
	if(!fmt)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"filePrintf(NULL): invalid 'fmt'.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	ptr=fmt_buffer;
	va_start(marker,fmt);
	len=vsprintf(ptr,fmt,marker);
	va_end(marker);
    if(len>=MAX_FORMAT_BUFFER)
    {
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
        	"filePrintf(): too many args; heap destoyed.\n");
#endif	/* CHECKED_BUILD */
		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(file_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(file_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(file_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(file_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/zlib.h>
#include <../support/zlib/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;
	file_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));
	if(keepdata)
	{
		if(s->infile != NULL)
			fileClose(s->infile);
		u8sAdjust(&(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;
	if(--port->vtbl.base.ref_count)
		return port->vtbl.base.ref_count;
	/* Free stack's memory */
	memFree(port->base);
	memFreeObject(port);
	return 0;
}

/*
 * gzConvertToDecompress() based on zlib routines.
 * Authors of zlib: Jean-loup Gailly and Mark Adler.
 */
file_t	V_GLOBAL
gzConvertToDecompress(file_t in)
{
	int c;
	int err;
	int method;                   /* method byte */
	int flags;                    /* flags byte */
	uInt len;
	Byte *out_head;
	io_section_p port;
#ifdef	CHECKED_BUILD
	V_LOCAL char *procname = "gzConvertToDecompress()";
#endif	/* CHECKED_BUILD */
	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
	 */
	s->infile=in;
	if(!fileControl(s->infile,pctl_map_ispace,(void**)&(s->stream.next_in)))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s: unable to mmap() input file.\n",procname);
#endif	/* CHECKED_BUILD */
		gz_finish(s,false);
		return NULL;
	}
	s->stream.avail_in = fileSize(s->infile);
	/*
	 * Read in gzip header
	 */
	if(gz_magic[0] != get_byte(s) || gz_magic[1] != get_byte(s))
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s: bad archive, magic wrong.\n",procname);
#endif	/* CHECKED_BUILD */
		gz_finish(s,false);
		return NULL;
	}
	method = get_byte (s);
	flags = get_byte (s);
	if(method != Z_DEFLATED || flags & RESERVED)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s: unknown compression method.\n",procname);
#endif	/* CHECKED_BUILD */
		gz_finish(s,false);
		return NULL;
	}
	/* 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)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"%s: unable to read gzip header.\n",procname);
#endif	/* CHECKED_BUILD */
		gz_finish(s,false);
		return NULL;
	}
	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)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"%s: inflateInit2() failed.\n",procname);
#endif	/* CHECKED_BUILD */
		gz_finish(s,false);
		return NULL;
	}
	/*
	 *	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",
				procname,s->stream.total_out);
#endif		/* CHECKED_BUILD */
			gz_finish(s,false);
			
			return NULL;
		}
		len = s->stream.total_out - len;
		if(len)
			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 */
