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

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

void Sebae_VM_AddInstruction( struct SebaeDefn *defn, int insn, int bytes ) {
	int i;
	
	assert( defn );
	assert( bytes > 0 );

	/* grow insnlist */
	if( defn->numinsns >= defn->sizeinsnlist ) {
		defn->sizeinsnlist += 16;
		defn->insnlist = (int *)realloc( defn->insnlist, sizeof( int ) * defn->sizeinsnlist );
		assert( defn->insnlist );

		/* initialize the new instructions */
		for( i = defn->numinsns; i < defn->sizeinsnlist; i++ ) defn->insnlist[i] = 0;
		}
	
	/* new insn */
	defn->insnlist[ defn->numinsns ] = insn;
	defn->bytes += bytes;
	defn->numinsns++;
	}

int Sebae_VM_Destroy( struct SebaeVM *vm ) {
	int ret, p;

	assert( vm );
	
	/* free pipes */
	for( p = 0; p < vm->sizepipelist; p++ ) {
		/* pipe is active */
		if( vm->pipelist[p].fd != -1 ) {
			/* free the pipe */
			ret = Sebae_Pipe_Free( vm, p );

			/* data commit failed */
			if( ret == -1 ) return -1;
			}
		}
	
	/* free pipe list */
	if( vm->sizepipelist ) {
		assert( vm->pipelist );
		free( vm->pipelist );
		}
	
	/* free stack */
	if( vm->stack != NULL ) free( vm->stack );
	
	/* free mem */
	if( vm->mem != NULL ) free( vm->mem );
	
	/* free vm */
	free( vm );
	
	return 1;
	}

void Sebae_VM_FreeDefn( struct SebaeVM *vm, int d ) {
	struct SebaeDefn *defn;
	
	assert( vm );
	assert( vm->numdefns > 0 );
	assert( vm->sizedefnlist >= vm->numdefns );
	assert( d > -1 );
	assert( d < vm->numdefns );

	/* defn */
	defn = &vm->defnlist[d];
	
	/* zero defn */
	defn->consume = -1;
	defn->consume = -1;
	defn->produce = -1;
	defn->numinsns = 0;
	defn->sizeinsnlist = 0;
	
	/* free instruction list */
	if( defn->insnlist != NULL ) free( defn->insnlist );
	
	vm->numdefns--;
	}

struct SebaeVM *Sebae_VM_New( int stacksize, int memsize ) {
	struct SebaeVM *vm;
	
	assert( stacksize >= 0 );
	assert( memsize >= 0 );
	
	/* allocate vm struct */
	vm = (struct SebaeVM *)malloc( sizeof( struct SebaeVM ) );
	assert( vm );
	
	/* initialize vm struct */
	vm->defn = 0;
	vm->insn = 0;
	vm->vector = 0;
	vm->height = 0;
	vm->datapipe = -1;
	
	vm->newdefn = NULL;
	vm->linenumber = -1;
	vm->workbuffer = (char *)malloc( BUFFERSIZE );
	assert( vm->workbuffer );
	vm->errortext = NULL;
	
	/* no code has been loaded */
	vm->numdefns = 0;
	vm->sizedefnlist = 0;
	vm->defnlist = NULL;
	
	/* no pipes yet */
	vm->sizepipelist = 0;
	vm->pipelist = NULL;
	
	/* stack */
	if( stacksize > 0 ) {
		/* allocate stack */
		vm->stack = (int *)malloc( sizeof( int ) * stacksize );
		assert( vm->stack );
		}
	else vm->stack = NULL;
	vm->stacksize = stacksize;
	vm->stackheight = 0;
	vm->frame = vm->stack;
	vm->fpframe = (float *)vm->frame;
	vm->framesize = 0;
	
	/* memory */
	if( memsize > 0 ) {
		/* allocate memory */
		vm->mem = (int *)malloc( sizeof( int ) * memsize );
		assert( vm->mem );
		}
	else vm->mem = NULL;
	vm->memsize = memsize;

