/* binding output generator for jsapi(spidermonkey) to libdom
 *
 * This file is part of nsgenbind.
 * Published under the MIT License,
 *                http://www.opensource.org/licenses/mit-license.php
 * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
 */

#include <stdbool.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#include "options.h"
#include "nsgenbind-ast.h"
#include "webidl-ast.h"
#include "jsapi-libdom.h"

#define HDR_COMMENT_SEP "\n * \n * "
#define HDR_COMMENT_PREAMBLE "/* Generated by nsgenbind from %s\n"	\
	" *\n"								\
	" * nsgenbind is published under the MIT Licence.\n"		\
	" * nsgenbind is similar to a compiler is a purely transformative tool which\n" \
	" * explicitly makes no copyright claim on this generated output"

#define HDROUTF(bndg, fmt, args...) do {			\
		if (bndg->hdrfile != NULL) {			\
			fprintf(bndg->hdrfile, fmt, ##args);	\
		}						\
	} while(0)


static int webidl_file_cb(struct genbind_node *node, void *ctx)
{
	struct webidl_node **webidl_ast = ctx;
	char *filename;

	filename = genbind_node_gettext(node);

	return webidl_parsefile(filename, webidl_ast);
}

static int
read_webidl(struct genbind_node *genbind_ast, struct webidl_node **webidl_ast)
{
	int res;

	res = genbind_node_for_each_type(genbind_ast,
					 GENBIND_NODE_TYPE_WEBIDLFILE,
					 webidl_file_cb,
					 webidl_ast);

	/* debug dump of web idl AST */
	if (options->verbose) {
		webidl_ast_dump(*webidl_ast, 0);
	}
	return res;
}


static int webidl_preamble_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;

	fprintf(binding->outfile, "%s", genbind_node_gettext(node));

	return 0;
}

static int webidl_prologue_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;

	fprintf(binding->outfile, "%s", genbind_node_gettext(node));

	return 0;
}

static int webidl_epilogue_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;

	fprintf(binding->outfile, "%s", genbind_node_gettext(node));

	return 0;
}


static int webidl_hdrcomments_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;

	fprintf(binding->outfile,
		HDR_COMMENT_SEP"%s",
		genbind_node_gettext(node));

	return 0;
}

static int webidl_hdrcomment_cb(struct genbind_node *node, void *ctx)
{
	genbind_node_for_each_type(genbind_node_getnode(node),
				   GENBIND_NODE_TYPE_STRING,
				   webidl_hdrcomments_cb,
				   ctx);
	return 0;
}

static int webidl_private_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;
	struct genbind_node *ident_node;
	struct genbind_node *type_node;


	ident_node = genbind_node_find_type(genbind_node_getnode(node),
					    NULL,
					    GENBIND_NODE_TYPE_IDENT);
	if (ident_node == NULL)
		return -1; /* bad AST */

	type_node = genbind_node_find_type(genbind_node_getnode(node),
					    NULL,
					    GENBIND_NODE_TYPE_STRING);
	if (type_node == NULL)
		return -1; /* bad AST */

	fprintf(binding->outfile,
		"        %s%s;\n",
		genbind_node_gettext(type_node),
		genbind_node_gettext(ident_node));

	return 0;
}

static int webidl_private_param_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;
	struct genbind_node *ident_node;
	struct genbind_node *type_node;


	ident_node = genbind_node_find_type(genbind_node_getnode(node),
					    NULL,
					    GENBIND_NODE_TYPE_IDENT);
	if (ident_node == NULL)
		return -1; /* bad AST */

	type_node = genbind_node_find_type(genbind_node_getnode(node),
					    NULL,
					    GENBIND_NODE_TYPE_STRING);
	if (type_node == NULL)
		return -1; /* bad AST */

	fprintf(binding->outfile,
		",\n\t\t%s%s",
		genbind_node_gettext(type_node),
		genbind_node_gettext(ident_node));

	return 0;
}

static int webidl_private_assign_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;
	struct genbind_node *ident_node;
	const char *ident;

	ident_node = genbind_node_find_type(genbind_node_getnode(node),
					    NULL,
					    GENBIND_NODE_TYPE_IDENT);
	if (ident_node == NULL)
		return -1; /* bad AST */

	ident = genbind_node_gettext(ident_node);

	fprintf(binding->outfile, "\tprivate->%s = %s;\n", ident, ident);

	return 0;
}

