/* Copyright (C) 2001 Michael Leonhard
 * Mike Leonhard
 * mike at tamale dot net
 * http://tamale.net/
 */

#include <assert.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include "sebae.h"

enum { unusedpipe = -1, filereadpipe = 0, filewritepipe = 1, mempipe = 2 };

int Sebae_Pipe_BytesAvailable( struct SebaeVM *vm, int p ) {
	struct SebaePipe *pipe;
	int ret;
	
	assert( vm );
	assert( p > -1 );
	assert( p < vm->sizepipelist );

	/* pipe */
	pipe = &vm->pipelist[p];
	
	/* readable pipe */
	if( pipe->type == filereadpipe ) return pipe->flen + pipe->bytes;
	
	/* writable pipe */
	else if( pipe->type == filewritepipe ) return Sebae_Error( vm, "no bytes to read on writable pipe" );
	
	/* mem pipe */
	else if( pipe->type == mempipe ) return pipe->flen - pipe->bytes;
	
	return Sebae_Error( vm, "unknown pipe type" );
	}

int Sebae_Pipe_Free( struct SebaeVM *vm, int p ) {
	struct SebaePipe *pipe;
	int ret;
	
	assert( vm );
	assert( p > -1 );
	assert( p < vm->sizepipelist );

	/* pipe to free */
	pipe = &vm->pipelist[p];
	assert( pipe->data );
	
	/* readable pipe */
	if( pipe->type == filereadpipe ) {
		assert( pipe->fd > -1 );
	
		/* close file */
		close( pipe->fd );
		}
	
	/* writable pipe */
	else if( pipe->type == filewritepipe ) {
		assert( pipe->fd > -1 );
		
		/* pipe has uncommited bytes in buffer */
		if( pipe->bytes > 0 ) {
			/* commit buffer to file handle */
			ret = write( pipe->fd, pipe->data, pipe->bytes );
		
			/* write failed */
			if( ret == -1 ) return Sebae_PError( vm, "write failed" );
			
			/* incomplete write */
			else if( ret != pipe->bytes ) return Sebae_Error( vm, "incomplete write" );
			}
	
		/* close file */
		close( pipe->fd );
		}

	/* not mem pipe */
	else if( pipe->type != mempipe ) return Sebae_Error( vm, "unknown pipe type" );
	
	/* free buffer */
	free( pipe->data );
	
	/* reset pipe */
	pipe->type = unusedpipe;
	pipe->fd = -1;
	pipe->flen = -1;
	pipe->bytes = -1;
	pipe->data = NULL;
	
	return 1;
	}

int Sebae_Pipe_FromFile( struct SebaeVM *vm, char *fname ) {
	struct SebaePipe *pipe;
	struct stat finfo;
	int fd, p, ret;
	
	assert( fname );
	assert( vm );

	/* open input file */
	/* printf( "reading %s\n", fname );/**/
	fd = open( fname, O_RDONLY );
	
	/* open failed */
	if( fd == -1 ) return Sebae_PError( vm, "could not open file" );
	
	/* read file characteristics */
	ret = fstat( fd, &finfo );
	
	/* fstat failed */
	if( ret == -1 ) return Sebae_PError( vm, "fstat failed" );
	
	/* new pipe */
	p = Sebae_Pipe_New( vm );
	pipe = &vm->pipelist[p];

	/* initialize new pipe struct */
	pipe->type = filereadpipe;
	pipe->fd = fd;
	pipe->flen = finfo.st_size;
	pipe->bytes = 0;
	pipe->data = (unsigned char *)malloc( BUFFERSIZE );
	assert( pipe->data );

	return p;
	}

int Sebae_Pipe_FromMemory( struct SebaeVM *vm, unsigned char *src, int len ) {
	struct SebaePipe *pipe;
	int p, b;
	
	assert( src );
	assert( len > 0 );
	assert( vm );

	/* ...just for now */
	assert( len < 16 * 1024 * 1024 );

	/* new pipe */
	p = Sebae_Pipe_New( vm );
	pipe = &vm->pipelist[p];

	/* initialize new pipe struct */
	pipe->type = mempipe;
	pipe->fd = -1;
	pipe->flen = len;
	pipe->bytes = 0;
	pipe->data = (unsigned char *)malloc( len );
	assert( pipe->data );
	
	/* copy the bytes */
	memcpy( pipe->data, src, len );

	/* print memory /
	printf( "new mem pipe %d, len %d:", p, len );
	for( b = 0; b < pipe->flen; b++ ) printf( " %02X", pipe->data[b] );
	printf( "\n" );/**/

	return p;
	}

int Sebae_Pipe_New( struct SebaeVM *vm ) {
	int p, n;
	
	assert( vm );
	
	/* find unused pipe id */
	for( p = 0; p < vm->sizepipelist; p++ ) if( vm->pipelist[p].type == unusedpipe ) break;
	
	/* grow pipelist */
	if( p == vm->sizepipelist ) {
		n = vm->sizepipelist;
		vm->sizepipelist += 4;
		vm->pipelist = (struct SebaePipe *)realloc( vm->pipelist, sizeof( struct SebaePipe ) * vm->sizepipelist );
		assert( vm->pipelist );
		
		/* reset the new pipes */
		for( ; n < vm->sizepipelist; n++ ) {
			vm->pipelist[n].type = unusedpipe;
			vm->pipelist[n].fd = -1;
			vm->pipelist[n].flen = -1;
			vm->pipelist[n].bytes = -1;
			vm->pipelist[n].data = NULL;
			}
		}
	
	/* new pipe */
	return p;
	}

