/* property generation
 *
 * This file is part of nsgenbind.
 * Licensed 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 "options.h"
#include "nsgenbind-ast.h"
#include "webidl-ast.h"
#include "jsapi-libdom.h"

static int generate_property_tinyid(struct binding *binding, const char *interface);
static int generate_property_spec(struct binding *binding, const char *interface);


/* generate context data fetcher if the binding has private data */
static inline int
output_private_get(struct binding *binding,
		   struct binding_interface *inf,
		   const char *argname)
{
	int ret = 0;

	if (binding->has_private) {

		ret = fprintf(binding->outfile,
			      "\tstruct jsclass_private *%s;\n\n", argname);

		if (inf->refcount == 0) {
			/* leaf class so use safer getinstance private */
			ret += fprintf(binding->outfile,
				      "\t%s = JS_GetInstancePrivate(cx, obj, &JSClass_%s, NULL);\n",
				      argname,
				      inf->name);
		} else {
			ret += fprintf(binding->outfile,
				      "\t%s = JS_GetPrivate(obj);\n",
				      argname);
		}

		ret += fprintf(binding->outfile,
			      "\tif (%s == NULL) {\n"
			      "\t\treturn JS_FALSE;\n"
			      "\t}\n\n",
			      argname);

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

	}

	return ret;
}

/* generate vars for property name getter */
static inline int
output_property_name_get_vars(struct binding *binding, const char *argname)
{
	/* get property name */
	return fprintf(binding->outfile,
		       "\tjsval %s_jsval;\n"
		       "\tJSString *%s_jsstr = NULL;\n"
		       "\tint %s_len = 0;\n"
		       "\tchar *%s = NULL;\n",
		       argname, argname, argname, argname);
}

/* generate vars for property tinyid getter */
static inline int
output_property_tinyid_get_vars(struct binding *binding, const char *argname)
{
	/* get property name */
	return fprintf(binding->outfile,
		       "\tjsval %s_jsval;\n"
		       "\tint8_t %s = JSAPI_PROP_TINYID_END;\n",
		       argname, argname);
}

/* generate property name getter */
static inline int
output_property_name_get(struct binding *binding, const char *argname)
{
	/* get property name */
	return fprintf(binding->outfile,
		       "\t /* obtain property name */\n"
		       "\tJSAPI_PROP_IDVAL(cx, &%s_jsval);\n"
		       "\t%s_jsstr = JS_ValueToString(cx, %s_jsval);\n"
		       "\tif (%s_jsstr != NULL) {\n"
		       "\t\tJSString_to_char(%s_jsstr, %s, %s_len);\n"
		       "\t}\n\n",
		       argname,argname,argname,argname,argname,argname,argname);
}

/* generate property name getter */
static inline int
output_property_tinyid_get(struct binding *binding, const char *argname)
{
	/* get property name */
	return fprintf(binding->outfile,
		       "\t /* obtain property tinyid */\n"
		       "\tJSAPI_PROP_IDVAL(cx, &%s_jsval);\n"
		       "\t%s = JSVAL_TO_INT(%s_jsval);\n\n",
		       argname, argname, argname);
}


/******************************** tinyid ********************************/

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

	ident_node = webidl_node_find_type(webidl_node_getnode(node),
					   NULL,
					   WEBIDL_NODE_TYPE_IDENT);
	ident = webidl_node_gettext(ident_node);
	if (ident == NULL) {
		/* properties must have an operator
		 * http://www.w3.org/TR/WebIDL/#idl-attributes
		 */
		return -1;
	}

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

	return 0;
}


