/* 
 * Javascript binding for Entity
 * Copyright (c) 2000 Brian Bassett and Ian Main
 *
 * Author: Ian Main <imain@gtk.org
 *         Brian Bassett <bbassett@bbassett.net>
 */

/* 
 * This renderer is free software; please see the LICENSE file for
 * specific information as to licensing conditions.
 */

/* 
 * $Source: /home/cvs/entity/renderers/javascript/js-embed.c,v $
 * $Id: js-embed.c,v 1.41 2000/12/14 22:53:33 imain Exp $
 */

#include <njs/njs.h>

#include "entity.h"
#include "js-embed.h"
#include "js-ENode.h"

static void
jse_init_types (JSInterpPtr interp)
{
    JSVirtualMachine *vm = interp->vm;

    njs_extension_entry (vm);
    js_Entity_ENodeAttrib (vm);

    return;
}

static JSInterpPtr
jse_create_compile_interp ()
{
    JSInterpOptions options;
    JSInterpPtr interp;

    js_init_default_options (&options);

    interp = js_create_interp (&options);
    if (interp == NULL) {
	g_warning ("javascript: Error creating JSInterpreter");
	return (NULL);
    }

    /* Make a smaller than average heap to force more * rigorous garbage
     * collection.  Usually this is 1024 * 1024. */
    interp->vm->gc.trigger = 1024L * 16L;

    if (!js_ext_default_directories (interp)) {
	g_warning ("javascript: Cannot load standard extension directories");
    }

    return (interp);
}

static JSInterpPtr
jse_create_interp ()
{
    JSInterpOptions options;
    JSInterpPtr interp;

    js_init_default_options (&options);

    options.warn_missing_semicolon = 1;
    options.warn_shadow = 1;
    options.warn_unused_variable = 1;
    options.no_compiler = 1;
    options.optimize_heavy = 1;
    /* options.stacktrace_on_error = 1; */

    interp = js_create_interp (&options);
    if (interp == NULL) {
	g_warning ("javascript: Error creating JSInterpreter");
	return (NULL);
    }

    /* Make a smaller than average heap to force more * rigorous garbage
     * collection.  Usually this is 1024 * 1024. */
    interp->vm->gc.trigger = 1024L * 64L;

    if (!js_ext_default_directories (interp)) {
	g_warning ("javascript: Cannot load standard extension directories");
    }

    if (!js_define_module (interp, jse_init_types)) {
	g_warning ("javascript: Cannot define ENode object type");
    }

    return interp;
}

static ENode *
jse_find_containing_object (ENode * node)
{
    if (ebuf_equal_str (node->element, "object"))
	return node;
    else
	return enode_parent (node, "object");
}

static void
jse_node_render (ENode * node)
{
    ENode *containing_object = jse_find_containing_object (node);
    EBufConst *data = enode_get_data (node);
    JSInterpPtr interp;
    static JSInterpPtr compile_interp = NULL;

    if (containing_object) {
	interp = (JSInterpPtr) enode_get_kv (containing_object, "js-interp");
    } else {
	g_warning ("<javascript> tags must go within <object>'s");
	return;
    }

    if (!compile_interp)
	compile_interp = jse_create_compile_interp ();

    /* Save reference node on stack.  Any functions or methods called dealing
     * * with enode lookups will use this as the reference. */
    enode_call_reference_push (node);

    EDEBUG (("javascript", "rendering"));

    /* Create our interpreter if we don't have one already. */
    if (interp == NULL) {
	interp = jse_create_interp ();
	if (!interp)
	    return;
	enode_set_kv (containing_object, "js-interp", interp);
    }

    if (ebuf_not_empty (data)) {
	unsigned char *bytecode;
	unsigned int codelen;
	int ret;

	ret =
	    js_compile_data_to_byte_code (compile_interp, data->str, data->len,
					  &bytecode, &codelen);
	if (!ret) {
	    g_warning ("javascript: byte compile failed in node %s.%s: %s",
		       node->element->str, 
		       enode_attrib_str (node, "name", NULL),
		       js_error_message (compile_interp));
	} else {
	    EDEBUG (("js-embed", "byte code compiled, length is %d", codelen));
	    ret = js_execute_byte_code (interp, bytecode, codelen);
	    if (!ret) {
		g_warning
		    ("javascript: error executing bytecode in node %s.%s: %s",
		     node->element->str, enode_attrib_str (node, "name", NULL),
		     js_error_message (interp));
	    }
	}
    }

    enode_call_reference_pop ();
    return;
}

