/* MicroDot-MsgBase-System */

#define RBSIZE MAILBFSIZE

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

struct ixcache
{
	struct MinNode    n;
	ULONG             puffID;
	BPTR              ixfile;
	struct AsyncFile *datafile;
};

static struct MinList ixlist;
static void *ixcachepool; 

void ixc_flush( void )
{
	struct ixcache *ixc;

	while( ( ixc = ( struct ixcache * ) RemHead( ( struct List * ) &ixlist ) ) )
	{
		if( ixc->ixfile )
			Close( ixc->ixfile );
		if( ixc->datafile )
			CloseAsync( ixc->datafile );
		LibFreePooled( ixcachepool, ixc, sizeof( *ixc ) );
	}
}

void ixc_init( void )
{
	ixcachepool = LibCreatePool( MEMF_CLEAR, sizeof( struct ixcache ) * 10, sizeof( struct ixcache ) * 5 );
	NewList( ( struct List * ) &ixlist );
}

void ixc_exit( void )
{
	ixc_flush();
	LibDeletePool( ixcachepool );
}

static struct ixcache *getixc( ULONG pID )
{
	struct ixcache *ixc = ( struct ixcache * ) ixlist.mlh_Head;

//	Printf( "getixc %ld\n", pID );

	while( ixc->n.mln_Succ )
	{
		if( ixc->puffID == pID )
		{
//			Printf("getixc: found\n" );
			return( ixc );
		}
		ixc = ( struct ixcache * ) ixc->n.mln_Succ;
	}

//	Printf("getixc: numentries %ld\n",GetNumEntries( ( struct List * ) &ixlist ) );

	if( GetNumEntries( ( struct List * ) &ixlist ) > 10 || AvailMem( MEMF_LARGEST | MEMF_FAST ) < (256*1024) )
		ixc_flush();

	ixc = LibAllocPooled( ixcachepool, sizeof( *ixc ) );
	ixc->puffID = pID;
	AddTail( ( struct List * ) &ixlist, ( struct Node * ) ixc );
	return( ixc );
}

static BPTR ixc_getix( ULONG pID )
{
	struct ixcache *ixc = getixc( pID );
	char buf[ 128 ];

	if( !ixc->ixfile )
	{
		sprintf( buf, "%sMD_%lx.Index", prefs.datadir, pID );
		ixc->ixfile = Open( buf, MODE_OLDFILE );
	}
	return( ixc->ixfile );
}

static struct AsyncFile *ixc_getdata( ULONG pID )
{
	struct ixcache *ixc = getixc( pID );
	char buf[ 128 ];

	if( !ixc->datafile )
	{
		sprintf( buf, "%sMD_%lx.Data", prefs.datadir, pID );
		ixc->datafile = OpenAsync( buf, MODE_READ, 8192 );
	}
	return( ixc->datafile );
}

struct msghandle *msg_open( ULONG pufID, ULONG mID )
{
	BPTR ixfile;
	struct msghandle *new;
	char buf[ 256 ];
	int rc;

	ixfile = ixc_getix( pufID );
	if( !ixfile )
	{
		sprintf( buf, "%sMD_%lx.Data", prefs.datadir, pufID );
		askreq( "Fehler beim Datenbankzugriff auf Nachricht %ld:%ld\nDatei %s läßt sich nicht öffnen!", "Abbruch",
			pufID, mID, buf
		);
		return( NULL );
	}

	rc = Seek( ixfile, mID * sizeof( struct pindex ), OFFSET_BEGINNING );

	if( rc < 0 )
	{
		askreq( "Fehler (%ld) beim Datenbankzugriff auf Nachricht %ld:%ld\nIndexeintrag fehlend oder fehlerhaft.", "Abbruch",
			IoErr(), pufID, mID
		);
		return( NULL );
	}

	new = LibAllocPooled( miscmempool, sizeof( *new ) );
	if( !new )
	{
		return( NULL );
	}