static int
generate_property_tinyid(struct binding *binding, const char *interface)
{
	struct webidl_node *interface_node;
	struct webidl_node *members_node;
	struct webidl_node *inherit_node;
	int res = 0;

	/* find interface in webidl with correct ident attached */
	interface_node = webidl_node_find_type_ident(binding->wi_ast,
						     WEBIDL_NODE_TYPE_INTERFACE,
						     interface);

	if (interface_node == NULL) {
		fprintf(stderr,
			"Unable to find interface %s in loaded WebIDL\n",
			interface);
		return -1;
	}

	/* generate property entries for each list (partial interfaces) */
	members_node = webidl_node_find_type(webidl_node_getnode(interface_node),
					NULL,
					WEBIDL_NODE_TYPE_LIST);

	while (members_node != NULL) {

		fprintf(binding->outfile,"\t/**** %s ****/\n", interface);

		/* for each property emit a JSAPI_PS() */
		webidl_node_for_each_type(webidl_node_getnode(members_node),
					  WEBIDL_NODE_TYPE_ATTRIBUTE,
					  webidl_property_tinyid_cb,
					  binding);

		members_node = webidl_node_find_type(webidl_node_getnode(interface_node),
						members_node,
						WEBIDL_NODE_TYPE_LIST);
	}

	/* check for inherited nodes and insert them too */
	inherit_node = webidl_node_find(webidl_node_getnode(interface_node),
					NULL,
					webidl_cmp_node_type,
					(void *)WEBIDL_NODE_TYPE_INTERFACE_INHERITANCE);

	if (inherit_node != NULL) {
		res = generate_property_tinyid(binding, webidl_node_gettext(inherit_node));
	}

	return res;
}

/* exported interface documented in jsapi-libdom.h */
int
output_property_tinyid(struct binding *binding)
{
	int res;

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

	res = generate_property_tinyid(binding, binding->interface);

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

	return res;
}



/******************************** specifier ********************************/


/* search binding for property sharing modifier */
static enum genbind_type_modifier
get_binding_shared_modifier(struct binding *binding, const char *type, const char *ident)
{
	struct genbind_node *shared_node;
	struct genbind_node *shared_mod_node;
	enum genbind_type_modifier *shared_modifier;

	/* look for node matching the ident first */
	shared_node = genbind_node_find_type_ident(binding->binding_list,
				   NULL,
				   GENBIND_NODE_TYPE_BINDING_PROPERTY,
				   ident);

	/* look for a node matching the type */
	if (shared_node == NULL) {
		shared_node = genbind_node_find_type_ident(binding->binding_list,
					   NULL,
					   GENBIND_NODE_TYPE_BINDING_PROPERTY,
					   type);

	}


	if (shared_node != NULL) {
		/* no explicit shared status */
		shared_mod_node = genbind_node_find_type(genbind_node_getnode(shared_node),
						NULL,
						GENBIND_NODE_TYPE_MODIFIER);
		shared_modifier = (enum genbind_type_modifier *)genbind_node_getint(shared_mod_node);
		if (shared_modifier != NULL) {
			return *shared_modifier;
		}

	}
	return GENBIND_TYPE_NONE;
}

/* obtain the value for a key/value type  extended attribute */
static char *
get_keyval_extended_attribute(struct webidl_node *node, const char *attribute)
{
	struct webidl_node *ext_node;
	struct webidl_node *ident_node;
	char *value;

	ext_node = webidl_node_find_type_ident(webidl_node_getnode(node),
					    WEBIDL_NODE_TYPE_EXTENDED_ATTRIBUTE,
					       attribute);

	if (ext_node == NULL) {
		return NULL; /* no matching extended attribute at all */
	}

	/* should be extended atrribute name node */
	ident_node = webidl_node_find_type(webidl_node_getnode(ext_node),
					   NULL,
					   WEBIDL_NODE_TYPE_IDENT);
	if (ident_node == NULL) {
		/* not the attribute name already matched - bail
		 * somethings broken
		 */
		return NULL;
	}
	value = webidl_node_gettext(ident_node);
	if (strcmp(attribute, value) != 0) {
		/* not the attribute name already matched - bail
		 * somethings broken
		 */
		return NULL;
	}

	/* should be an = sign for key/value pair */
	ident_node = webidl_node_find_type(webidl_node_getnode(ext_node),
					   ident_node,
					   WEBIDL_NODE_TYPE_IDENT);
	if (ident_node == NULL) {
		/* no additional attribute - not key/value then */
		return NULL;
	}
	value = webidl_node_gettext(ident_node);
	if (strcmp("=", value) != 0) {
		/* not a key/value pair then */
		return NULL;
	}

	/* value */
	ident_node = webidl_node_find_type(webidl_node_getnode(ext_node),
					   ident_node,
					   WEBIDL_NODE_TYPE_IDENT);
	if (ident_node == NULL) {
		/* no value */
		return NULL;
	}
	value = webidl_node_gettext(ident_node);

	return value;
}