static void
jse_node_destroy (ENode * node)
{
    EDEBUG (("javascript", "destroying"));

    /* Really ought to destroy the interpreters here. */

    /* Actually, what I'd probably recommend here, is attaching * a destroy
     * watcher to the containing object when you set * up the interpreter,
     * and use that callback to destroy it. * That way you don't have to
     * worry about multiple <javascript> * sections and the semantics
     * involved there. */
    return;
}

static EBuf *
jse_execute_function (ENode * node, gchar * function, GSList * args)
{
    GSList *tmp;
    LangArg *arg;
    gint n_args;
    JSNode *js_args = NULL;
    ENode *containing_object = jse_find_containing_object (node);
    JSInterpPtr interp =
	(JSInterpPtr) enode_get_kv (containing_object, "js-interp");
    static JSNode js_tmp;
    gint i = 1;

    if (interp == NULL) {
	g_warning
	    ("javascript function '%s' asked to be executed, but no interpreter has been created for this object.",
	     function);
	return (NULL);
    }

    n_args = g_slist_length (args);

    /* njs uses first arg as argument count */
    n_args++;
    js_args = js_calloc (interp->vm, 1, n_args * sizeof (JSNode));

    js_args[0].u.vinteger = n_args;
    js_args[0].type = JS_INTEGER;

    for (tmp = args; tmp; tmp = tmp->next) {
	arg = (LangArg *) tmp->data;

	if (arg->type == LANG_NODE) {
	    ENodeInstanceCtx *ni;
	    ENode *node = arg->data;
	    JSNode *enode_node;
	    JSBuiltinInfo *enode_info;

	    enode_node =
		&interp->vm->globals[js_vm_intern (interp->vm, "ENode")];
	    enode_info = enode_node->u.vbuiltin->info;

	    ni = js_calloc (interp->vm, 1, sizeof (*ni));
	    ni->enode = node;
	    enode_ref (node);
	    js_vm_builtin_create (interp->vm, &js_args[i], enode_info, ni);

	} else if (arg->type == LANG_STRING) {
	    char *str = arg->data;
	    js_vm_make_string (interp->vm, &js_args[i], str, strlen (str));
	    js_args[i].type = JS_STRING;
	} else if (arg->type == LANG_INT) {
	    js_args[i].type = JS_INTEGER;
	    js_args[i].u.vinteger = arg->intdata;
	} else if (arg->type == LANG_BINSTRING) {
	    char *str = arg->data;
	    int len = arg->size;
	    js_vm_make_string (interp->vm, &js_args[i], str, len);
	    js_args[i].type = JS_STRING;
	} else if (arg->type == LANG_DOUBLE) {
	    js_args[i].type = JS_FLOAT;
	    js_args[i].u.vfloat = arg->doubledata;
	}

	i++;
	enode_call_free_arg (arg);
    }
    EDEBUG (("javascript", "calling function '%s'", function));


    if (!interp->vm->consts) {
	g_print ("interp->vm->globals is NULL\n");
    }

    if (!js_vm_apply (interp->vm, function, &js_tmp, n_args, js_args)) {
	/* I doubt this is correct, but it works :) */
	g_warning ("Error executing function '%s', called from node %s.%s: %s",
		   function, node->element->str, enode_attrib_str (node, "name",
								   NULL),
		   interp->vm->error);
    }

    EDEBUG (("javascript", "call complete", function));

    g_free (js_args);

    return NULL;
}

void
#ifdef STATIC_JAVASCRIPT
javascript_init (RendererFlags flags)
#else
renderer_init (RendererFlags flags)
#endif
{
    Element *element;

    if (flags & RENDERER_REGISTER) {
	/* Register javascript as a tag type */
	element = g_malloc0 (sizeof (Element));
	element->render_func = jse_node_render;
	element->destroy_func = jse_node_destroy;
	element->description = "Embed JavaScript in your application.";
	element->tag = "javascript";

	element_register (element);

	/* Register javascript language type */
	language_register ("javascript", jse_execute_function);
    }
}


syntax highlighted by Code2HTML, v. 0.9.1