	Read( ixfile, &new->pix, sizeof( struct pindex ) );

	new->file = ixc_getdata( pufID );

//	Printf( "pix.offset %ld, realoffset %ld\n", new->pix.offset, FtellAsync( new->file ) );

	if( !new->file )
	{
		askreq( "Fehler beim Datenbankzugriff auf Nachricht %ld:%ld\nDatei %s läßt sich nicht öffnen!", "Abbruch",
			pufID, mID, buf
		);
		return( NULL );
	}

	return( new );

}

void msg_close( struct msghandle *msg )
{
	if( msg )
	{
		LibFreePooled( miscmempool, msg, sizeof( *msg ) );
		/*if( msg->file )
			CloseAsync( msg->file );*/
	}
}

static int copyfh( struct AsyncFile *from, BPTR to, int bytes, int packed )
{
	void *bufmem;
	int rc;
	struct pfhandle *pfh;
	int bs;

	if( bytes < 1 )
		return( 0 );

	pfh = pf_open( from, FALSE, packed, &bytes );

	bufmem = allocbuffmem( bytes, &bs );

	while( bytes )
	{
		rc = pf_read( pfh, bufmem, min( bs, bytes ) );
		if( rc )
			Write( to, bufmem, rc );
		bytes -= rc;
	}

	pf_close( pfh );

	freebuffmem();

	return( 0 );
}

int copyfh2p( struct AsyncFile *from, FILE *to, int bytes, int packed )
{
	void *bufmem;
	int rc;
	struct pfhandle *pfh;
	int bs;

	if( bytes < 1 )
		return( 0 );

	pfh = pf_open( from, FALSE, packed, &bytes );

	bufmem = allocbuffmem( bytes, &bs );

	while( bytes )
	{
		rc = pf_read( pfh, bufmem, min( bs, bytes ) );
		if( rc )
			fwrite( bufmem, rc, 1, to );
		bytes -= rc;
	}

	pf_close( pfh );

	freebuffmem();

	return( 0 );
}


/* Nachricht rauskopieren */
int msg_copyfh( struct msghandle *msg, BPTR tofile, ULONG flags )
{
	if( !msg || !msg->file )
		return( -1 );

	if( flags & MC_HEADER )
	{
		SeekAsync( msg->file, msg->pix.offset, OFFSET_BEGINNING );
		copyfh( msg->file, tofile, msg->pix.headersize, msg->pix.flags & ( PIF_PACKED | PIF_XPK ) );
	}

	if( msg->pix.commentsize )
	{
		if( flags & MC_COMMENT )
		{
			SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize, OFFSET_BEGINNING );
			copyfh( msg->file, tofile, msg->pix.commentsize, msg->pix.flags & ( PIF_PACKED | PIF_XPK ) );
		}
	}

	if( msg->pix.datasize )
	{
		if( flags & MC_DATA )
		{
			SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize + msg->pix.commentsize, OFFSET_BEGINNING );
			copyfh( msg->file, tofile, msg->pix.datasize, msg->pix.flags & ( PIF_PACKED | PIF_XPK ) );
		}
	}

	return( 0 );
}

int msg_copyfp( struct msghandle *msg, FILE *tofile, ULONG flags )
{
	if( !msg || !msg->file )
		return( -1 );

	if( flags & MC_HEADER )
	{
		SeekAsync( msg->file, msg->pix.offset, OFFSET_BEGINNING );
		copyfh2p( msg->file, tofile, msg->pix.headersize, msg->pix.flags & ( PIF_PACKED | PIF_XPK ) );
	}

	if( msg->pix.commentsize )
	{
		if( flags & MC_COMMENT )
		{
			SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize, OFFSET_BEGINNING );
			copyfh2p( msg->file, tofile, msg->pix.commentsize, msg->pix.flags & ( PIF_PACKED | PIF_XPK ) );
		}
	}

	if( msg->pix.datasize )
	{
		if( flags & MC_DATA )
		{
			SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize + msg->pix.commentsize, OFFSET_BEGINNING );
			copyfh2p( msg->file, tofile, msg->pix.datasize, msg->pix.flags & ( PIF_PACKED | PIF_XPK ) );
		}
	}

	return( 0 );
}