static bool property_is_ro(struct webidl_node *node)
{
	struct webidl_node *modifier_node;
	modifier_node = webidl_node_find_type(webidl_node_getnode(node),
					      NULL,
					      WEBIDL_NODE_TYPE_MODIFIER);

	if (webidl_node_getint(modifier_node) == WEBIDL_TYPE_READONLY) {
		return true;
	}

	return false;
}

static int webidl_property_spec(struct binding *binding,
				struct binding_interface *inf,
				struct webidl_node *node)
{
	struct webidl_node *type_node;
	const char *type = NULL;
	struct webidl_node *ident_node;
	const char *ident;
	bool ro = false;

	ident_node = webidl_node_find_type(webidl_node_getnode(node),
					   NULL,
					   WEBIDL_NODE_TYPE_IDENT);
	ident = webidl_node_gettext(ident_node);
	if (ident == NULL) {
		/* properties must have an operator
		 * http://www.w3.org/TR/WebIDL/#idl-attributes
		 */
		return -1;
	}


	/* get type name */
	type_node = webidl_node_find_type(webidl_node_getnode(node),
					  NULL,
					  WEBIDL_NODE_TYPE_TYPE);
	ident_node = webidl_node_find_type(webidl_node_getnode(type_node),
					   NULL,
					   WEBIDL_NODE_TYPE_IDENT);
	type = webidl_node_gettext(ident_node);


	/* generate JSAPI_PS macro entry */
	/* if there is a putforwards the property requires a setter */
	if ((property_is_ro(node)) &&
	    (get_keyval_extended_attribute(node, "PutForwards") == NULL)) {
		ro = true;
	}


	/* generate property shared status */
	switch (get_binding_shared_modifier(binding, type, ident)) {

	default:
	case GENBIND_TYPE_NONE:
		/* shared property without type handler
		 *
		 * js doesnt provide storage and setter/getter must
		 * perform all GC management.
		 */
		fprintf(binding->outfile, 
			"\tJSAPI_PS_%s(%s, %s, JSPROP_SHARED),\n",
			ro?"RO":"RW",
			inf->name,
			ident);
		break;

	case GENBIND_TYPE_TYPE:
		/* shared property with a type handler */
		fprintf(binding->outfile, 
			"\tJSAPI_PS_ID_%s(%s, %s, JSPROP_SHARED, %s),\n",
			ro?"RO":"RW",
			inf->name,
			ident,
			type);
		break;

	case GENBIND_TYPE_UNSHARED:
		/* unshared property without type handler */
		fprintf(binding->outfile, 
			"\tJSAPI_PS_%s(%s, %s, 0),\n",
			ro?"RO":"RW",
			inf->name,
			ident);
		break;

	case GENBIND_TYPE_TYPE_UNSHARED:
		/* unshared property with a type handler */
		fprintf(binding->outfile, 
			"\tJSAPI_PS_ID_%s(%s, %s, 0, %s),\n",
			ro?"RO":"RW",
			inf->name,
			ident,
			type);
		break;

	}

	return 0;
}