	return vm;
	}

struct SebaeDefn *Sebae_VM_NewDefn( struct SebaeVM *vm ) {
	struct SebaeDefn *defn;
	int d;
	
	assert( vm );

	/* grow defnlist */
	if( vm->numdefns >= vm->sizedefnlist ) {
		vm->sizedefnlist += 256;
		vm->defnlist = (struct SebaeDefn *)realloc( vm->defnlist, sizeof( struct SebaeDefn ) * vm->sizedefnlist );
		assert( vm->defnlist );

		/* initialize the new defns */
		for( d = vm->numdefns; d < vm->sizedefnlist; d++ ) {
			defn = &vm->defnlist[d];
			defn->vector = -1;
			defn->consume = -1;
			defn->produce = -1;
			defn->numinsns = 0;
			defn->sizeinsnlist = 0;
			defn->insnlist = NULL;
			defn->bytes = 0;
			defn->magic = NULL;
			}
		}
	
	/* first defn in VM has id 1 */
	if( vm->numdefns == 0 ) vm->numdefns = 1;
	
	/* new defn */
	defn = &vm->defnlist[vm->numdefns];
	vm->numdefns++;
	
	return defn;
	}

void Sebae_VM_Rewind( struct SebaeVM *vm ) {
	int base;
	
	assert( vm );
	assert( vm->numdefns > 1 );
	assert( vm->defnlist[1].vector == 0 );
	assert( vm->defnlist[1].consume == 0 );

	vm->stackheight = 0;
	vm->frame = vm->stack;
	vm->fpframe = (float *)vm->frame;
	vm->framesize = 0;
	vm->defn = 1;
	vm->insn = 0;
	}