int msg_copy( struct msghandle *msg, char *to, ULONG flags )
{
	BPTR tofile;
	int rc;

	tofile = Open( to, MODE_NEWFILE );
	if( !tofile )
		return( -1 );
	rc = msg_copyfh( msg, tofile, flags );
	Close( tofile );
	return( rc );

}


/* Header einer Nachricht laden */
struct hlist *msg_loadheader( struct msghandle *msg )
{
	char *buffer, *bp;
	struct hlist *hlist;
	struct pfhandle *pfh;
	int len = msg->pix.headersize;
	char *p, *p2;

	if( SeekAsync( msg->file, msg->pix.offset, MODE_START ) < 0 )
	{
		return( NULL );
	}
	pfh = pf_open( msg->file, FALSE, msg->pix.flags & ( PIF_PACKED | PIF_XPK ), &len );

	bp = buffer = myAllocVec( len + 1, MEMF_CLEAR );
	if( !buffer )
	{
		pf_close( pfh );
		return( NULL );
	}

	pf_read( pfh, buffer, len );
	pf_close( pfh );

	hlist = hl_initheader();

	while( ( p = strchr( buffer, '\n' ) ) )
	{
		*p++ = 0;
		p2 = strchr( buffer, ':' );
		if( !p2 )
			break;
		else
			*p2++ = 0;
		hl_addheader( hlist, buffer, stpblk( p2 ) );
		buffer = p;
	}

	myFreeVec( bp );

	return( hlist );

}

int msg_initread( struct msghandle *msg, int readwhat )
{
	switch( readwhat )
	{
		case MC_HEADER:
			msg->readmode = 1;
			msg->readremain = msg->pix.headersize;
			if( !msg->readremain )
				return( 0 );
			SeekAsync( msg->file, msg->pix.offset, MODE_START );
			break;

		case MC_COMMENT:
			msg->readmode = 1;
			msg->readremain = msg->pix.commentsize;
			if( !msg->readremain )
				return( 0 );
			SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize, MODE_START );
			break;

		case MC_DATA:
			msg->readmode = 1;
			msg->readremain = msg->pix.datasize;
			if( !msg->readremain )
				return( 0 );
			SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize + msg->pix.commentsize, MODE_START );
			break;

		default:	/* tja... */
			return( -1 );
	}

	msg->pfh = pf_open( msg->file, FALSE, msg->pix.flags & ( PIF_PACKED | PIF_XPK ), ( int * ) &msg->readremain );
	return( 0 );
}

void msg_endread( struct msghandle *msg )
{
	msg->readmode = msg->readremain = 0;

	if( msg->pfh )
	{
		pf_close( msg->pfh );
		msg->pfh = NULL;
	}
}


/* Nachricht zeilenweise lesen; null bei ende/fehler */
int msg_read( struct msghandle *msg, UBYTE *buffer, int size )
{
	int count = 0;
	int ch;

	if( !msg->readmode || !msg->readremain )
		return( 0 );

	while( size-- )
	{
		ch = pf_readch( msg->pfh );
		if( ch < 0 )
			break;
		if( ch == 13 )
			continue;
		if( ch == 10  )
			break;
		buffer[ count++ ] = ch;
	}
	buffer[ count ] = 0;

	return( ( ch == 10 ) ? 1 : ( count ? 2 : 0 ) );
}

/* Nummer des Userpuffers zurückgeben */

static ULONG getmypuffer( void )
{
	ULONG c = ~0;
	char buff[ 64 ];
	int pl;

	pushdir( prefs.datadir );

	while( c )
	{
		sprintf( buff, "MD_%lx.Data", c );
		pl = getfilelen( buff );
		if( pl < 256000 )
			break;
		c--;
	}
	popdir();
	return( c );
}

