#include "system/xstdlib.h"
#include "system/xstring.h"
#include "system/xstdio.h"
#include "misc/dbfs.h"
#include "misc/dbfsutil.h"
#include "misc/dbfspack.h"

int dbfspack = TRUE;
int dbfscrypt = TRUE;
int dbfserrno = 0;
void( *dbfscallback)( char *statusstr);

/*################################################################
#
# dbfsopen
#
#################################################################*/

DBFS *dbfsopen( char *filename)
{
	char id[DBFS_IDSIZE];
	DBFS *dbfs;
	DBFILE *dbfile;
	BYTE slen;
	int i;

	if( (dbfs = (DBFS *)xmalloc( sizeof(DBFS))) == NULL)
		return( NULL);

	if( (dbfs->readfile = fopen( filename, "r+b")) == NULL)
		if( (dbfs->readfile = fopen( filename, "rb")) == NULL)
		{
      xfree( dbfs);
			return( NULL);
		}
		else
			dbfs->flags |= DBFSFLAG_READONLY;

	strcpy( dbfs->readname, filename);

	fseek( dbfs->readfile, -DBFS_IDSIZE, SEEK_END);
	fread( id, sizeof( char), DBFS_IDSIZE, dbfs->readfile);
  if( strncmp( id, DBFS_ID, DBFS_IDSIZE) != 0)
	{
		fclose( dbfs->readfile);
    xfree( dbfs);
		return( NULL);
	}

	fseek( dbfs->readfile, -(int)(DBFS_IDSIZE + 2*sizeof(WORD) + sizeof(DWORD)), SEEK_END);
  dbfs->starttoc = frle_dword( dbfs->readfile);
  dbfs->files = frle_word( dbfs->readfile);
  if( frle_word( dbfs->readfile) != DBFS_VERSION)
	{
		fclose( dbfs->readfile);
    xfree(dbfs);
		return( NULL);
	}


	fseek( dbfs->readfile, dbfs->starttoc, SEEK_END);
	dbfile = NULL;
	for( i=0; i<dbfs->files; i++)
	{
		if( i == 0)
		{
			if( (dbfs->filelist = (DBFILE *)xmalloc( sizeof( DBFILE))) == NULL)
			{
				dbfsclose( dbfs);
				return( NULL);
			}
			dbfs->filelist->prev = NULL;
			dbfile = dbfs->filelist;
		}
		else
		{
			if( (dbfile->next = (DBFILE *)xmalloc( sizeof( DBFILE))) == NULL)
			{
				dbfsclose( dbfs);
				return( NULL);
			}
			dbfile->next->prev = dbfile;
			dbfile = dbfile->next;
		}
		dbfile->dbfs = dbfs;
    slen = fr_byte( dbfs->readfile);
		fread( &dbfile->name, sizeof( BYTE), slen, dbfs->readfile);
		dbfs_crypt( (BYTE *)dbfile->name, (DWORD)slen);
    dbfile->flags = frle_dword( dbfs->readfile);
    dbfile->flength = frle_dword( dbfs->readfile);
    dbfile->plength = frle_dword( dbfs->readfile);
    dbfile->length = frle_dword( dbfs->readfile);
    dbfile->crc = frle_dword( dbfs->readfile);
    dbfile->filepos = frle_dword( dbfs->readfile);
		dbfile->next = NULL;
	}

	fseek( dbfs->readfile, 0, SEEK_END);
	dbfs->startdata = ftell( dbfs->readfile) + dbfs->starttoc;
	dbfile = dbfs->filelist;
	while( dbfile != NULL)
	{
		dbfs->startdata -= dbfile->plength;
		dbfile = dbfile->next;
	}

	dbfile = dbfs->filelist;
	while( dbfile != NULL)
	{
		dbfile->filepos += dbfs->startdata;
		dbfile = dbfile->next;
	}

	return( dbfs);
}

/*################################################################
#
# dbfscreate
#
#################################################################*/

