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

#include <assert.h>
#include <stdlib.h>
#include "sebae.h"

int Sebae_Bytecode_LoadFile( struct SebaeVM *vm, char *fname ) {
	int ret, p;
	
	assert( vm );
	
	/* create pipe from file */
	p = Sebae_Pipe_FromFile( vm, fname );
	
	/* pipe creation failed */
	if( p == -1 ) return -1;

	/* read signature */
	ret = Sebae_Bytecode_ReadSignature( vm, p );
	
	/* signature not found */
	if( ret == -1 ) return -1;
	
	/* read the bytecode from pipe */
	ret = Sebae_Bytecode_Read( vm, p );
	
	/* bytecode read failed */
	if( ret == -1 ) return -1;
	
	/* data block is present */
	if( Sebae_Pipe_BytesAvailable( vm, p ) == 1 ) {
		/* set data pipe */
		vm->datapipe = p;
		}

	return 1;
	}

int Sebae_Bytecode_Read( struct SebaeVM *vm, int p ) {
	int dllen, ret, num = 0;
	
	assert( vm );
	
	/* vector of new defns */
	vm->vector = vm->numdefns;
	
	/* read defn list length */
	dllen = Sebae_Bytecode_ReadDefnListLength( vm, p );
	
	/* defn list length not found */
	if( dllen == -1 ) return -1;
	
	/* denf list length invalid */
	if( dllen < 3 ) return Sebae_Error( vm, "invalid bytecode.  defn-list-length is invalid" );
	
	/* read defns */
	while( dllen > 0 ) {
		/* read a defn */
		ret = Sebae_Bytecode_ReadDefn( vm, p );

		/* defn read failed */
		if( ret == -1 ) return -1;
		
		/* there are now fewer bytes of defn-list to read */
		dllen -= ret;
		
		/* loaded another defn */
		num++;
		}
	
	/* defn list length does not match that specified */
	if( dllen < 0 ) return Sebae_Error( vm, "invalid bytecode.  specified defn-list-length does not match the bytecode" );
	
	/* all defns read */
	return num;
	}

int Sebae_Bytecode_ReadDefn( struct SebaeVM *vm, int p ) {
	struct SebaeDefn *defn;
	unsigned char code[2];
	int ret;
	
	/* read bytes */
	ret = Sebae_Pipe_Read( vm, p, code, 2 );
	
	/* read failed */
	if( ret == -1 ) return -1;
	
	/* file too short */
	if( ret != 2 ) return Sebae_Error( vm, "invalid byetode.  defn not found" );

	/* new defn */
	defn = Sebae_VM_NewDefn( vm );
	assert( defn );
	defn->vector = vm->vector;
	defn->consume = (int)code[0];
	defn->produce = (int)code[1];
	defn->maxheight = defn->consume;
	defn->bytes = 2;
	
	/* printf( "DEFN %d %d;\n", defn->consume, defn->produce );/**/
	
	/* stack frame */
	vm->height = defn->consume;

	/* read instructions */
	ret = 2;
	while( ret > 1 ) ret = Sebae_Bytecode_ReadInstruction( defn, vm, p );
	
	/* error reading instructions */
	if( ret == -1 ) return -1;
	
	/* check stack frame */
	if( vm->height != defn->produce ) return Sebae_Error( vm, "invalid defn.  stack effect is incorrect" );

	return defn->bytes;
	}

int Sebae_Bytecode_ReadDefnListLength( struct SebaeVM *vm, int p ) {
	unsigned char code[4];
	int ret;

	/* read bytes */
	ret = Sebae_Pipe_Read( vm, p, code, 4 );
	
	/* read failed */
	if( ret == -1 ) return -1;
	
	/* file too short */
	if( ret != 4 ) return Sebae_Error( vm, "invalid bytecode.  defn-list-length field not found" );
	
	/* get big endian integer */
	ret = (code[0] << 24) & 0xFF000000;
	ret += (code[1] << 16) & 0xFF0000;
	ret += (code[2] << 8) & 0xFF00;
	ret += code[3] & 0xFF;

	return ret;
	}