static int copypack( BPTR fromfile, struct AsyncFile *tofile, int size )
{
	struct pfhandle *pfh = pf_open( tofile, TRUE, prefs.do_packmsg, 0 );
	char buffer[ 2048 ];
	int rc;

	while( size )
	{
		rc = Read( fromfile, buffer, min( size, 2048 ) );
		if( rc )
			pf_write( pfh, buffer, rc );
		size -= rc;
	}
	return( pf_close( pfh ) );
}

static int copymempack( struct AsyncFile *tofile, UBYTE *data, int size )
{
	struct pfhandle *pfh = pf_open( tofile, TRUE, prefs.do_packmsg, 0 );

	pf_write( pfh, data, size );
	return( pf_close( pfh ) );
}


ULONG msg_getsize( struct msghandle *msg, int what )
{
	ULONG size = 0;

	if( ! ( msg->pix.flags & ( PIF_PACKED ) ) )
	{
		switch( what )
		{
			case MC_HEADER:
				return( msg->pix.headersize );

			case MC_COMMENT:
				return( msg->pix.commentsize );

			case MC_DATA:
				return( msg->pix.datasize );

		}
	}
	else switch( what )
	{
		case MC_HEADER:
			if( msg->pix.headersize )
			{
				SeekAsync( msg->file, msg->pix.offset, MODE_START );
				ReadAsync( msg->file, &size, 4 );
			}
			break;

		case MC_COMMENT:
			if( msg->pix.commentsize )
			{
				SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize, MODE_START );
				ReadAsync( msg->file, &size, 4 );
			}
			break;

		case MC_DATA:
			if( msg->pix.datasize )
			{
				SeekAsync( msg->file, msg->pix.offset + msg->pix.headersize + msg->pix.commentsize, MODE_START );
				ReadAsync( msg->file, &size, 4 );
			}
			break;

	}

	return( size );
}


/* Nachricht speichern; header in header... */

struct pindex *msg_store( char *comment, LONG commentsize,
               			  char *data, LONG datasize,
						  ULONG *pID, ULONG *mID, struct hlist *hlist )
{
	BPTR commentfile = NULL, datafile = NULL;
	BPTR ixfile;
	struct AsyncFile *outfile;
	static struct pindex pix;
	ULONG p;
	int c;
	char buff[ 128 ];

	ixc_flush();

	memset( &pix, 0, sizeof( pix ) );

	if( prefs.do_packmsg )
		pix.flags |= PIF_PACKED;

	if( prefs.do_packmsg >= PACKMODE_XPK )
		pix.flags |= PIF_XPK;

	/* Comment ist Datei */
	if( commentsize < 0 )
	{
		commentsize = getfilelen( comment );
		commentfile = Open( comment, MODE_OLDFILE );
		if( !commentfile )
		{
			if( commentsize > 0 )
				return( NULL );
			commentsize = 0;
		}
	}

	/* Data ist Datei */
	if( datasize < 0 )
	{
		datasize = getfilelen( data );
		datafile = Open( data, MODE_OLDFILE );
		if( !datafile )
		{
			if( datasize > 0 )
				return( NULL );
			datasize = 0;
		}
	}

	/* Gut... */
	p = getmypuffer();
	sprintf( buff, "%sMD_%lx.Data", prefs.datadir, p );
	c = getfilelen( buff );
	if( c < 0 )
		c = 0;
	outfile = OpenAsync( buff, MODE_APPEND, 32768 );
	if( !outfile )
		return( NULL );
//	WriteAsync( outfile, "Test0\n", 6 );
	*pID = p;
	pix.offset = c;
	pix.headersize = hl_dumpheaderpf( hlist, outfile );

//	WriteAsync( outfile, "Test1\n", 6 );

	if( commentsize )
	{
		if( commentfile )
			pix.commentsize = copypack( commentfile, outfile, commentsize );
		else
			pix.commentsize = copymempack( outfile, comment, commentsize );
	}

//	WriteAsync( outfile, "Test2\n", 6 );

	if( datasize )
	{
		if( datafile )
			pix.datasize = copypack( datafile, outfile, datasize );
		else
			pix.datasize = copymempack( outfile, data, datasize );
	}
//	WriteAsync( outfile, "Test3\n", 6 );

	CloseAsync( outfile );
	if( datafile )
		Close( datafile );
	if( commentfile )
		Close( commentfile );

	/* Index schreiben */
	sprintf( buff, "%sMD_%lx.Index", prefs.datadir, p );
	ixfile = OpenAppend( buff );
	if( !ixfile )
		return( NULL );
	*mID = Seek( ixfile, 0, OFFSET_CURRENT ) / sizeof( pix );
	Write( ixfile, &pix, sizeof( pix ) );
	Close( ixfile );

	/* Alles k.o. */
	return( &pix );

}