/* section output generators */

/** Output the epilogue right at the end of the generated program */
static int
output_epilogue(struct binding *binding)
{
	genbind_node_for_each_type(binding->gb_ast,
				   GENBIND_NODE_TYPE_EPILOGUE,
				   webidl_epilogue_cb,
				   binding);

	fprintf(binding->outfile,"\n\n");

	if (binding->hdrfile) {
		binding->outfile = binding->hdrfile;

		fprintf(binding->outfile,
			"\n\n#endif /* _%s_ */\n",
			binding->hdrguard);

		binding->outfile = binding->srcfile;
	}

	return 0;
}

/** Output the prologue right before the generated function bodies */
static int
output_prologue(struct binding *binding)
{
	genbind_node_for_each_type(binding->gb_ast,
				   GENBIND_NODE_TYPE_PROLOGUE,
				   webidl_prologue_cb,
				   binding);

	fprintf(binding->outfile,"\n\n");

	return 0;
}


static int
output_api_operations(struct binding *binding)
{
	int res = 0;

	/* finalise */
	if (binding->has_private) {
		/* finalizer with private to free */
		fprintf(binding->outfile,
			"static void jsclass_finalize(JSContext *cx, JSObject *obj)\n"
			"{\n"
			"\tstruct jsclass_private *private;\n"
			"\n"
			"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
			binding->interface);

		if (options->dbglog) {
			fprintf(binding->outfile,
				"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}

		if (binding->finalise != NULL) {
			output_code_block(binding,
				genbind_node_getnode(binding->finalise));
		}

		fprintf(binding->outfile,
			"\tif (private != NULL) {\n"
			"\t\tfree(private);\n"
			"\t}\n"
			"}\n\n");
	} else if (binding->finalise != NULL) {
		/* finaliser without private data */
		fprintf(binding->outfile,
			"static void jsclass_finalize(JSContext *cx, JSObject *obj)\n"
			"{\n");

		if (options->dbglog) {
			fprintf(binding->outfile,
				"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
		}

		output_code_block(binding,
				  genbind_node_getnode(binding->finalise));

		fprintf(binding->outfile,
			"}\n\n");

	}

	/* generate class default property add implementation */
	if (binding->addproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_PROP(add, JSContext *cx, JSObject *obj, jsval *vp)\n"
			"{\n");

		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				binding->interface);
		
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
			}

		}


		output_code_block(binding,
				  genbind_node_getnode(binding->addproperty));

		fprintf(binding->outfile,
			"\treturn JS_TRUE;\n"
			"}\n\n");
	}

	/* generate class default property delete implementation */
	if (binding->delproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_PROP(del, JSContext *cx, JSObject *obj, jsval *vp)\n"
			"{\n");

		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				binding->interface);
		
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
			}

		}


		output_code_block(binding,
				  genbind_node_getnode(binding->delproperty));

		fprintf(binding->outfile,
			"\treturn JS_TRUE;\n"
			"}\n\n");
	}

	/* generate class default property get implementation */
	if (binding->getproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_PROP(get, JSContext *cx, JSObject *obj, jsval *vp)\n"
			"{\n");

		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				binding->interface);
		
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
			}

		}


		output_code_block(binding,
				  genbind_node_getnode(binding->getproperty));

		fprintf(binding->outfile,
			"\treturn JS_TRUE;\n"
			"}\n\n");
	}

	/* generate class default property set implementation */
	if (binding->setproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_STRICTPROP(set, JSContext *cx, JSObject *obj, jsval *vp)\n"
			"{\n");

		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				binding->interface);
		
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
			}

		}


		output_code_block(binding,
				  genbind_node_getnode(binding->setproperty));

		fprintf(binding->outfile,
			"\treturn JS_TRUE;\n"
			"}\n\n");
	}

	/* generate class enumerate implementation */
	if (binding->enumerate != NULL) {
		fprintf(binding->outfile,
			"static JSBool jsclass_enumerate(JSContext *cx, JSObject *obj)\n"
			"{\n");

		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				binding->interface);

			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
			}

		}

		output_code_block(binding, genbind_node_getnode(binding->enumerate));

		fprintf(binding->outfile,
			"\treturn JS_TRUE;\n"
			"}\n\n");
	}

	/* generate class resolver implementation */
	if (binding->resolve != NULL) {
		fprintf(binding->outfile,
			"static JSBool jsclass_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp)\n"
			"{\n");

		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				binding->interface);
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", cx, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", cx, obj);\n");
			}

		}


		output_code_block(binding, genbind_node_getnode(binding->resolve));

		fprintf(binding->outfile,
			"\treturn JS_TRUE;\n"
			"}\n\n");
	}

	/* generate trace/mark entry */
	if (binding->mark != NULL) {
		fprintf(binding->outfile,
			"static JSAPI_MARKOP(jsclass_mark)\n"
			"{\n");
		if (binding->has_private) {

			fprintf(binding->outfile,
				"\tstruct jsclass_private *private;\n"
				"\n"
				"\tprivate = JS_GetInstancePrivate(JSAPI_MARKCX, obj, &JSClass_%s, NULL);\n",
				binding->interface);

			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p private:%%p\", JSAPI_MARKCX, obj, private);\n");
			}
		} else {
			if (options->dbglog) {
				fprintf(binding->outfile,
					"\tJSLOG(\"jscontext:%%p jsobject:%%p\", JSAPI_MARKCX, obj);\n");
			}

		}

		output_code_block(binding, genbind_node_getnode(binding->mark));

		fprintf(binding->outfile,
			"\tJSAPI_MARKOP_RETURN(JS_TRUE);\n"
			"}\n\n");
	}
	return res;
}