int Sebae_Bytecode_ReadInstruction( struct SebaeDefn *defn, struct SebaeVM *vm, int pipe ) {
	struct InstructionSet *icode;
	unsigned char code[4];
	int ret, opcode, p;
	
	assert( defn );
	
	/* read instruction byte */
	ret = Sebae_Pipe_Read( vm, pipe, code, 1);
	
	/* read failed */
	if( ret == -1 ) return -1;
	
	/* file too short */
	if( ret != 1 ) return Sebae_Error( vm, "invalid bytecode.  instruction not found" );
	
	/* get opcode */
	opcode = (int)code[0];
	
	/* check that instruction is valid */
	if( opcode < 0 || opcode > inum ) return Sebae_Error( vm, "invalid bytecode.  invalid instruction found" );
	
	/* find the instruction */
	icode = &iset[opcode];
	
	/* add the instruction */
	Sebae_VM_AddInstruction( defn, opcode, 1 );
	/* printf( "  %s", icode->name );/**/
	
	/* read the parameters */
	for( p = 0; p < icode->parameters; p++ ) {
		/* read bytes */
		ret = Sebae_Pipe_Read( vm, pipe, code, 1 );
		
		/* read failed */
		if( ret == -1 ) return -1;
		
		/* file too short */
		if( ret != 1 ) return Sebae_Error( vm, "invalid bytecode.  file too short, partial instruction found" );
	
		/* parameter */
		ret = (int)code[0];

		/* add the parameter */
		Sebae_VM_AddInstruction( defn, ret, 1 );
		/* printf( " %d", ret );/**/
		}
	
	/* 32bit parm */
	if( icode->parameters == -1 ) {
		/* read bytes */
		ret = Sebae_Pipe_Read( vm, pipe, code, 4 );
		
		/* read failed */
		if( ret == -1 ) return -1;
		
		/* file too short */
		if( ret != 4 ) return Sebae_Error( vm, "invalid bytecode.  file too short, partial instruction found" );
	
		/* parameter */
		ret = (code[0] << 24) & 0xFF000000;
		ret += (code[1] << 16) & 0xFF0000;
		ret += (code[2] << 8) & 0xFF00;
		ret += code[3] & 0xFF;

		/* add the parameter */
		Sebae_VM_AddInstruction( defn, ret, 4 );
		/* printf( " 0x%08X", ret );/**/
		}
	
	/* adjust stack frame */
	vm->height += icode->stackeffect;
	
	/* check for stack frame overflow */
	if( vm->height > 255 ) return Sebae_Error( vm, "invalid bytecode.  stack frame reaches a height of >255" );

	/* stack frame is higher than any previous time */
	if( vm->height > defn->maxheight ) defn->maxheight = vm->height;
	
	/* printf( ";\n" );/**/
	return opcode;
	}

int Sebae_Bytecode_ReadSignature( struct SebaeVM *vm, int p ) {
	unsigned char code[4];
	int ret;

	/* read bytes */
	ret = Sebae_Pipe_Read( vm, p, code, 4 );
	
	/* read failed */
	if( ret == -1 ) return -1;
	
	/* file too short */
	if( ret != 4 ) return Sebae_Error( vm, "file too short, signature not found" );
	
	/* signature does not match */
	if( code[0] != 'S' || code[1] != 'E' || code[2] != 'B' || code[3] != '1' ) {
		return Sebae_Error( vm, "file lacks proper signature" );
		}
	
	/* signature matches */
	return 1;
	}

int Sebae_Bytecode_Write( struct SebaeVM *vm, int p ) {
	int d, dllen, ret;
	
	assert( vm );
	assert( vm->numdefns > 0 );
	assert( vm->defnlist );

	/* write defn list length */
	dllen = Sebae_Bytecode_WriteDefnListLength( vm, p );
	
	/* defn list length not found */
	if( dllen == -1 ) return -1;
	
	/* sanity */
	assert( dllen >= 3 );
	
	/* write defns */
	for( d = 0; d < vm->numdefns; d++ ) {
		
		/* defn is valid */
		if( vm->defnlist[d].vector != -1 ) {
			/* write the defn */
			ret = Sebae_Bytecode_WriteDefn( vm, p, &vm->defnlist[d] );
			
			/* defn write failed */
			if( ret == -1 ) return -1;
		
			/* there are now fewer bytes of defn-list to write */
			dllen -= ret;
			}
		}
	
	assert( dllen == 0 );
	
	/* all defns written */
	return 1;
	}