/* exported interface documented in jsapi-libdom.h */
int
output_property_spec(struct binding *binding)
{
	int inf;
	int res;
	struct webidl_node *list_node;
	struct webidl_node *op_node; /* operation on list node */

	/* for each interface in the map */
	for (inf = 0; inf < binding->interfacec; inf++) {
		if (binding->interfaces[inf].own_properties == 0) {
			/* if the interface has no properties then
			 * there is nothing to generate.
			 */
			continue;
		}

		/* generate property specifier */
		fprintf(binding->outfile,
			"static JSPropertySpec JSClass_%s_properties[] = {\n",
			binding->interfaces[inf].name);

		/* iterate each list within an interface */
		list_node = webidl_node_find_type(
			webidl_node_getnode(binding->interfaces[inf].widl_node),
			NULL,
			WEBIDL_NODE_TYPE_LIST);

		while (list_node != NULL) {
			/* iterate through operations in a list */
			op_node = webidl_node_find_type(
				webidl_node_getnode(list_node),
				NULL,
				WEBIDL_NODE_TYPE_ATTRIBUTE);

			while (op_node != NULL) {
				res = webidl_property_spec(
					binding,
					&binding->interfaces[inf],
					op_node);
				if (res != 0) {
					return res;
				}

				op_node = webidl_node_find_type(
					webidl_node_getnode(list_node),
					op_node,
					WEBIDL_NODE_TYPE_ATTRIBUTE);
			}


			list_node = webidl_node_find_type(
				webidl_node_getnode(binding->interfaces[inf].widl_node),
				list_node,
				WEBIDL_NODE_TYPE_LIST);
		}

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

	}
	return 0;
}


/******************************** body ********************************/


static int output_return(struct binding *binding,
			 const char *ident,
			 struct webidl_node *node)
{
	struct webidl_node *type_node = NULL;
	struct webidl_node *type_base = NULL;
	struct webidl_node *type_nullable = NULL;
	enum webidl_type webidl_arg_type;

	type_node = webidl_node_find_type(webidl_node_getnode(node),
					 NULL,
					 WEBIDL_NODE_TYPE_TYPE);

	type_base = webidl_node_find_type(webidl_node_getnode(type_node),
					      NULL,
					      WEBIDL_NODE_TYPE_TYPE_BASE);

	webidl_arg_type = webidl_node_getint(type_base);

	type_nullable = webidl_node_find_type(webidl_node_getnode(type_node),
					      NULL,
					      WEBIDL_NODE_TYPE_TYPE_NULLABLE);

	switch (webidl_arg_type) {
	case WEBIDL_TYPE_USER:
		/* User type are represented with jsobject */
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(%s));\n",
			ident);

		break;

	case WEBIDL_TYPE_BOOL:
		/* JSBool */
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, BOOLEAN_TO_JSVAL(%s));\n",
			ident);

		break;

	case WEBIDL_TYPE_BYTE:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_BYTE");
		break;

	case WEBIDL_TYPE_OCTET:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_OCTET");
		break;

	case WEBIDL_TYPE_FLOAT:
	case WEBIDL_TYPE_DOUBLE:
		/* double */
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, DOUBLE_TO_JSVAL(%s));\n",
			ident);
		break;

	case WEBIDL_TYPE_SHORT:
		/* int16_t  */
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, INT_TO_JSVAL(%s));\n",
			ident);
		break;

	case WEBIDL_TYPE_LONGLONG:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_LONGLONG");
		break;

	case WEBIDL_TYPE_LONG:
		/* int32_t  */
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, INT_TO_JSVAL(%s));\n",
			ident);
		break;

	case WEBIDL_TYPE_STRING:
		/* JSString * */
		if (type_nullable == NULL) {
			/* entry is not nullable ensure it is set to a string */
			fprintf(binding->outfile,
				"\tif (%s == NULL) {\n"
				"\t\t%s = JS_NewStringCopyN(cx, NULL, 0);\n"
				"\t}\n",
				ident, ident);
		}
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, JSAPI_STRING_TO_JSVAL(%s));\n",
			ident);
		break;

	case WEBIDL_TYPE_SEQUENCE:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_SEQUENCE");
		break;

	case WEBIDL_TYPE_OBJECT:
		/* JSObject * */
		fprintf(binding->outfile,
			"\tJSAPI_PROP_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(%s));\n",
			ident);
		break;

	case WEBIDL_TYPE_DATE:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_DATE");
		break;

	case WEBIDL_TYPE_VOID:
		/* specifically requires no value */
		break;

	default:
		break;
	}

	return 0;
}