void
output_code_block(struct binding *binding, struct genbind_node *codelist)
{
	struct genbind_node *code_node;

	code_node = genbind_node_find_type(codelist,
					   NULL,
					   GENBIND_NODE_TYPE_CBLOCK);
	if (code_node != NULL) {
		fprintf(binding->outfile,
			"%s\n",
			genbind_node_gettext(code_node));
	}
}

/** generate class initialiser which create the javascript class prototype */
static int
output_class_init(struct binding *binding)
{
	int res = 0;
	struct genbind_node *api_node;

	/* class Initialisor declaration */
	if (binding->hdrfile) {
		binding->outfile = binding->hdrfile;

	fprintf(binding->outfile,
		"JSObject *jsapi_InitClass_%s(JSContext *cx, JSObject *parent);\n",
		binding->interface);

		binding->outfile = binding->srcfile;
	}

	/* class Initialisor definition */
	fprintf(binding->outfile,
		"JSObject *jsapi_InitClass_%s(JSContext *cx, JSObject *parent)\n"
		"{\n"
		"\tJSObject *prototype;\n",
		binding->interface);

	api_node = genbind_node_find_type_ident(binding->gb_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "init");

	if (api_node != NULL) {
		output_code_block(binding, genbind_node_getnode(api_node));
	} else {
		fprintf(binding->outfile,
			"\n"
			"\tprototype = JS_InitClass(cx,\n"
			"\t\tparent,\n"
			"\t\tNULL,\n"
			"\t\t&JSClass_%s,\n"
			"\t\tNULL,\n"
			"\t\t0,\n"
			"\t\tNULL,\n"
			"\t\tNULL, \n"
			"\t\tNULL, \n"
			"\t\tNULL);\n",
			binding->interface);
	}

	output_const_defines(binding, binding->interface);

	fprintf(binding->outfile,
		"\treturn prototype;\n"
		"}\n\n");

	return res;
}

