/*
 *   msgpack.c - provide "packed" file frontend
 */

#include "microdot.h"
#include "asyncio.h"

#define PFHSIZE MAILBFSIZE

struct pfhandle {
	struct AsyncFile *file;
	int offset;
	int mode;
	int buffpos;
	int decrunchlen;
	int crunchlen;
	int bytesleft;
	int packmode;
	int buffsize;
	UBYTE buffer[ PFHSIZE + 128 ];
};

#define CRUNCHBITS (crunchbits[ prefs.do_packmsg - PACKMODE_INTFAST ])
static ULONG crunchbits[] = { 9, 12, 15 };
static UBYTE *crunchbuffer;

struct pfhandle *pf_open( struct AsyncFile *asf, int rw, int packed, int *size )
{
	struct pfhandle *new;

	/* Setup Crunch/Decrunch-Buffer */
	if( packed && !crunchbuffer )
		crunchbuffer = LibAllocPooled( miscmempool, PFHSIZE + 2048 );

	new = AllocMem( sizeof( *new ), MEMF_CLEAR | MEMF_PUBLIC );
	if( !new )
		return( NULL );

	new->file = asf;
	new->mode = rw;
	new->packmode = packed;

	if( !rw )
	{
		if( packed )
		{
			ReadAsync( asf, &new->decrunchlen, 4 );
//			Printf("read decrunchlen %ld\n", new->decrunchlen );
			new->buffpos = PFHSIZE + 1; /* Force fill on first read */
			new->bytesleft = new->decrunchlen;
			if( size )
				*size = new->decrunchlen;
		}
		else
		{
			new->bytesleft = new->decrunchlen = *size;
		}
//		Printf("pfopen size %ld\n", new->decrunchlen );
	}
	else
	{
		if( packed )
		{
			new->offset = FtellAsync( asf );
			WriteAsync( asf, &new, 4 );
			/* Account Decrunchlen LW */
			new->crunchlen = 4;
		}
	}

	return( new );
}

static int fillbuffer( struct pfhandle *pfh )
{
	struct mwheader mwh;
	int rc;

	if( !pfh->bytesleft )
		return( 0 );

	/*pfh->bytesleft -= 4;*/

//	Printf("fillbuffer offset %ld\n", FtellAsync( pfh->file ) );

	ReadAsync( pfh->file, &mwh, 4 );

//	Printf("fillbuffer chunksize %ld chunkreal %ld\n", mwh.chunksize, mwh.chunkreal );

	if( !mwh.chunksize )
		return( 0 );
	/* ungepackt */
	if( !mwh.chunkreal )
	{
		rc = ReadAsync( pfh->file, pfh->buffer, mwh.chunksize );
		if( rc != mwh.chunksize )
		{
			askreq( "Lesefehler: %lD statt %lD Bytes", "Weiter", rc, mwh.chunksize  );
			return( 0 );
		}
		pfh->buffpos = 0;
		pfh->bytesleft -= rc;
		pfh->buffsize = rc;
		return( rc );
	}

	/* gepackt */
	if( ( rc = ReadAsync( pfh->file, crunchbuffer, mwh.chunksize ) ) != mwh.chunksize )
	{
		askreq( "Lesefehler: %lD statt %lD Bytes", "Weiter", rc, mwh.chunksize  );
		return( 0 );
	}

	if( pfh->packmode & PIF_XPK )
		rc = xpkunpack( crunchbuffer, pfh->buffer, mwh.chunksize );
	else
		rc = lzw_unpack( crunchbuffer, pfh->buffer );

//	Printf( "rc unpack %ld\n", rc );

	pfh->buffpos = 0;
	pfh->bytesleft -= rc;
	pfh->buffsize = rc;

	return( rc );
}

int pf_readch( struct pfhandle *pfh )
{
	/* Ungepackt */
	if( !pfh->packmode )
	{
		if( !pfh->bytesleft )
			return( EOF );
		pfh->bytesleft--;
		return( ReadCharAsync( pfh->file ) );
	}

	if( pfh->buffpos >= pfh->buffsize )
	{
		if( !fillbuffer( pfh ) )
			return( EOF );
	}
	return( pfh->buffer[ pfh->buffpos++ ] );
}