/* generate variable declaration of the correct type with appropriate default */
static int output_return_declaration(struct binding *binding,
				     const char *ident,
				     struct webidl_node *node)
{
	struct webidl_node *type_node = NULL;
	struct webidl_node *type_base = NULL;
	struct webidl_node *type_name = NULL;
	struct webidl_node *type_mod = NULL;
	enum webidl_type webidl_arg_type;

	type_node = webidl_node_find_type(webidl_node_getnode(node),
					 NULL,
					 WEBIDL_NODE_TYPE_TYPE);

	type_base = webidl_node_find_type(webidl_node_getnode(type_node),
					      NULL,
					      WEBIDL_NODE_TYPE_TYPE_BASE);

	webidl_arg_type = webidl_node_getint(type_base);

	switch (webidl_arg_type) {
	case WEBIDL_TYPE_USER:
		/* User type are represented with jsobject */
		type_name = webidl_node_find_type(webidl_node_getnode(type_node),
						  NULL,
						  WEBIDL_NODE_TYPE_IDENT);
		fprintf(binding->outfile,
			"\tJSObject *%s = NULL; /* %s */\n",
			ident,
			webidl_node_gettext(type_name));

		break;

	case WEBIDL_TYPE_BOOL:
		/* JSBool */
		fprintf(binding->outfile, "\tJSBool %s = JS_FALSE;\n",ident);

		break;

	case WEBIDL_TYPE_BYTE:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_BYTE");
		break;

	case WEBIDL_TYPE_OCTET:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_OCTET");
		break;

	case WEBIDL_TYPE_FLOAT:
	case WEBIDL_TYPE_DOUBLE:
		/* double */
		fprintf(binding->outfile, "\tdouble %s = 0;\n",	ident);
		break;

	case WEBIDL_TYPE_SHORT:
		/* int16_t  */
		type_mod = webidl_node_find_type(webidl_node_getnode(type_node),
						 NULL,
						 WEBIDL_NODE_TYPE_MODIFIER);
		if ((type_mod != NULL) &&
		    (webidl_node_getint(type_mod) == WEBIDL_TYPE_MODIFIER_UNSIGNED)) {
			fprintf(binding->outfile,
				"\tuint16_t %s = 0;\n",
				ident);
		} else {
			fprintf(binding->outfile,
				"\tint16_t %s = 0;\n",
				ident);
		}

		break;

	case WEBIDL_TYPE_LONGLONG:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_LONGLONG");
		break;

	case WEBIDL_TYPE_LONG:
		/* int32_t  */
		type_mod = webidl_node_find_type(webidl_node_getnode(type_node),
						 NULL,
						 WEBIDL_NODE_TYPE_MODIFIER);
		if ((type_mod != NULL) &&
		    (webidl_node_getint(type_mod) == WEBIDL_TYPE_MODIFIER_UNSIGNED)) {
			fprintf(binding->outfile,
				"\tuint32_t %s = 0;\n",
				ident);
		} else {
			fprintf(binding->outfile,
				"\tint32_t %s = 0;\n",
				ident);
		}

		break;

	case WEBIDL_TYPE_STRING:
		/* JSString * */
		fprintf(binding->outfile,
			"\tJSString *%s = NULL;\n",
			ident);
		break;

	case WEBIDL_TYPE_SEQUENCE:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_SEQUENCE");
		break;

	case WEBIDL_TYPE_OBJECT:
		/* JSObject * */
		fprintf(binding->outfile, "\tJSObject *%s = NULL;\n", ident);
		break;

	case WEBIDL_TYPE_DATE:
		WARN(WARNING_UNIMPLEMENTED, "Unhandled type WEBIDL_TYPE_DATE");
		break;

	case WEBIDL_TYPE_VOID:
		/* specifically requires no value */
		break;

	default:
		break;
	}
	return 0;
}