static int
output_class_new(struct binding *binding)
{
	int res = 0;
	struct genbind_node *api_node;

	/* constructor declaration */
	if (binding->hdrfile) {
		binding->outfile = binding->hdrfile;

		fprintf(binding->outfile,
			"JSObject *jsapi_new_%s(JSContext *cx,\n"
			"\t\tJSObject *prototype,\n"
			"\t\tJSObject *parent",
			binding->interface);

		genbind_node_for_each_type(binding->binding_list,
					   GENBIND_NODE_TYPE_BINDING_PRIVATE,
					   webidl_private_param_cb,
					   binding);

		fprintf(binding->outfile, ");");

		binding->outfile = binding->srcfile;
	}

	/* constructor definition */
	fprintf(binding->outfile,
		"JSObject *jsapi_new_%s(JSContext *cx,\n"
		"\t\tJSObject *prototype,\n"
		"\t\tJSObject *parent",
		binding->interface);

	genbind_node_for_each_type(binding->binding_list,
				   GENBIND_NODE_TYPE_BINDING_PRIVATE,
				   webidl_private_param_cb,
				   binding);

	fprintf(binding->outfile,
		")\n"
		"{\n"
		"\tJSObject *newobject;\n");

	/* create private data */
	if (binding->has_private) {
		fprintf(binding->outfile,
			"\tstruct jsclass_private *private;\n"
			"\n"
			"\tprivate = malloc(sizeof(struct jsclass_private));\n"
			"\tif (private == NULL) {\n"
			"\t\treturn NULL;\n"
			"\t}\n");

		genbind_node_for_each_type(binding->binding_list,
					   GENBIND_NODE_TYPE_BINDING_PRIVATE,
					   webidl_private_assign_cb,
					   binding);
	}

	api_node = genbind_node_find_type_ident(binding->gb_ast,
						NULL,
						GENBIND_NODE_TYPE_API,
						"new");

	if (api_node != NULL) {
		output_code_block(binding, genbind_node_getnode(api_node));
	} else {
		fprintf(binding->outfile,
			"\n"
			"\tnewobject = JS_NewObject(cx, &JSClass_%s, prototype, parent);\n",
			binding->interface);
	}

	if (binding->has_private) {
		fprintf(binding->outfile,
			"\tif (newobject == NULL) {\n"
			"\t\tfree(private);\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");

		/* root object to stop it being garbage collected */
		fprintf(binding->outfile,
			"\tif (JSAPI_ADD_OBJECT_ROOT(cx, &newobject) != JS_TRUE) {\n"
			"\t\tfree(private);\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");

		fprintf(binding->outfile,
			"\n"
			"\t/* attach private pointer */\n"
			"\tif (JS_SetPrivate(cx, newobject, private) != JS_TRUE) {\n"
			"\t\tfree(private);\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");


		/* attach operations and attributes (functions and properties) */
		fprintf(binding->outfile,
			"\tif (JS_DefineFunctions(cx, newobject, jsclass_functions) != JS_TRUE) {\n"
			"\t\tfree(private);\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");

		fprintf(binding->outfile,
			"\tif (JS_DefineProperties(cx, newobject, jsclass_properties) != JS_TRUE) {\n"
			"\t\tfree(private);\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");
	} else {
		fprintf(binding->outfile,
			"\tif (newobject == NULL) {\n"
			"\t\treturn NULL;\n"
			"\t}\n");

		/* root object to stop it being garbage collected */
		fprintf(binding->outfile,
			"\tif (JSAPI_ADD_OBJECT_ROOT(cx, &newobject) != JS_TRUE) {\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");

		/* attach operations and attributes (functions and properties) */
		fprintf(binding->outfile,
			"\tif (JS_DefineFunctions(cx, newobject, jsclass_functions) != JS_TRUE) {\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");

		fprintf(binding->outfile,
			"\tif (JS_DefineProperties(cx, newobject, jsclass_properties) != JS_TRUE) {\n"
			"\t\treturn NULL;\n"
			"\t}\n\n");
	}

	/* unroot object and return it */
	fprintf(binding->outfile,
		"\tJSAPI_REMOVE_OBJECT_ROOT(cx, &newobject);\n"
		"\n"
		"\treturn newobject;\n"
		"}\n");


	return res;
}