int Sebae_Pipe_Read( struct SebaeVM *vm, int p, unsigned char *dest, int len ) {
	struct SebaePipe *pipe;
	unsigned char *remember;
	int ret;

	assert( dest );
	assert( len > 0 );
	assert( vm );
	assert( p > -1 );
	assert( p < vm->sizepipelist );
	
	/* pipe */
	pipe = &vm->pipelist[p];
	assert( pipe->data );
	
	/* writable pipe */
	if( pipe->type == filewritepipe ) return Sebae_Error( vm, "cannot read from a writable pipe" );
	
	/* mem pipe */
	else if( pipe->type == mempipe ) {
		assert( pipe->flen > 0 );
		
		/* bytes ready to read */
		ret = pipe->flen - pipe->bytes;
		assert( ret > -1 );
		
		/* no bytes left to read */
		if( ret == 0 ) return 0;
		
		/* requested bytes */
		if( ret > len ) ret = len;
		
		/* copy bytes to buffer */
		memcpy( dest, pipe->data + pipe->bytes, ret );
		
		/* more data has been read */
		pipe->bytes += ret;
		
		return ret;
		}

	/* not readable pipe */
	else if( pipe->type != filereadpipe ) return Sebae_Error( vm, "unknown pipe type" );

	assert( vm->pipelist[p].fd > -1 );
	assert( vm->pipelist[p].flen > -1 );
	
	/* read blocks no bigger than buffers */
	if( len > BUFFERSIZE ) len = BUFFERSIZE;
	
	/* need more bytes than is in data */
	if( pipe->bytes < len && pipe->flen > 0 ) {
		/* read more bytes */
		ret = read( pipe->fd, pipe->data + pipe->bytes, 1024 - pipe->bytes );
		
		/* give up if read fails */
		if( ret == -1 ) return Sebae_Error( vm, "read failed" );

		/* end of file */
		if( ret == 0 ) {
			pipe->flen = 0;
			Sebae_Error( vm, "file decreased in size since open()" );
			}
		
		/* we now have more bytes */
		pipe->bytes += ret;
		
		/* and fewer left to read */
		pipe->flen -= ret;
		}
	
	/* return only as many bytes as we have */
	if( pipe->bytes < len ) len = pipe->bytes;
	
	/* nothing to copy */
	if( len == 0 ) return 0;
	
	/* copy over the bytes */
	memcpy( dest, pipe->data, len );
	
	/* we now have fewer bytes */
	pipe->bytes -= len;

	/* copy remaining bytes to work buffer */
	memcpy( vm->workbuffer, pipe->data + len, pipe->bytes );
	
	/* swap buffers */
	remember = pipe->data;
	pipe->data = (unsigned char *)vm->workbuffer;
	vm->workbuffer = (char *)remember;
	
	return len;
	}
	
int Sebae_Pipe_ToFile( struct SebaeVM *vm, char *fname ) {
	struct SebaePipe *pipe;
	int fd, p;
	
	assert( fname );
	assert( vm );

	/* open output file */
	/* printf( "writing %s\n", fname );/**/
	fd = open( fname, O_WRONLY | O_CREAT | O_EXCL, 0666 );
	
	/* open failed */
	if( fd == -1 ) return Sebae_PError( vm, "could not open file" );
	
	/* new pipe */
	p = Sebae_Pipe_New( vm );
	pipe = &vm->pipelist[p];

	/* initialize new pipe struct */
	pipe->type = filewritepipe;
	pipe->fd = fd;
	pipe->flen = -1;
	pipe->bytes = 0;
	pipe->data = (unsigned char *)malloc( BUFFERSIZE );
	assert( pipe->data );

	return p;
	}

int Sebae_Pipe_Write( struct SebaeVM *vm, int p, unsigned char *src, int len ) {
	struct SebaePipe *pipe;
	int ret, towrite, place = 0;

	assert( src );
	assert( len > 0 );
	assert( vm );
	assert( p > -1 );
	assert( p < vm->sizepipelist );
	
	/* pipe */
	pipe = &vm->pipelist[p];
	
	/* not writable pipe */
	if( pipe->type != filewritepipe ) return Sebae_Error( vm, "pipe is not writable" );
	
	assert( pipe->fd > -1 );
	assert( pipe->flen < 0 );
	assert( pipe->data );

	/* loop and write BUFFERSIZE */
	while( len > 0 ) {
		/* space in buffer */
		towrite = BUFFERSIZE - pipe->bytes;
		
		/* how many to add to buffer */
		if( towrite > len ) towrite = len;
		
		/* add new bytes to buffer */
		memcpy( pipe->data + pipe->bytes, src + place, towrite );
		
		/* more bytes in buffer */
		pipe->bytes += towrite;
		
		/* fewer uncopied bytes */
		len -= towrite;

		/* uncopied bytes begin here */
		place += towrite;
		
		/* output buffer is full */
		if( pipe->bytes == BUFFERSIZE ) {
			/* commit buffer to file handle */
			ret = write( pipe->fd, pipe->data, pipe->bytes );
			
			/* write failed */
			if( ret == -1 ) return Sebae_PError( vm, "write failed" );
			
			/* incomplete write */
			if( ret != pipe->bytes ) return Sebae_Error( vm, "incomplete write" );
			
			/* buffer is now empty */
			pipe->bytes = 0;
			}
		}

	return place;
	}