DBFS *dbfscreate( char *filename)
{
	DBFS *dbfs;

	if( (dbfs = (DBFS *)xmalloc( sizeof(DBFS))) == NULL)
		return( NULL);

	if( (dbfs->readfile = fopen( filename, "wb")) == NULL)
	{
    xfree( dbfs);
		return( NULL);
	}
	strcpy( dbfs->readname, filename);

  fwle_dword( dbfs->readfile, -16);
  fwle_word( dbfs->readfile, 0);
  fwle_word( dbfs->readfile, DBFS_VERSION);
	fwrite( DBFS_ID, sizeof( char), DBFS_IDSIZE, dbfs->readfile);

	dbfs->startdata = 0;
	dbfs->starttoc = 0;
	dbfs->files = 0;

	return( dbfs);
}

/*################################################################
#
# dbfsclose
#
#################################################################*/

void dbfsclose( DBFS *dbfs)
{
	DBFILE *current,*next;

	if( dbfs->flags & DBFSFLAG_MODIFIED)
		dbfs_update( dbfs);

	fclose( dbfs->readfile);

	current = dbfs->filelist;
	while( current != NULL)
	{
		next = current->next;
		xfree( current);
		current = next;
	}
	xfree( dbfs);
}

/*################################################################
#
# dbfilefind
#
#################################################################*/

DBFILE *dbfilefind( DBFS *dbfs, char *filename)
{
	DBFILE *current;

	current = dbfs->filelist;
	while( (current != NULL) && (_stricmp( current->name, filename) != 0))
		current = current->next;

	return( current);
}

/*################################################################
#
# dbfilecreate
#
#################################################################*/

DBFILE *dbfilecreate( DBFS *dbfs, char *filename)
{
	DBFILE *prev, *current;

	if( dbfs->flags & DBFSFLAG_READONLY)
		return( NULL);

	prev = current = dbfs->filelist;
	while( (current != NULL) && (_stricmp( current->name, filename) != 0))
		prev = current, current = current->next;

	if( current != NULL)
		return( NULL);

	current = (DBFILE *)xmalloc( sizeof( DBFILE));
	current->prev = prev;
	current->next = NULL;
	current->dbfs = dbfs;
	strcpy( current->name, filename);
	current->flength = current->length = current->plength = 0;
	current->flags = DBFILEFLAG_NULLFILE;

	if( prev == NULL)
		dbfs->filelist = current;
	else
		current->prev->next = current;
	dbfs->files++;
	return( current);
}


/*################################################################
#
# dbfileread
#
#################################################################*/

int dbfileread( DBFILE *dbfile, BYTE *data)
{

	if( dbfile->flags & DBFILEFLAG_NULLFILE)
		return( TRUE);

	fseek( dbfile->dbfs->readfile, dbfile->filepos, SEEK_SET);
	if( dbfile->flags & DBFILEFLAG_COMPRESSED)
	{
		BYTE *pdata = (BYTE *)xmalloc( dbfile->plength);

		if( pdata == NULL)
			return( FALSE);

		if( fread( pdata, sizeof( BYTE), dbfile->flength, dbfile->dbfs->readfile) != dbfile->flength)
		{
			xfree( pdata);
			return( FALSE);
		}
		if( dbfile->flags & DBFILEFLAG_CRYPTED)
			dbfs_crypt( pdata, dbfile->flength);

    if ( !dbfs_decompress( pdata, dbfile->plength, data, dbfile->length))
		{
      xfree( pdata);
			return( FALSE);
		}
	}
	else
	{
		if( fread( data, sizeof( BYTE), dbfile->flength, dbfile->dbfs->readfile) != dbfile->flength)
			return( FALSE);
		if( dbfile->flags & DBFILEFLAG_CRYPTED)
			dbfs_crypt( data, dbfile->flength);
	}
	return( TRUE);
}

/*################################################################
#
# dbfilewrite
#
#################################################################*/