static int
output_jsclass(struct binding *binding)
{
	/* forward declare add property */
	if (binding->addproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_PROP(add, JSContext *cx, JSObject *obj, jsval *vp);\n\n");
	}

	/* forward declare del property */
	if (binding->delproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_PROP(del, JSContext *cx, JSObject *obj, jsval *vp);\n\n");
	}

	/* forward declare get property */
	if (binding->getproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_PROP(get, JSContext *cx, JSObject *obj, jsval *vp);\n\n");
	}

	/* forward declare set property */
	if (binding->setproperty != NULL) {
		fprintf(binding->outfile,
			"static JSBool JSAPI_STRICTPROP(set, JSContext *cx, JSObject *obj, jsval *vp);\n\n");
	}

	/* forward declare the enumerate */
	if (binding->enumerate != NULL) {
		fprintf(binding->outfile,
			"static JSBool jsclass_enumerate(JSContext *cx, JSObject *obj);\n\n");
	}

	/* forward declare the resolver */
	if (binding->resolve != NULL) {
		fprintf(binding->outfile,
			"static JSBool jsclass_resolve(JSContext *cx, JSObject *obj, jsval id, uintN flags, JSObject **objp);\n\n");
	}

	/* forward declare the GC mark operation */
	if (binding->mark != NULL) {
		fprintf(binding->outfile,
			"static JSAPI_MARKOP(jsclass_mark);\n\n");
	}

	/* forward declare the finalizer */
	if (binding->has_private || (binding->finalise != NULL)) {
		fprintf(binding->outfile,
			"static void jsclass_finalize(JSContext *cx, JSObject *obj);\n\n");
	}

	/* forward declare property list */
	fprintf(binding->outfile,
		"static JSPropertySpec jsclass_properties[];\n\n");

	/* output the class declaration */
	HDROUTF(binding, "JSClass JSClass_%s;\n", binding->interface);

	/* output the class definition */
	fprintf(binding->outfile,
		"JSClass JSClass_%s = {\n"
		"\t\"%s\",\n",
		binding->interface,
		binding->interface);

	/* generate class flags */
	if (binding->has_global) {
		fprintf(binding->outfile, "\tJSCLASS_GLOBAL_FLAGS");
	} else {
		fprintf(binding->outfile, "\t0");
	}

	if (binding->resolve != NULL) {
		fprintf(binding->outfile, " | JSCLASS_NEW_RESOLVE");
	}

	if (binding->mark != NULL) {
		fprintf(binding->outfile, " | JSAPI_JSCLASS_MARK_IS_TRACE");
	}

	if (binding->has_private) {
		fprintf(binding->outfile, " | JSCLASS_HAS_PRIVATE");
	}

	fprintf(binding->outfile, ",\n");

	/* add property */
	if (binding->addproperty != NULL) {
		fprintf(binding->outfile,
			"\tjsapi_property_add,\t/* addProperty */\n");
	} else {
		fprintf(binding->outfile,
			"\tJS_PropertyStub,\t/* addProperty */\n");
	}

	/* del property */
	if (binding->delproperty != NULL) {
		fprintf(binding->outfile,
			"\tjsapi_property_del,\t/* delProperty */\n");
	} else {
		fprintf(binding->outfile,
			"\tJS_PropertyStub,\t/* delProperty */\n");
	}

	/* get property */
	if (binding->getproperty != NULL) {
		fprintf(binding->outfile,
			"\tjsapi_property_get,\t/* getProperty */\n");
	} else {
		fprintf(binding->outfile,
			"\tJS_PropertyStub,\t/* getProperty */\n");
	}

	/* set property */
	if (binding->setproperty != NULL) {
		fprintf(binding->outfile,
			"\tjsapi_property_set,\t/* setProperty */\n");
	} else {
		fprintf(binding->outfile,
			"\tJS_StrictPropertyStub,\t/* setProperty */\n");
	}

	/* enumerate */
	if (binding->enumerate != NULL) {
		fprintf(binding->outfile,
			"\tjsclass_enumerate,\t/* enumerate */\n");
	} else {
		fprintf(binding->outfile,
			"\tJS_EnumerateStub,\t/* enumerate */\n");
	}

	/* resolver */
	if (binding->resolve != NULL) {
		fprintf(binding->outfile, "\t(JSResolveOp)jsclass_resolve,\n");
	} else {
		fprintf(binding->outfile, "\tJS_ResolveStub,\n");
	}

	fprintf(binding->outfile, "\tJS_ConvertStub,\t/* convert */\n");

	if (binding->has_private || (binding->finalise != NULL)) {
		fprintf(binding->outfile, "\tjsclass_finalize,\n");
	} else {
		fprintf(binding->outfile, "\tJS_FinalizeStub,\n");
	}
	fprintf(binding->outfile,
		"\t0,\t/* reserved */\n"
		"\tNULL,\t/* checkAccess */\n"
		"\tNULL,\t/* call */\n"
		"\tNULL,\t/* construct */\n"
		"\tNULL,\t/* xdr Object */\n"
		"\tNULL,\t/* hasInstance */\n");

	/* trace/mark */
	if (binding->mark != NULL) {
		fprintf(binding->outfile, "\tJSAPI_JSCLASS_MARKOP(jsclass_mark),\n");
	} else {
		fprintf(binding->outfile, "\tNULL, /* trace/mark */\n");
	}

	fprintf(binding->outfile,
		"\tJSAPI_CLASS_NO_INTERNAL_MEMBERS\n"
		"};\n\n");
	return 0;
}