/* Nachricht ersetzen (!) */

struct pindex *msg_replace( char *comment, LONG commentsize,
               			  char *data, LONG datasize,
						  ULONG pID, ULONG mID, struct hlist *hlist )
{
	BPTR commentfile = NULL, datafile = NULL;
	BPTR ixfile;
	struct AsyncFile *outfile;
	static struct pindex pix;
	char buff[ 128 ];

	ixc_flush();

	memset( &pix, 0, sizeof( pix ) );

	if( prefs.do_packmsg )
		pix.flags |= PIF_PACKED;

	if( prefs.do_packmsg >= PACKMODE_XPK )
		pix.flags |= PIF_XPK;

	/* Comment ist Datei */
	if( commentsize < 0 )
	{
		commentsize = getfilelen( comment );
		commentfile = Open( comment, MODE_OLDFILE );
		if( !commentfile )
		{
			if( commentsize > 0 )
				return( NULL );
			commentsize = 0;
		}
	}

	/* Data ist Datei */
	if( datasize < 0 )
	{
		datasize = getfilelen( data );
		datafile = Open( data, MODE_OLDFILE );
		if( !datafile )
		{
			if( datasize > 0 )
				return( NULL );
			datasize = 0;
		}
	}

	/* Gut... */
	sprintf( buff, "%sMD_%lx.Data", prefs.datadir, pID );
	outfile = OpenAsync( buff, MODE_APPEND, 32768 );
	if( !outfile )
		return( NULL );
	pix.offset = FtellAsync( outfile );
	pix.headersize = hl_dumpheaderpf( hlist, outfile );

	if( commentsize )
	{
		if( commentfile )
			pix.commentsize = copypack( commentfile, outfile, commentsize );
		else
			pix.commentsize = copymempack( outfile, comment, commentsize );
	}

	if( datasize )
	{
		if( datafile )
			pix.datasize = copypack( datafile, outfile, datasize );
		else
			pix.datasize = copymempack( outfile, data, datasize );
	}

	CloseAsync( outfile );
	if( datafile )
		Close( datafile );
	if( commentfile )
		Close( commentfile );

	/* Index schreiben */
	sprintf( buff, "%sMD_%lx.Index", prefs.datadir, pID );
	ixfile = OpenAppend( buff );
	if( !outfile )
		return( NULL );
	Seek( ixfile, sizeof( pix ) * mID, OFFSET_BEGINNING );
	Write( ixfile, &pix, sizeof( pix ) );
	Close( ixfile );

	/* Alles k.o. */
	return( &pix );

}


/*

  Format gecrunchter Daten: 

  ULONG decrunchlen		Entpackte Länge des gesammten Parts

  dann ein oder mehrere

  UWORD chunksize       Gepackte Größe dieses Chunks; 0 = Ende
  UWORD chunkreal       Entpackte Größe dieses Chunks; 0 = ungepackt
  UBYTE data[ xxx ]     Gepackte Daten

*/