int Sebae_VM_Tick( struct SebaeVM *vm ) {
	struct SebaeDefn *defn;
	int i, a, b, c, d;
	float x, y, z;
	
	assert( vm );
	assert( vm->defn > -1 );
	assert( vm->defn < vm->numdefns );
	
	defn = &vm->defnlist[vm->defn];
	
	if( defn->vector < 0 ) return Sebae_Module_DoMagicDefn( vm );
	
	assert( vm->insn > -1 );
	assert( vm->insn < defn->numinsns );
	assert( defn->insnlist );
	
	/* get instruction */
	i = defn->insnlist[vm->insn++];
	assert( i == 0 || vm->insn < defn->numinsns );

	assert( i > -1 );
	assert( i < inum );
	
	switch( i ) {
		case 0: /* HALT */
			assert( vm->insn == defn->numinsns );
			assert( vm->framesize == defn->produce );
			
			printf( "HALT;\n" );
			return 0;

		case 1: /* JUMP element */
			assert( vm->framesize == defn->produce );

			/* element */
			a = defn->insnlist[vm->insn++];
			assert( vm->insn == defn->numinsns );
			assert( a > -1 && a < vm->framesize );
			
			/* destination */
			b = vm->frame[a];
			defn = &vm->defnlist[b];
			
			/* destination does not exist */
			if( b < 0 || b >= vm->numdefns ) return Sebae_Error( vm, "invalid destination for JUMP" );
			
			/* stack has no room for the elements defn will create */
			if( vm->stackheight + defn->maxheight >= vm->stacksize ) return Sebae_Error( vm, "stack overflow" );
			
			/* stack has not enough elements to be consumed by defn */
			if( vm->stackheight < defn->consume ) return Sebae_Error( vm, "stack underflow" );

			/* set new destination */
			vm->defn = b;
			vm->insn = 0;
			
			printf( "JUMP %d;\t\t\t// defn %d\n", a, b );

			/* new frame */
			vm->framesize = defn->consume;
			c = vm->stackheight - vm->framesize;
			assert( c > -1 );
			vm->frame = &vm->stack[ c ];
			vm->fpframe = (float *)vm->frame;
			
			return 1;
		
		case 2: /* LOADCODE element1 */
			
			/* vector of new defns */
			Sebae_VM_AddToStack( vm->numdefns );
			
			/* element1 */
			Sebae_VM_GetElement( a );

			/* address */
			b = vm->frame[a];

			printf( "LOADCODE %d;\t\t// load code from address %d\n", a, b );
		
			/* import ModuleLoad defn */
			if( b == -1 ) {
				/* will load just one magic defn */
				Sebae_VM_AddToStack( 1 );

				/* load it */
				return Sebae_Module_InstallMagicDefn( vm, Sebae_Module_Load, 3, 3 );
				}
			 
			/* memory does not exist */
			if( b < 0 || b >= vm->memsize ) return Sebae_Error( vm, "memory access failure" );
			
			/* get length of bytecode */
			c = vm->mem[b];
			
			/* convert from big-endian to little-endian */
			c = ((c >> 24) & 0xFF)
			   +((c >> 8) & 0xFF00)
			   +((c << 8) & 0xFF0000)
			   +((c << 24) & 0xFF000000);
	
			/* include length field */
			c += 4;

			/* how many memory chunks */
			d = c / 4;
			if( (c % 4) > 0 ) d++;
			
			/* bytecode doesn't fit in memory */
			if( b + d < 0 || b + d > vm->memsize ) return Sebae_Error( vm, "memory access failure (overflow)" );
			
			/* put bytecode into pipe */
			d = Sebae_Pipe_FromMemory( vm, (unsigned char *)&vm->mem[b], c );
			
			/* pipe failed */
			if( d == -1 ) return -1;
			
			/* read the bytecode */
			a = Sebae_Bytecode_Read( vm, d );
			
			/* invalid bytecode */
			if( a == -1 ) return -1;
			
			/* free the pipe */
			b = Sebae_Pipe_Free( vm, d );
			
			/* free failed */
			if( b == -1 ) return -1;
			
			/* good bytecode.  add to stack the num defns loaded */
			Sebae_VM_AddToStack( a );

			return 1;

		case 3: /* VECTOR */
			printf( "VECTOR;\t\t\t// frame[%d] = %d\n", vm->framesize, defn->vector );

			/* add it to the stack */
			Sebae_VM_AddToStack( defn->vector );

			return 1;
			
		case 4: /* PUSH value */
			/* value */
			a = defn->insnlist[vm->insn++];
			assert( vm->insn < defn->numinsns );
			
			printf( "PUSH 0x%08X;\t// frame[%d] = 0x%08X\n", a, vm->framesize, a );
			
			/* add it to the stack */
			Sebae_VM_AddToStack( a );

			return 1;
		
		case 5: /* POP */
			
			printf( "POP;\t\t\t// frameheight = %d\n", vm->framesize - 1 );
			
			/* remove top stack element */
			assert( vm->stackheight > 0 );
			vm->stackheight--;
			assert( vm->framesize > 0 );
			vm->framesize--;
			return 1;
			
		case 6: /* ASSIGN element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* take the value in element1 and put it in element2 */
			vm->frame[b] = vm->frame[a];
			
			printf( "ASSIGN %d %d;\t\t// frame[%d] = %d\n", a, b, b, vm->frame[a] );
			return 1;
		
		case 7: /* STACKSIZE */
			printf( "STACKSIZE;\t\t// frame[%d] = %d\n", vm->framesize, vm->stacksize );

			/* add it to the stack */
			Sebae_VM_AddToStack( vm->stacksize );

			return 1;

		case 8: /* STACKHEIGHT */
			printf( "STACKHEIGHT;\t\t\t// frame[%d] = %d\n", vm->framesize, vm->stackheight );

			/* add it to the stack */
			Sebae_VM_AddToStack( vm->stackheight );

			return 1;

		case 9: /* ADD element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* sum */
			c = vm->frame[a] + vm->frame[b];
			printf( "ADD %d %d;\t\t// frame[%d] = %d + %d == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );

			return 1;
			
		case 10: /* FPADD element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* sum */
			y = vm->fpframe[a] + vm->fpframe[b];
			printf( "FPADD %d %d;\t\t// frame[%d] = %f + %f == %f\n", a, b, vm->framesize, vm->fpframe[a], vm->fpframe[b], y );

			/* add it to the stack */
			Sebae_VM_AddToFPStack( y );
			
			return 1;
		
		case 11: /* SUBTRACT element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* subtract */
			c = vm->frame[a] - vm->frame[b];
			printf( "SUBTRACT %d %d;\t\t// frame[%d] = %d - %d == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 12: /* FPSUBTRACT element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* subtract */
			y = vm->fpframe[a] - vm->fpframe[b];
			printf( "FPSUBTRACT %d %d;\t\t// frame[%d] = %f - %f == %f\n", a, b, vm->framesize, vm->fpframe[a], vm->fpframe[b], y );

			/* add it to the stack */
			Sebae_VM_AddToFPStack( y );
			
			return 1;

		case 13: /* MULTIPLY element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* multiply */
			c = vm->frame[a] * vm->frame[b];
			printf( "MULTIPLY %d %d;\t\t// frame[%d] = %d * %d == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 14: /* FPMULTIPLY element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* multiply */
			y = vm->fpframe[a] * vm->fpframe[b];
			printf( "FPMULTIPLY %d %d;\t\t// frame[%d] = %f * %f == %f\n", a, b, vm->framesize, vm->fpframe[a], vm->fpframe[b], y );

			/* add it to the stack */
			Sebae_VM_AddToFPStack( y );
			
			return 1;
			
		case 15: /* DIVIDE element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* catch divide by zero */
			if( vm->frame[b] == 0 ) return Sebae_Error( vm, "divide by zero" );

			/* divide */
			c = vm->frame[a] / vm->frame[b];
			printf( "DIVIDE %d %d;\t\t// frame[%d] = %d / %d == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 16: /* FPDIVIDE element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* catch divide by zero */
			if( vm->fpframe[b] == 0 ) return Sebae_Error( vm, "divide by zero" );

			/* divide */
			y = vm->fpframe[a] / vm->fpframe[b];
			printf( "FPDIVIDE %d %d;\t\t// frame[%d] = %f / %f == %f\n", a, b, vm->framesize, vm->fpframe[a], vm->fpframe[b], y );

			/* add it to the stack */
			Sebae_VM_AddToFPStack( y );
			
			return 1;
			
		case 17: /* REMAINDER element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* catch divide by zero */
			if( vm->frame[b] == 0 ) return Sebae_Error( vm, "divide by zero" );

			/* remainder */
			c = vm->frame[a] % vm->frame[b];
			printf( "REMAINDER %d %d;\t\t// frame[%d] = %d % %d == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 18: /* FPREMAINDER element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* catch divide by zero */
			if( vm->fpframe[b] == 0 ) return Sebae_Error( vm, "divide by zero" );

			/* remainder */
			y = (float)fmod( vm->fpframe[a], vm->fpframe[b] );
			printf( "FPREMAINDER %d %d;\t\t// frame[%d] = %f % %f == %f\n", a, b, vm->framesize, vm->fpframe[a], vm->fpframe[b], y );

			/* add it to the stack */
			Sebae_VM_AddToFPStack( y );
			
			return 1;
			
		case 19: /* GREATERTHAN element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* greater than */
			c = (vm->frame[a] > vm->frame[b] )? 1 : 0;
			printf( "GREATERTHAN %d %d;\t// frame[%d] = %d > %d == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 20: /* FPGREATERTHAN element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* greater than */
			c = (vm->fpframe[a] > vm->fpframe[b] )? 1 : 0;
			printf( "FPGREATERTHAN %d %d;\t// frame[%d] = %f > %f == %f\n", a, b, vm->framesize, vm->fpframe[a], vm->fpframe[b], y );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 21: /* AND element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* and */
			c = vm->frame[a] & vm->frame[b];
			printf( "AND %d %d;\t\t// frame[%d] = 0x%08X & 0x%08X == 0x%08X\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 22: /* OR element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* or */
			c = vm->frame[a] | vm->frame[b];
			printf( "OR %d %d;\t\t// frame[%d] = 0x%08X | 0x%08X == 0x%08X\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 23: /* XOR element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* xor */
			c = vm->frame[a] ^ vm->frame[b];
			printf( "XOR %d %d;\t\t// frame[%d] = 0x%08X ^ 0x%08X == 0x%08X\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 24: /* ROTATE element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* bits to rotate */
			d = vm->frame[b];
			
			/* keep only lower 5 bits */
			d = d & 0x1F;
			
			/* no rotation */
			if( d == 0 ) c = vm->frame[a];

			/* rotate */
			else {
				/* right part */
				c = vm->frame[a] >> d;
				/* left part */
				c += vm->frame[a] << 32 - d;
				}
			
			printf( "ROTATE %d %d;\t\t// frame[%d] = 0x%08X rot %d == 0x%08X\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 25: /* EQUAL element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* equal */
			c = (vm->frame[a] == vm->frame[b] )? 1 : 0;
			printf( "EQUAL %d %d;\t\t// frame[%d] = (0x%08X == 0x%08X) == %d\n", a, b, vm->framesize, vm->frame[a], vm->frame[b], c );

			/* add it to the stack */
			Sebae_VM_AddToStack( c );
			
			return 1;
			
		case 26: /* DECIDE element1 element2 element3 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* element3 */
			Sebae_VM_GetElement( c );
			
			/* decide */
			if( vm->frame[c] == 1 ) d = vm->frame[a];
			else if( vm->frame[c] == 0 ) d = vm->frame[b];
			else return Sebae_Error( vm, "invalid instruction, DECIDE with neither 1 or 0" );

			printf( "DECIDE %d %d %d;\t\t// frame[%d] = (%d)? 0x%08X : 0x%08X == 0x%08X\n", a, b, c, vm->framesize, vm->frame[c], vm->frame[a], vm->frame[b], d );

			/* add it to the stack */
			Sebae_VM_AddToStack( d );
			
			return 1;
			
		case 27: /* LOAD element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* memory address */
			c = vm->frame[a];
			
			/* memory does not exist */
			if( c < 0 || c > vm->memsize ) return Sebae_Error( vm, "memory access failure" );
			
			/* get value from memory */
			d = vm->mem[c];
			
			/* convert from big-endian to little-endian */
			d = ((d >> 24) & 0xFF)
			   +((d >> 8) & 0xFF00)
			   +((d << 8) & 0xFF0000)
			   +((d << 24) & 0xFF000000);
			
			/* store value in element2 */
			vm->frame[b] = d;
			
			printf( "LOAD %d %d;\t\t// frame[%d] = mem[%d] == 0x%08X\n", a, b, b, c, d );

			return 1;
			
		case 28: /* SAVE element1 element2 */
			/* element1 */
			Sebae_VM_GetElement( a );

			/* element2 */
			Sebae_VM_GetElement( b );
			
			/* memory address */
			c = vm->frame[b];
			
			/* memory does not exist */
			if( c < 0 || c >= vm->memsize ) return Sebae_Error( vm, "invalid memory access" );
			
			/* load the value from element1 */
			d = vm->frame[a];

			printf( "SAVE %d %d;\t\t// mem[%d] = 0x%08X\n", a, b, c, d );

			/* convert from little-endian to big-endian */
			d = ((d >> 24) & 0xFF)
			   +((d >> 8) & 0xFF00)
			   +((d << 8) & 0xFF0000)
			   +((d << 24) & 0xFF000000);
			
			/* store value to memory */
			vm->mem[c] = d;

			return 1;

		case 29: /* MEMSIZE */
			printf( "MEMSIZE;\t\t// frame[%d] = %d\n", vm->framesize, vm->memsize );

			/* add it to the stack */
			Sebae_VM_AddToStack( vm->memsize );

			return 1;
		}
	
	return Sebae_Error( vm, "unknown instruction" );
	}