static int
output_private_declaration(struct binding *binding)
{
	struct genbind_node *type_node;

	if (!binding->has_private) {
		return 0;
	}

	type_node = genbind_node_find(binding->binding_list,
				      NULL,
				      genbind_cmp_node_type,
				      (void *)GENBIND_NODE_TYPE_TYPE);

	if (type_node == NULL) {
		return -1;
	}

	fprintf(binding->outfile, "struct jsclass_private {\n");

	genbind_node_for_each_type(binding->binding_list,
				   GENBIND_NODE_TYPE_BINDING_PRIVATE,
				   webidl_private_cb,
				   binding);

	genbind_node_for_each_type(binding->binding_list,
				   GENBIND_NODE_TYPE_BINDING_INTERNAL,
				   webidl_private_cb,
				   binding);

	fprintf(binding->outfile, "};\n\n");


	return 0;
}

static int
output_preamble(struct binding *binding)
{
	genbind_node_for_each_type(binding->gb_ast,
				   GENBIND_NODE_TYPE_PREAMBLE,
				   webidl_preamble_cb,
				   binding);

	fprintf(binding->outfile,"\n\n");

	if (binding->hdrfile) {
		binding->outfile = binding->hdrfile;

		fprintf(binding->outfile,
			"#ifndef _%s_\n"
			"#define _%s_\n\n",
			binding->hdrguard,
			binding->hdrguard);

		binding->outfile = binding->srcfile;
	}


	return 0;
}


static int
output_header_comments(struct binding *binding)
{
	const char *preamble = HDR_COMMENT_PREAMBLE;
	fprintf(binding->outfile, preamble, options->infilename);

	genbind_node_for_each_type(binding->gb_ast,
				   GENBIND_NODE_TYPE_HDRCOMMENT,
				   webidl_hdrcomment_cb,
				   binding);

	fprintf(binding->outfile,"\n */\n\n");

	if (binding->hdrfile != NULL) {
		binding->outfile = binding->hdrfile;

		fprintf(binding->outfile, preamble, options->infilename);

		genbind_node_for_each_type(binding->gb_ast,
					   GENBIND_NODE_TYPE_HDRCOMMENT,
					   webidl_hdrcomment_cb,
					   binding);

		fprintf(binding->outfile,"\n */\n\n");

		binding->outfile = binding->srcfile;
	}
	return 0;
}

static bool
binding_has_private(struct genbind_node *binding_list)
{
	struct genbind_node *node;

	node = genbind_node_find_type(binding_list,
				      NULL,
				      GENBIND_NODE_TYPE_BINDING_PRIVATE);

	if (node != NULL) {
		return true;
	}

	node = genbind_node_find_type(binding_list,
				      NULL,
				      GENBIND_NODE_TYPE_BINDING_INTERNAL);
	if (node != NULL) {
		return true;
	}
	return false;
}

static bool
binding_has_global(struct binding *binding)
{
	struct genbind_node *api_node;

	api_node = genbind_node_find_type_ident(binding->gb_ast,
						NULL,
						GENBIND_NODE_TYPE_API,
						"global");
	if (api_node != NULL) {
		return true;
	}
	return false;
}

static struct binding *
binding_new(char *outfilename,
	    char *hdrfilename,
	    struct genbind_node *genbind_ast)
{
	struct binding *nb;
	struct genbind_node *binding_node;
	struct genbind_node *binding_list;
	struct genbind_node *ident_node;
	struct genbind_node *interface_node;
	FILE *outfile = NULL; /* output source file */
	FILE *hdrfile = NULL; /* output header file */
	char *hdrguard = NULL;
	struct webidl_node *webidl_ast = NULL;
	int res;

	binding_node = genbind_node_find_type(genbind_ast,
					 NULL,
					 GENBIND_NODE_TYPE_BINDING);
	if (binding_node == NULL) {
		return NULL;
	}

	binding_list = genbind_node_getnode(binding_node);
	if (binding_list == NULL) {
		return NULL;
	}

	ident_node = genbind_node_find_type(binding_list,
					    NULL,
					    GENBIND_NODE_TYPE_IDENT);
	if (ident_node == NULL) {
		return NULL;
	}

	interface_node = genbind_node_find_type(binding_list,
					   NULL,
					   GENBIND_NODE_TYPE_BINDING_INTERFACE);
	if (interface_node == NULL) {
		return NULL;
	}