int Sebae_Bytecode_WriteDefn( struct SebaeVM *vm, int p, struct SebaeDefn *defn ) {
	struct InstructionSet *icode;
	unsigned char code[4];
	int i, opcode, parm, ret, byteswritten, height;
	
	assert( defn );
	assert( defn->vector > -1 );
	
	/* consume */
	code[0] = (unsigned char)defn->consume;

	/* produce */
	code[1] = (unsigned char)defn->produce;

	/* write bytes */
	ret = Sebae_Pipe_Write( vm, p, code, 2 );
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	/* printf( "DEFN %d %d;\n", defn->consume, defn->produce );/**/
	byteswritten = 2;
	
	/* stack frame */
	height = defn->consume;

	/* write instructions */
	i = 0;
	while( defn->numinsns - i > 0 ) {
		/* opcode */
		opcode = defn->insnlist[i++];
		
		/* instruction */
		code[0] = (unsigned char)opcode;
		
		/* write instruction byte */
		ret = Sebae_Pipe_Write( vm, p, code, 1 );
		byteswritten += 1;
	
		/* write failed */
		if( ret == -1 ) return -1;
		
		/* sanity */
		assert( opcode > -1 );
		assert( opcode < inum );
	
		/* find the instruction */
		icode = &iset[opcode];

		/* printf( "  %s", icode->name );/**/
		
		/* write the parameters */
		for( parm = 0; parm < icode->parameters; parm++ ) {
			/* sanity */
			assert( defn->numinsns - i > 0 );
			
			/* parameter */
			code[0] = (unsigned char)defn->insnlist[i++];

			/* write byte */
			ret = Sebae_Pipe_Write( vm, p, code, 1 );
			byteswritten += 1;
			
			/* write failed */
			if( ret == -1 ) return -1;
			
			/* printf( " %d", ret );/**/
			}
		
		/* 32bit parm */
		if( icode->parameters == -1 ) {
			/* sanity */
			assert( defn->numinsns - i > 0 );

			/* parameter */
			code[0] = (unsigned char)((defn->insnlist[i] >> 24) & 0xFF);
			code[1] = (unsigned char)((defn->insnlist[i] >> 16) & 0xFF);
			code[2] = (unsigned char)((defn->insnlist[i] >> 8) & 0xFF);
			code[3] = (unsigned char)(defn->insnlist[i++] & 0xFF);

			/* write bytes */
			ret = Sebae_Pipe_Write( vm, p, code, 4 );
			byteswritten += 4;
			
			/* write failed */
			if( ret == -1 ) return -1;
			
			/* printf( " 0x%08X", ret );/**/
			}
		
		/* adjust stack frame */
		height += icode->stackeffect;
		
		/* sanity */
		assert( height < 256 );
		
		/* printf( ";\n" );/**/
		
		/* defn terminal instruction */
		if( opcode < 2 ) break;
		}

	/* sanity */
	assert( i == defn->numinsns );
	assert( height == defn->produce );
	assert( byteswritten == defn->bytes );

	/* normal defn */
	return byteswritten;
	}

int Sebae_Bytecode_WriteDefnListLength( struct SebaeVM *vm, int p ) {
	unsigned char code[4];
	int d, dllen = 0, ret;

	assert( vm );
	assert( vm->numdefns > 0 );
	assert( vm->defnlist );
	
	/* tally up lengths */
	for( d = 0; d < vm->numdefns; d++ ) {
		/* defn is valid */
		if( vm->defnlist[d].vector != -1 ) {
			/* add defn bytes to tally */
			dllen += vm->defnlist[d].bytes;
			}
		}
	
	/* sanity */
	assert( dllen > 2 );
	
	/* defn list length */
	code[0] = (dllen >> 24) & 0xFF;
	code[1] = (dllen >> 16) & 0xFF;
	code[2] = (dllen >> 8) & 0xFF;
	code[3] = dllen & 0xFF;
	
	/* write bytes */
	ret = Sebae_Pipe_Write( vm, p, code, 4 );
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	return dllen;
	}

int Sebae_Bytecode_WriteFile( struct SebaeVM *vm, char *fname ) {
	int ret, p;
	char *buffer;

	assert( vm );
	
	/* create pipe from file */
	p = Sebae_Pipe_ToFile( vm, fname );

	/* pipe creation failed */
	if( p == -1 ) return -1;

	/* write signature */
	ret = Sebae_Bytecode_WriteSignature( vm, p );
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	/* write the bytecode to the pipe */
	ret = Sebae_Bytecode_Write( vm, p );
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	/* data pipe is present */
	if( vm->datapipe > -1 ) {
		/* allocate buffer */
		buffer = (char *)malloc( BUFFERSIZE );
		assert( buffer );
		
		/* copy data block to bytecode file */
		while( 1 ) {
			/* read chunk */
			ret = Sebae_Pipe_Read( vm, vm->datapipe, (unsigned char *)buffer, BUFFERSIZE );
			if( ret == 0 ) break;
			if( ret == -1 ) return -1;
	
			ret = Sebae_Pipe_Write( vm, p, (unsigned char *)buffer, ret );
			if( ret == -1 ) return -1;
			}
		}

	/* close the bytecode file and free the pipe */
	ret = Sebae_Pipe_Free( vm, p );
	
	/* data commit failed */
	if( ret == -1 ) return -1;

	return 1;
	}

int Sebae_Bytecode_WriteSignature( struct SebaeVM *vm, int p ) {
	unsigned char code[5] = { 'S', 'E', 'B', '1', 0 };
	int ret;

	/* write bytes */
	ret = Sebae_Pipe_Write( vm, p, code, 4);
	
	/* write failed */
	if( ret == -1 ) return -1;
	
	return 1;
	}
