/*
 *   Creation Date: <2003/11/24 12:30:18 samuel>
 *   Time-stamp: <2004/01/07 19:37:38 samuel>
 *
 *	<bindings.c>
 *
 *	Forth bindings
 *
 *   Copyright (C) 2003, 2004 Samuel Rydh (samuel@ibrium.se)
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   version 2
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "libc/string.h"
#include "libc/stdlib.h"
#include "libc/byteorder.h"


/************************************************************************/
/*	forth interface glue						*/
/************************************************************************/

void
push_str( const char *str )
{
	PUSH( pointer2cell(str) );
	PUSH( str ? strlen(str) : 0 );
}

/* WARNING: sloooow - AVOID */
cell
feval( const char *str )
{
	push_str( str );
	return eword("evaluate", 2);
}

cell
_eword( const char *word, xt_t *cache_xt, int nargs )
{
	static xt_t catch_xt = 0;
	cell ret = -1;

	if( !catch_xt )
		catch_xt = findword("catch");
	if( !*cache_xt )
		*cache_xt = findword( (char*)word );

	if( *cache_xt ) {
		PUSH_xt( *cache_xt );
		enterforth( catch_xt );
		if( (ret=POP()) )
			dstackcnt -= nargs;
	}
	return ret;
}

/* note: only the built-in dictionary is searched */
int
_fword( const char *word, xt_t *cache_xt )
{
	if( !*cache_xt )
		*cache_xt = findword( (char*)word );

	if( *cache_xt ) {
		enterforth( *cache_xt );
		return 0;
	}
	return -1;
}

int
_selfword( const char *method, xt_t *cache_xt )
{
	if( !*cache_xt )
		*cache_xt = find_ih_method( method, my_self() );
	if( *cache_xt ) {
		enterforth( *cache_xt );
		return 0;
	}
	return -1;
}

int
_parword( const char *method, xt_t *cache_xt )
{
	if( !*cache_xt )
		*cache_xt = find_ih_method( method, my_parent() );
	if( *cache_xt ) {
		enterforth( *cache_xt );
		return 0;
	}
	return -1;
}

void
bind_func( const char *name, void (*func)(void) )
{
	PUSH( pointer2cell(func) );
	push_str( name );
	fword("is-cfunc");
}

void
bind_xtfunc( const char *name, xt_t xt, ucell arg, void (*func)(void) )
{
	PUSH_xt( xt );
	PUSH( arg );
	PUSH( pointer2cell(func) );
	push_str( name );
	fword("is-xt-cfunc");
}

xt_t
bind_noname_func( void (*func)(void) )
{
	PUSH( pointer2cell(func) );
	fword("is-noname-cfunc");
	return POP_xt();
}

void
throw( int error )
{
	PUSH( error );
	fword("throw");
}


/************************************************************************/
/*	ihandle related							*/
/************************************************************************/

phandle_t
ih_to_phandle( ihandle_t ih )
{
	PUSH_ih( ih );
	fword("ihandle>phandle");
	return POP_ph();
}

ihandle_t
my_parent( void )
{
	fword("my-parent");
	return POP_ih();
}

ihandle_t
my_self( void )
{
	fword("my-self");
	return POP_ih();
}

xt_t
find_package_method( const char *method, phandle_t ph )
{
	push_str( method );
	PUSH_ph( ph );
	fword("find-method");
	if( POP() )
		return POP_xt();
	return 0;
}

xt_t
find_ih_method( const char *method, ihandle_t ih )
{
	return find_package_method( method, ih_to_phandle(ih) );
}


xt_t
find_parent_method( const char *method )
{
	return find_ih_method( method, my_parent() );
}

void
call_package( xt_t xt, ihandle_t ihandle )
{
	PUSH_xt( xt );
	PUSH_ih( ihandle );
	fword("call-package");
}

void
call_parent( xt_t xt )
{
	PUSH_xt( xt );
	fword("call-parent");
}

void
call_parent_method( const char *method )
{
	push_str( method );
	fword("$call-parent");
}


/************************************************************************/
/*	open/close package/dev						*/
/************************************************************************/

ihandle_t
open_dev( const char *spec )
{
	push_str( spec );
	fword("open-dev");
	return POP_ih();
}

void
close_dev( ihandle_t ih )
{
	PUSH_ih( ih );
	fword("close-dev");
}

ihandle_t
open_package( const char *argstr, phandle_t ph )
{
	push_str( argstr );
	PUSH_ph( ph );
	fword("open-package");
	return POP_ih();
}

void
close_package( ihandle_t ih )
{
	PUSH_ih( ih );
	fword("close-package");
}


/************************************************************************/
/*	ihandle arguments						*/
/************************************************************************/

char *
pop_fstr_copy( void )
{
	int len = POP();
	char *str, *p = (char*)cell2pointer(POP());
	if( !len )
		return NULL;
	str = malloc( len + 1 );
        if( !str )
                return NULL;
	memcpy( str, p, len );
	str[len] = 0;
	return str;
}

char *
my_args_copy( void )
{
	fword("my-args");
	return pop_fstr_copy();
}


/************************************************************************/
/*	properties							*/
/************************************************************************/

void
set_property( phandle_t ph, const char *name, const char *buf, int len )
{
	if( !ph ) {
		printk("set_property: NULL phandle\n");
		return;
	}
	PUSH(pointer2cell(buf));
	PUSH(len);
	push_str( name );
	PUSH_ph(ph);
	fword("set-property");
}