	/* walk ast and load any web IDL files required */
	res = read_webidl(genbind_ast, &webidl_ast);
	if (res != 0) {
		fprintf(stderr, "Error reading Web IDL files\n");
		return NULL;
	}

	/* open output file */
	if (outfilename == NULL) {
		outfile = stdout;
	} else {
		outfile = fopen(outfilename, "w");
	}
	if (outfile == NULL) {
		fprintf(stderr, "Error opening source output %s: %s\n",
			outfilename,
			strerror(errno));
		return NULL;
	}

	/* output header file if required */
	if (hdrfilename != NULL) {
		int guardlen;
		int pos;

		hdrfile = fopen(hdrfilename, "w");
		if (hdrfile == NULL) {
			fprintf(stderr, "Error opening header output %s: %s\n",
				hdrfilename,
				strerror(errno));
			fclose(outfile);
			return NULL;
		}
		guardlen = strlen(hdrfilename);
		hdrguard = calloc(1, guardlen + 1);
		for (pos = 0; pos < guardlen; pos++) {
			if (isalpha(hdrfilename[pos])) {
				hdrguard[pos] = toupper(hdrfilename[pos]);
			} else {
				hdrguard[pos] = '_';
			}
		}
	}

	nb = calloc(1, sizeof(struct binding));

	nb->gb_ast = genbind_ast;
	nb->wi_ast = webidl_ast;
	nb->name = genbind_node_gettext(ident_node);
	nb->interface = genbind_node_gettext(interface_node);
	nb->outfile = outfile;
	nb->srcfile = outfile;
	nb->hdrfile = hdrfile;
	nb->hdrguard = hdrguard;
	nb->has_private = binding_has_private(binding_list);
	nb->has_global = binding_has_global(nb);
	nb->binding_list = binding_list;

	/* class API */
	nb->addproperty = genbind_node_find_type_ident(genbind_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "addproperty");

	nb->delproperty = genbind_node_find_type_ident(genbind_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "delproperty");

	nb->getproperty = genbind_node_find_type_ident(genbind_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "getproperty");

	nb->setproperty = genbind_node_find_type_ident(genbind_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "setproperty");

	nb->enumerate = genbind_node_find_type_ident(genbind_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "enumerate");

	nb->resolve = genbind_node_find_type_ident(genbind_ast,
						      NULL,
						      GENBIND_NODE_TYPE_API,
						      "resolve");

	nb->finalise = genbind_node_find_type_ident(genbind_ast,
						    NULL,
						    GENBIND_NODE_TYPE_API,
						    "finalise");

	nb->mark = genbind_node_find_type_ident(genbind_ast,
						    NULL,
						    GENBIND_NODE_TYPE_API,
						    "mark");
	return nb;
}


int
jsapi_libdom_output(char *outfilename,
		    char *hdrfilename,
		    struct genbind_node *genbind_ast)
{
	int res;
	struct binding *binding;

	/* get general binding information used in output */
	binding = binding_new(outfilename, hdrfilename, genbind_ast);
	if (binding == NULL) {
		return 40;
	}

	/* start with comment block */
	res = output_header_comments(binding);
	if (res) {
		return 50;
	}

	res = output_preamble(binding);
	if (res) {
		return 60;
	}

	res = output_private_declaration(binding);
	if (res) {
		return 70;
	}

	res = output_jsclass(binding);
	if (res) {
		return 80;
	}

	res = output_property_tinyid(binding);
	if (res) {
		return 85;
	}

	/* user code outout just before function bodies emitted */
	res = output_prologue(binding);
	if (res) {
		return 89;
	}

	/* operator and atrtribute body generation */

	res = output_operator_body(binding, binding->interface);
	if (res) {
		return 90;
	}

	res = output_property_body(binding);
	if (res) {
		return 100;
	}

	/* operator and atrtribute specifier generation */

	res = output_function_spec(binding);
	if (res) {
		return 110;
	}

	res = output_property_spec(binding);
	if (res) {
		return 120;
	}

	/* binding specific operations (destructors etc.) */

	res = output_api_operations(binding);
	if (res) {
		return 130;
	}

	res = output_class_init(binding);
	if (res) {
		return 140;
	}

	res = output_class_new(binding);
	if (res) {
		return 150;
	}

	res = output_epilogue(binding);
	if (res) {
		return 160;
	}

	fclose(binding->outfile);

	return 0;
}