static int
output_property_placeholder(struct binding *binding,
			    struct binding_interface *inf,
			    struct webidl_node* oplist,
			    const char *ident)
{
	oplist=oplist;

	WARN(WARNING_UNIMPLEMENTED,
	     "property %s.%s has no implementation\n",
	     inf->name,
	     ident);


	if (options->dbglog) {
		fprintf(binding->outfile,
			"\tJSLOG(\"property %s.%s has no implementation\");\n",
			inf->name,
			ident);
	}
	return 0;
}

static int output_property_getter(struct binding *binding,
				  struct binding_interface *inf,
				  struct webidl_node *node,
				  const char *ident)
{
	struct genbind_node *property_node;

	fprintf(binding->outfile,
		"static JSBool JSAPI_PROP_GET(%s, %s, JSContext *cx, JSObject *obj, jsval *vp)\n"
		"{\n",
		inf->name,
		ident);

	/* return value declaration */
	output_return_declaration(binding, "jsret", node);

	output_private_get(binding, inf, "private");

	property_node = genbind_node_find_type_ident(binding->gb_ast,
				      NULL,
				      GENBIND_NODE_TYPE_GETTER,
				      ident);

	if (property_node != NULL) {
		/* binding source block */
		output_code_block(binding, genbind_node_getnode(property_node));
	} else {
		/* examine internal variables and see if they are gettable */
		struct genbind_node *binding_node;
		struct genbind_node *internal_node = NULL;

		binding_node = genbind_node_find_type(binding->gb_ast,
						 NULL,
						 GENBIND_NODE_TYPE_BINDING);

		if (binding_node != NULL) {
			internal_node = genbind_node_find_type_ident(genbind_node_getnode(binding_node),
				      NULL,
				      GENBIND_NODE_TYPE_BINDING_INTERNAL,
				      ident);

		}

		if (internal_node != NULL) {
			/** @todo fetching from internal entries ought to be type sensitive */
			fprintf(binding->outfile,
				"\tjsret = private->%s;\n",
				ident);
		} else {
			output_property_placeholder(binding, inf, node, ident);
		}

	}

	output_return(binding, "jsret", node);

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

	return 0;
}

static int output_property_setter(struct binding *binding,
				  struct binding_interface *inf,
				  struct webidl_node *node,
				  const char *property_ident)
{
	struct genbind_node *property_node;
	char *putforwards;
	putforwards = get_keyval_extended_attribute(node, "PutForwards");

	if (putforwards != NULL) {
		/* generate a putforwards setter */
		fprintf(binding->outfile,
			"/* PutForwards setter */\n"
			"static JSBool JSAPI_PROP_SET(%s, %s, JSContext *cx, JSObject *obj, jsval *vp)\n"
			"{\n",
			inf->name,
			property_ident);

		fprintf(binding->outfile,
			"\tjsval propval;\n"
			"\tif (JS_GetProperty(cx, obj, \"%s\", &propval) == JS_TRUE) {\n"
			"\t\tJS_SetProperty(cx, JSVAL_TO_OBJECT(propval), \"%s\", vp);\n"
			"\t}\n",
			property_ident,
			putforwards);

		fprintf(binding->outfile,
			"\treturn JS_FALSE; /* disallow the asignment */\n"
			"}\n\n");

		return 0;
	}

	if (property_is_ro(node)) {
		/* readonly so a set function is not required */
		return 0;
	}

	property_node = genbind_node_find_type_ident(binding->gb_ast,
				      NULL,
				      GENBIND_NODE_TYPE_SETTER,
				      property_ident);

	fprintf(binding->outfile,
		"static JSBool JSAPI_PROP_SET(%s, %s, JSContext *cx, JSObject *obj, jsval *vp)\n"
		"{\n",
		inf->name,
		property_ident);

	output_private_get(binding, inf, "private");

	if (property_node != NULL) {
		/* binding source block */
		output_code_block(binding, genbind_node_getnode(property_node));
	} else {
		output_property_placeholder(binding, inf, node, property_ident);
	}

	fprintf(binding->outfile,
		"        return JS_FALSE; /* disallow the asignment by default */\n"
		"}\n\n");


	return 0;
}

