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

#include <assert.h>
#include <dlfcn.h>
#include <string.h>
#include "sebae.h"

/* OpenBSD doesn't have RTLD_NOW */
#ifndef RTLD_NOW
	#define RTLD_NOW 0
	#endif

int Sebae_Module_InstallMagicDefn( struct SebaeVM *vm, MagicDefnFunc magic, int consume, int produce ) {
	struct SebaeDefn *defn;
	
	/* new defn */
	defn = Sebae_VM_NewDefn( vm );
	assert( defn );
	defn->vector = -1;/* magic defn */
	defn->consume = consume;/* ret, name, magic */
	defn->produce = produce;/* vector, num, ret */
	defn->maxheight = (consume > produce)? consume : produce;
	defn->bytes = -1;
	defn->magic = magic;

	printf( "loaded magic 0x%08X at defn %d\n", magic, vm->numdefns - 1 );
	return 1;
	}

int Sebae_Module_DoMagicDefn( struct SebaeVM *vm ) {
	struct SebaeDefn *defn;
	int ret;
	
	assert( vm );
	assert( vm->defn > -1 );
	assert( vm->defn < vm->numdefns );
	
	/* magic defn */
	defn = &vm->defnlist[vm->defn];
	assert( defn->vector == -1 );
	assert( defn->magic );
	printf( "magic defn %d\n", vm->defn );

	/* call func */
	ret = defn->magic( vm );
	
	/* error */
	if( ret == -1 ) return -1;
	
	assert( vm->framesize == defn->produce );
	assert( vm->framesize > 0 );
	
	/* return defn id is at top of frame */
	ret = vm->frame[ vm->framesize - 1 ];
	printf( "JUMP %d;\t\t\t// defn %d\n", vm->framesize - 1, ret );
	
	/* return defn does not exist */
	if( ret < 0 || ret >= vm->numdefns ) return Sebae_Error( vm, "invalid destination for JUMP" );
	
	/* return defn */
	defn = &vm->defnlist[ret];
	
	/* 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 = ret;
	vm->insn = 0;
			
	/* new frame */
	vm->framesize = defn->consume;
	ret = vm->stackheight - vm->framesize;
	assert( ret > -1 );
	vm->frame = &vm->stack[ ret ];
	vm->fpframe = (float *)vm->frame;

	return 1;
	}

int Sebae_Module_Load( struct SebaeVM *vm ) {
	struct SebaeDefn *defn;
	int ret, addr, len, c, vector;
	MagicDefnFunc magic;
	char *name, *here;
	void *dllhandle;
	
	printf( "Sebae_ModuleLoad()\n" );
	
	assert( vm );
	assert( vm->defn > -1 );
	assert( vm->defn < vm->numdefns );
	
	/* defn */
	defn = &vm->defnlist[vm->defn];
	assert( defn->vector == -1 );
	assert( vm->framesize == 3 );
	
	/* return defn */
	ret = vm->frame[0];
	if( ret < 0 ) return Sebae_Error( vm, "invalid return defn" );
	vm->frame[2] = ret;
	
	/* vector of new defns */
	vector = vm->numdefns;
	
	/* memory address */
	addr = vm->frame[0];
	
	printf( "string is at %d\n", addr );
	
	/* memory does not exist */
	if( addr < 0 || addr > vm->memsize ) return Sebae_Error( vm, "memory access failure" );
	
	/* get the length of the module name */
	len = vm->mem[addr];
	
	/* convert from big-endian to little-endian */
	len = ((len >> 24) & 0xFF)
	     +((len >> 8)  & 0xFF00)
	     +((len << 8)  & 0xFF0000)
	     +((len << 24) & 0xFF000000);
	
	printf( "len is %d\n", len );

	/* string length is invalid */
	if( len < 1 || len > 65536 ) return Sebae_Error( vm, "invalid string length" );
	
	/* string extends past memory limit */
	if( len + addr > vm->memsize ) return Sebae_Error( vm, "memory access failure" );
	
	/* get memory for name */
	name = (char *)malloc( 2 + len + strlen( MODULESUFFIX ) + 1 );
	assert( name );
	here = name;
	
	/* prefix name with current directory */
	*here++ = '.';
	*here++ = '/';
	
	/* copy name from VM memory */
	addr++;
	while( len > 0 ) {
		/* get the character */
		c = vm->mem[addr];
		
		/* convert from big-endian to little-endian */
		c = ((c >> 24) & 0xFF)
		   +((c >> 8)  & 0xFF00)
		   +((c << 8)  & 0xFF0000)
		   +((c << 24) & 0xFF000000);
		
		/* only alpha numeric characters are allowed */
		if( isalnum( c ) == 0 ) return Sebae_Error( vm, "invalid character in module name" );
		
		/* store the character */
		*here = (char)c;
		
		/* next character */
		here++;
		addr++;
		len--;
		}
	
	/* null terminate */
	*here = 0;
	
	/* append suffix */
	strcat( name, MODULESUFFIX );
	
	printf( "name is \"%s\"\n", name );
	
	/* open the dll */
	dllhandle = dlopen( name, RTLD_NOW );
	
	/* open failed */
	if( dllhandle == NULL ) {
		printf( "dlopen failed for %s, %s\n", name, dlerror() );
		return Sebae_Error( vm, "could not load module" );
		}
	
	/* get the InitSebaeModule() func from dll */
	magic = (MagicDefnFunc)dlsym( dllhandle, "InitSebaeModule" );
	
	/* func not found */
	if( magic == NULL ) return Sebae_Error( vm, "dll is not a Sebae module" );
	
	/* call func */
	ret = (magic)( vm );
	
	/* func failed */
	if( ret == -1 ) return -1;
	
	/* vector of module */
	vm->frame[0] = vector;
	
	/* number of defns loaded */
	vm->frame[1] = vm->numdefns - vector;
	
	return 1;
	}