void
set_int_property( phandle_t ph, const char *name, u32 val )
{
	u32 swapped=__cpu_to_be32(val);
	set_property( ph, name, (char*)&swapped, sizeof(swapped) );
}

char *
get_property( phandle_t ph, const char *name, int *retlen )
{
	int len;

	if( retlen )
		*retlen = -1;

	push_str( name );
	PUSH_ph( ph );
	fword("get-package-property");
	if( POP() )
		return NULL;
	len = POP();
	if( retlen )
		*retlen = len;
	return (char*)cell2pointer(POP());
}

u32
get_int_property( phandle_t ph, const char *name, int *retlen )
{
	u32 *p;

	if( !(p=(u32 *)get_property(ph, name, retlen)) )
		return 0;
	return __be32_to_cpu(*p);
}


/************************************************************************/
/*	device selection / iteration					*/
/************************************************************************/

void
activate_dev( phandle_t ph )
{
	PUSH_ph( ph );
	fword("active-package!");
}

phandle_t
activate_device( const char *str )
{
	phandle_t ph = find_dev( str );
	activate_dev( ph );
	return ph;
}

void
device_end( void )
{
	fword("device-end");
}

phandle_t
get_cur_dev( void )
{
	fword("active-package");
	return POP_ph();
}

phandle_t
find_dev( const char *path )
{
	phandle_t ret = 0;
	push_str( path );
	fword("(find-dev)");
	if( POP() )
		return POP_ph();
	return ret;
}

char *
get_path_from_ph( phandle_t ph )
{
	PUSH(ph);
	fword("get-package-path");
	return pop_fstr_copy();
}

phandle_t
dt_iter_begin( void )
{
	fword("iterate-tree-begin");
	return POP_ph();
}

phandle_t
dt_iterate( phandle_t last_tree )
{
        if( !last_tree )
		return dt_iter_begin();

        PUSH_ph( last_tree );
	fword("iterate-tree");
	return POP_ph();
}

phandle_t
dt_iterate_type( phandle_t last_tree, const char *type )
{
        if( !last_tree )
                last_tree = dt_iter_begin();

	/* root node is never matched but we don't care about that */
        while( (last_tree = dt_iterate(last_tree)) ) {
                char *s = get_property( last_tree, "device_type", NULL );
		if( s && !strcmp(type, s) )
			break;
	}
        return last_tree;
}


/************************************************************************/
/*	node methods							*/
/************************************************************************/

void
make_openable( int only_parents )
{
	phandle_t ph, save_ph = get_cur_dev();
	PUSH_ph( save_ph );

	for( ;; ) {
		if( only_parents++ )
			fword("parent");
		if( !(ph=POP_ph()) )
			break;
		activate_dev( ph );
		PUSH_ph( ph );
		fword("is-open");
	}
	activate_dev( save_ph );
}

static void
call1_func( void )
{
	void (*func)(cell v);
	func = (void*)cell2pointer(POP());

	(*func)( POP() );
}


static void
add_methods( int flags, int size, const method_t *methods, int nmet )
{
	xt_t xt=0;
	int i;

	/* nodes might be matched multiple times */
	if( find_package_method(methods[0].name, get_cur_dev()) )
		return;

	if( size ) {
		PUSH( size );
		fword("is-ibuf");
		xt = POP_xt();
	}

	for( i=0; i<nmet; i++ ) {
		/* null-name methods specify static initializers */
		if( !methods[i].name ) {
			typedef void (*initfunc)( void *p );
			char *buf = NULL;
			if( xt ) {
				enterforth( xt );
				buf = (char*)cell2pointer(POP());
			}
			(*(initfunc)methods[i].func)( buf );
			continue;
		}
		if( !size )
			bind_func( methods[i].name, methods[i].func );
		else
			bind_xtfunc( methods[i].name, xt, pointer2cell(methods[i].func),
				     &call1_func );
	}

	if( flags & INSTALL_OPEN )
		make_openable(0);
}

void
bind_node( int flags, int size, const char * const *paths, int npaths,
	   const method_t *methods, int nmet )
{
	phandle_t save_ph = get_cur_dev();
	int i;

	for( i=0; i<npaths; i++ ) {
		const char *name = paths[i];

		/* type matching? */
		if( *name == 'T' ) {
			phandle_t ph = 0;
			name++;
			while( (ph=dt_iterate_type(ph, name)) ) {
				activate_dev( ph );
				add_methods( flags, size, methods, nmet );
			}
			continue;
		}

		/* path patching */
		if( activate_device(name) )
			add_methods( flags, size, methods, nmet );
		else if( *name == '+' ) {
			/* create node (and missing parents) */
			if( !activate_device(++name) ) {
				push_str( name );
				fword("create-node");
			}
			add_methods( flags, size, methods, nmet );
		}
	}
	activate_dev( save_ph );
}

phandle_t
bind_new_node( int flags, int size, const char *name,
	   const method_t *methods, int nmet )
{
	phandle_t save_ph = get_cur_dev();
	phandle_t new_ph;
	/* create node */
	push_str( name );
	fword("create-node");
	add_methods( flags, size, methods, nmet );
    new_ph = get_cur_dev();

	activate_dev( save_ph );
	return new_ph;
}