/**
 * generate atribute (property) body.
 */
static int webidl_property_body(struct binding *binding,
				struct binding_interface *inf,
				struct webidl_node *node)
{
	struct webidl_node *ident_node;
	const char *ident;
	struct webidl_node *type_node;
	const char *type = NULL;
	int ret;
	enum genbind_type_modifier shared_mod;

	ident_node = webidl_node_find_type(webidl_node_getnode(node),
					   NULL,
					   WEBIDL_NODE_TYPE_IDENT);
	ident = webidl_node_gettext(ident_node);
	if (ident == NULL) {
		/* properties must have an operator
		 * http://www.w3.org/TR/WebIDL/#idl-attributes
		 */
		return -1;
	}

	/* get type name */
	type_node = webidl_node_find_type(webidl_node_getnode(node),
					  NULL,
					  WEBIDL_NODE_TYPE_TYPE);
	ident_node = webidl_node_find_type(webidl_node_getnode(type_node),
					   NULL,
					   WEBIDL_NODE_TYPE_IDENT);
	type = webidl_node_gettext(ident_node);

	/* find shared modifiers */
	shared_mod = get_binding_shared_modifier(binding, type, ident);

	/* only generate individual getters/setters if there is not a
	 * type handler
	 */
	if ((shared_mod & GENBIND_TYPE_TYPE) == 0) {
		ret = output_property_setter(binding, inf, node, ident);
		if (ret == 0) {
			/* property getter */
			ret = output_property_getter(binding, inf, node, ident);
		}
	}
	return ret;

}



/* setter for type handler */
static int
output_property_type_setter(struct binding *binding,
			    struct binding_interface *inf,
			    struct genbind_node *node,
			    const char *type)
{
	struct genbind_node *property_node;
	node = node;/* currently unused */

	fprintf(binding->outfile,
		"static JSBool\n"
		"JSAPI_STRICTPROP(%s_set, JSContext *cx, JSObject *obj, jsval *vp)\n"
		"{\n",
		type);

	/* property name vars */
	output_property_tinyid_get_vars(binding, "tinyid");
	/* context data */
	output_private_get(binding, inf, "private");
	/* property name */
	output_property_tinyid_get(binding, "tinyid");

	/* output binding code block */
	property_node = genbind_node_find_type_ident(binding->gb_ast,
						     NULL,
						     GENBIND_NODE_TYPE_SETTER,
						     type);

	if (property_node != NULL) {
		/* binding source block */
		output_code_block(binding, genbind_node_getnode(property_node));
	}

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

}


/* getter for type handlers */
static int output_property_type_getter(struct binding *binding,
				       struct binding_interface *inf,
				       struct genbind_node *node,
				       const char *type)
{
	struct genbind_node *property_node;
	node = node;/* currently unused */

	fprintf(binding->outfile,
		"static JSBool JSAPI_PROP(%s_get, JSContext *cx, JSObject *obj, jsval *vp)\n"
		"{\n",
		type);

	/* property tinyid vars */
	output_property_tinyid_get_vars(binding, "tinyid");
	/* context data */
	output_private_get(binding, inf, "private");
	/* property tinyid */
	output_property_tinyid_get(binding, "tinyid");

	/* output binding code block */
	property_node = genbind_node_find_type_ident(binding->gb_ast,
						     NULL,
						     GENBIND_NODE_TYPE_GETTER,
						     type);

	if (property_node != NULL) {
		/* binding source block */
		output_code_block(binding, genbind_node_getnode(property_node));
	}

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

}