int dbfilewrite( DBFILE *dbfile, BYTE* data, DWORD length)
{
	if( dbfile->dbfs->flags & DBFSFLAG_READONLY)
		return( FALSE);

	if( length == 0)
	{
		dbfile-> flength = dbfile->plength = dbfile->length = 0;
		dbfile->flags = DBFILEFLAG_NULLFILE;
	}

	if( !(dbfile->dbfs->flags & DBFSFLAG_MODIFIED))
		dbfs_makewrite( dbfile->dbfs);


	if( dbfile->flags & DBFILEFLAG_MODIFIED)
	{
		dbfile->flength = dbfile->plength = dbfile->length = 0;
		dbfile->flags = DBFILEFLAG_NULLFILE;
		dbfs_rewrite( dbfile->dbfs);
	}

	if( dbfspack && (length > 128))
	{
		BYTE *pdata = (BYTE *)xmalloc( length);
		DWORD plength;

		if( pdata == NULL)
			return( FALSE);

    if( dbfs_compress( data, length, pdata, &plength))
		{
			dbfile->flags |= DBFILEFLAG_COMPRESSED;
			if( dbfscrypt)
			{
				dbfs_crypt( pdata, plength);
				dbfile->flags |= DBFILEFLAG_CRYPTED;
			}
			fseek( dbfile->dbfs->writefile, 0, SEEK_END);
			dbfile->filepos = ftell( dbfile->dbfs->writefile);
			if( fwrite( pdata, sizeof( BYTE), plength, dbfile->dbfs->writefile) != plength)
			{
				xfree(pdata);
				return( FALSE);
			}
			dbfile->flength = dbfile->plength = plength;
			dbfile->length = length;
			dbfile->flags &= ~DBFILEFLAG_NULLFILE;
		}
		else
		{
			if( dbfscrypt)
			{
				dbfs_crypt( data, length);
				dbfile->flags |= DBFILEFLAG_CRYPTED;
			}

			dbfile->filepos = ftell( dbfile->dbfs->writefile);
			if( fwrite( data, sizeof( BYTE), length, dbfile->dbfs->writefile) != length)
			{
				xfree(pdata);
				return( FALSE);
			}

			if( dbfscrypt)
				dbfs_crypt( data, length);

			dbfile->flength = dbfile->plength = dbfile->length = length;
			dbfile->flags &= ~DBFILEFLAG_NULLFILE;
		}
		xfree( pdata);
	}
	else
	{
		if( dbfscrypt)
		{
			dbfs_crypt( data, length);
			dbfile->flags |= DBFILEFLAG_CRYPTED;
		}

		dbfile->filepos = ftell( dbfile->dbfs->writefile);
		if( fwrite( data, sizeof( BYTE), length, dbfile->dbfs->writefile) != length)
			return( FALSE);

		if( dbfscrypt)
			dbfs_crypt( data, length);

		dbfile->flength = dbfile->plength = dbfile->length = length;
		dbfile->flags &= ~DBFILEFLAG_NULLFILE;
	}
	dbfile->flags |= DBFILEFLAG_MODIFIED;
	return( TRUE);
}

/*################################################################
#
# dbfiledelete
#
#################################################################*/

int dbfiledelete( DBFILE *dbfile)
{
	if( dbfile->dbfs->flags & DBFSFLAG_READONLY)
		return( FALSE);

	if( (dbfile->prev == NULL) && (dbfile->next == NULL))
		dbfile->dbfs->filelist = NULL;
	else if( (dbfile->prev == NULL) && (dbfile->next != NULL))
	{
		dbfile->dbfs->filelist = dbfile->next;
		dbfile->next->prev = NULL;
	}
	else if( (dbfile->prev != NULL) && (dbfile->next == NULL))
		dbfile->prev->next = NULL;
	else
	{
		dbfile->prev->next = dbfile->next;
		dbfile->next->prev = dbfile->prev;
	}
	dbfile->dbfs->files--;

	if( dbfile->flags & DBFILEFLAG_NULLFILE)
	{
		xfree( dbfile);
		return( TRUE);
	}

	if( !(dbfile->dbfs->flags & DBFSFLAG_MODIFIED))
		dbfs_makewrite( dbfile->dbfs);

	if( dbfile->flags & DBFILEFLAG_MODIFIED)
		dbfs_rewrite( dbfile->dbfs);

	xfree( dbfile);
	return( TRUE);
}