int pf_read( struct pfhandle *pfh, UBYTE *to, int size )
{
	int count = 0;
	int c;

	if( !pfh->packmode )
	{
		size = min( size, pfh->bytesleft );
		pfh->bytesleft -= size;
		if( size )
			return( ReadAsync( pfh->file, to, size ) );
		else
			return( 0 );
	}

	while( size )
	{
/*		if( !pfh->bytesleft )
			return( count );*/
		c = min( size, pfh->buffsize - pfh->buffpos );
		if( c > 0 )
		{
			CopyMem( &pfh->buffer[ pfh->buffpos ], to, c );
			pfh->buffpos += c;
			count += c;
			to += c;
			size -= c;
		}
		if( pfh->buffpos >= pfh->buffsize )
		{
			if( !fillbuffer( pfh ) )
				return( count );
		}
	}
	return( count );
}

static int writebuffer( struct pfhandle *pfh )
{
	struct mwheader mwh;
	int bs;
	int crunched = 1;

//	Printf("enter writebuffer buffpos %ld\n", pfh->buffpos );

	mwh.chunkreal = pfh->buffpos;
	if( prefs.do_packmsg >= PACKMODE_XPK )
		mwh.chunksize = xpkpack( pfh->buffer, crunchbuffer, pfh->buffpos );
	else
		mwh.chunksize = lzw_pack( pfh->buffer, crunchbuffer, pfh->buffpos, CRUNCHBITS );

//	Printf(" after pack chunksize %ld\n", mwh.chunksize );

	/* Nicht gepackt */
	if( ( !mwh.chunksize ) || ( mwh.chunksize >= mwh.chunkreal && prefs.do_packmsg != PACKMODE_XPKPW ) )
	{
		bs = mwh.chunkreal;
		mwh.chunksize = mwh.chunkreal;
		mwh.chunkreal = 0;
		crunched = 0;
	}
	else
	{
		bs = mwh.chunksize;
	}
	pfh->buffpos = 0;
	if( WriteAsync( pfh->file, &mwh, 4 ) != 4 )
		return( 0 );
	if( WriteAsync( pfh->file, ( crunched ) ? crunchbuffer : pfh->buffer, bs ) != bs )
		return( 0 );

	pfh->crunchlen += bs + 4;

	return( 1 );
}

int pf_writech( struct pfhandle *pfh, int ch )
{
	int rc = 1;

	if( !pfh->packmode )
	{
		pfh->crunchlen++;
		return( WriteCharAsync( pfh->file, ch ) );
	}

	pfh->buffer[ pfh->buffpos++ ] = ch;
	pfh->decrunchlen++;

	if( pfh->buffpos >= PFHSIZE )
		rc = writebuffer( pfh );

	return( rc );
}

int pf_write( struct pfhandle *pfh, UBYTE *data, int size )
{
	int count = 0;
	int c;

	if( !pfh->packmode )
	{
		count = WriteAsync( pfh->file, data, size );
		pfh->crunchlen += count;
	}
	else
	{
		if( !registerinfo.serie )
			return( 0 );

		/* Freie Zeichen im Buffer */
		while( size )
		{
			c = min( size, PFHSIZE - pfh->buffpos );
			if( c > 0 )
			{
				CopyMem( data, &pfh->buffer[ pfh->buffpos ], c );
				pfh->buffpos += c;
				pfh->decrunchlen += c;
				count += c;
				size -= c;
				data += c;
			}
			if( pfh->buffpos >= PFHSIZE )
				if( !writebuffer( pfh ) ) 
					return( 0 );
		}
	}
	return( count );
}

int pf_close( struct pfhandle *pfh )
{
	int oldpos;

	if( pfh->packmode )
	{
		/* Schreiben? */
		if( pfh->mode )
		{
			/* Flush ? */
			if( pfh->buffpos )
				writebuffer( pfh );
			oldpos = 0;
			WriteAsync( pfh->file, &oldpos, 4 );
			pfh->crunchlen += 4;
			oldpos = SeekAsync( pfh->file, pfh->offset, MODE_START );
			WriteAsync( pfh->file, &pfh->decrunchlen, 4 );
			SeekAsync( pfh->file, oldpos, MODE_START );
		}
	}

	oldpos = pfh->crunchlen;

	FreeMem( pfh, sizeof( *pfh ) );

	return( oldpos );
}