/* callback to emit property handlers for whole types */
static int typehandler_property_cb(struct genbind_node *node, void *ctx)
{
	struct binding *binding = ctx;
	struct genbind_node *ident_node;
	const char *type;
	struct genbind_node *mod_node;
	enum genbind_type_modifier *share_mod;
	int ret = 0;
	struct binding_interface *inf;

	mod_node = genbind_node_find_type(genbind_node_getnode(node),
					  NULL,
					  GENBIND_NODE_TYPE_MODIFIER);
	share_mod = (enum genbind_type_modifier *)genbind_node_getint(mod_node);

	if ((share_mod != NULL) &&
	    ((*share_mod & GENBIND_TYPE_TYPE) == GENBIND_TYPE_TYPE)) {
		/* type handler */

		ident_node = genbind_node_find_type(genbind_node_getnode(node),
						    NULL,
						    GENBIND_NODE_TYPE_IDENT);
		type = genbind_node_gettext(ident_node);
		if (type != NULL) {
			ret = output_property_type_setter(binding,
							  inf,
							  node,
							  type);
			if (ret == 0) {
				/* property getter */
				ret = output_property_type_getter(binding,
								  inf,
								  node,
								  type);
			}
		}
	}
	return ret;
}


/**
 * generate atribute (property) type body.
 *
 * Output property handler for an entire type of property
 */
static int webidl_attribute_typehandler_body(struct binding *binding,
				struct binding_interface *inf)
{
	int res;

	/* check if the interface has any properties with type handlers */
	if (!inf->has_type_properties) {
		return 0;
	}

	res = genbind_node_foreach_type(binding->binding_list,
					GENBIND_NODE_TYPE_BINDING_PROPERTY,
					typehandler_property_cb,
					binding);


	return res;

}

/* exported interface documented in jsapi-libdom.h */
int
output_property_body(struct binding *binding)
{
	int inf;
	int res;
	struct webidl_node *list_node;
	struct webidl_node *op_node; /* operation on list node */

	/* for each interface in the map */
	for (inf = 0; inf < binding->interfacec; inf++) {
		if (binding->interfaces[inf].own_properties == 0) {
			/* if the interface has no properties then
			 * there is nothing to generate.
			 */
			continue;
		}

		/* generate property bodies */

		/* iterate each list within an interface */
		list_node = webidl_node_find_type(
			webidl_node_getnode(binding->interfaces[inf].widl_node),
			NULL,
			WEBIDL_NODE_TYPE_LIST);

		while (list_node != NULL) {
			/* iterate through operations in a list */
			op_node = webidl_node_find_type(
				webidl_node_getnode(list_node),
				NULL,
				WEBIDL_NODE_TYPE_ATTRIBUTE);

			while (op_node != NULL) {
				res = webidl_property_body(
					binding,
					&binding->interfaces[inf],
					op_node);
				if (res != 0) {
					return res;
				}

				op_node = webidl_node_find_type(
					webidl_node_getnode(list_node),
					op_node,
					WEBIDL_NODE_TYPE_ATTRIBUTE);
			}


			list_node = webidl_node_find_type(
				webidl_node_getnode(binding->interfaces[inf].widl_node),
				list_node,
				WEBIDL_NODE_TYPE_LIST);
		}

		/* generate type handler bodies */
		res = webidl_attribute_typehandler_body(binding,
					&binding->interfaces[inf]);

		if (res != 0) {
			return res;
		}

	}
	return 0;
}
