// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "object_model.h"

#include <k3dsdk/algebra.h>
#include <k3dsdk/application.h>
#include <k3dsdk/command_node.h>
#include <k3dsdk/file_filter.h>
#include <k3dsdk/ianimation_render_engine.h>
#include <k3dsdk/iapplication_plugin_factory.h>
#include <k3dsdk/ibezier_channel.h>
#include <k3dsdk/idag.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/idocument_plugin_factory.h>
#include <k3dsdk/igeometry_read_format.h>
#include <k3dsdk/igeometry_write_format.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/iplugin_factory.h>
#include <k3dsdk/iplugin_factory_collection.h>
#include <k3dsdk/iproperty.h>
#include <k3dsdk/iproperty_collection.h>
#include <k3dsdk/iselectable.h>
#include <k3dsdk/istill_render_engine.h>
#include <k3dsdk/iuser_interface.h>
#include <k3dsdk/iwritable_property.h>
#include <k3dsdk/iviewport.h>
#include <k3dsdk/iviewport_host.h>
#include <k3dsdk/objects.h>
#include <k3dsdk/plugins.h>
#include <k3dsdk/scripting.h>
#include <k3dsdk/selection.h>
#include <k3dsdk/serialization.h>
#include <k3dsdk/state_change_set.h>
#include <k3dsdk/viewport.h>

#include <boost/filesystem/path.hpp>

#include <cmath>
#include <fstream>
#include <iostream>
#include <stdexcept>

#include <jsapi.h>

namespace libk3djavascript
{

namespace javascript
{

/// Casts a "generic" (wraps any interface) JavaScript object to a specific interface type (could return NULL)
template<class InterfaceType>
InterfaceType cast(JSContext* Context, JSObject* Object)
{
	return (JS_GetClass(Object) == &generic_object_class) ? dynamic_cast<InterfaceType>(reinterpret_cast<k3d::iunknown*>(JS_GetPrivate(Context, Object))) : 0;
}

const jsval convert(JSContext* Context, const bool From)
{
	return BOOLEAN_TO_JSVAL(From);
}

const jsval convert(JSContext* Context, const unsigned long From)
{
	return DOUBLE_TO_JSVAL(JS_NewDouble(Context, From));
}

const jsval convert(JSContext* Context, const double From)
{
	return DOUBLE_TO_JSVAL(JS_NewDouble(Context, From));
}

const jsval convert(JSContext* Context, const std::string& From)
{
	return STRING_TO_JSVAL(JS_NewStringCopyZ(Context, From.c_str()));
}

const jsval convert(JSContext* Context, const k3d::vector2& From)
{
	jsval values[2];
	values[0] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[0]));
	values[1] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[1]));

	return OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 2, values));
}

const jsval convert(JSContext* Context, const k3d::vector3& From)
{
	jsval values[3];
	values[0] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[0]));
	values[1] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[1]));
	values[2] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[2]));

	return OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 3, values));
}

const jsval convert(JSContext* Context, const k3d::vector4& From)
{
	jsval values[4];
	values[0] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[0]));
	values[1] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[1]));
	values[2] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[2]));
	values[3] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From[3]));

	return OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 4, values));
}

const jsval convert(JSContext* Context, const k3d::angle_axis& From)
{
	jsval values[4];
	values[0] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From.angle));
	values[1] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From.axis[0]));
	values[2] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From.axis[1]));
	values[3] = DOUBLE_TO_JSVAL(JS_NewDouble(Context, From.axis[2]));

	return OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 4, values));
}

template<typename data_t>
const jsval convert(JSContext* Context, const std::vector<data_t>& From)
{
	std::vector<jsval> values;
	for(typename std::vector<data_t>::const_iterator value = From.begin(); value != From.end(); ++value)
		values.push_back(convert(Context, *value));

	return OBJECT_TO_JSVAL(JS_NewArrayObject(Context, values.size(), &values.front()));
}

const jsval convert(JSContext* Context, const boost::any Value)
{
	const std::type_info& value_type = Value.type();

	// We put the likely types up front for efficiency ...
	if(value_type == typeid(bool))
		return convert(Context, boost::any_cast<bool>(Value));

	if(value_type == typeid(double))
		return convert(Context, boost::any_cast<double>(Value));

	if(value_type == typeid(std::string))
		return convert(Context, boost::any_cast<std::string>(Value));

	if(value_type == typeid(k3d::vector2))
		return convert(Context, boost::any_cast<k3d::vector2>(Value));

	if(value_type == typeid(k3d::vector3))
		return convert(Context, boost::any_cast<k3d::vector3>(Value));

	if(value_type == typeid(k3d::vector4))
		return convert(Context, boost::any_cast<k3d::vector4>(Value));

	if(value_type == typeid(k3d::angle_axis))
		return convert(Context, boost::any_cast<k3d::angle_axis>(Value));

	if(value_type == typeid(unsigned long))
		return convert(Context, boost::any_cast<unsigned long>(Value));

	if(value_type == typeid(k3d::iobject*))
		return OBJECT_TO_JSVAL(create_generic_object(*boost::any_cast<k3d::iobject*>(Value), Context));

	if(value_type == typeid(boost::filesystem::path))
		return convert(Context, boost::any_cast<boost::filesystem::path>(Value).native_file_string());

	std::cerr << error << __PRETTY_FUNCTION__ << " : unrecognized type" << std::endl;
	return JSVAL_NULL;
}

bool convert(JSContext* Context, const jsval From, bool& To)
{
	JSBool to;
	if(JS_TRUE == JS_ValueToBoolean(Context, From, &to))
		{
			To = JS_TRUE == to;
			return true;
		}

	return false;
}

bool convert(JSContext* Context, const jsval From, double& To)
{
	return JS_TRUE == JS_ValueToNumber(Context, From, &To);
}

bool convert(JSContext* Context, const jsval From, k3d::vector2& To)
{
	if(JSVAL_IS_OBJECT(From))
		{
			jsval element;

			JS_GetElement(Context, JSVAL_TO_OBJECT(From), 0, &element); JS_ValueToNumber(Context, element, &To[0]);
			JS_GetElement(Context, JSVAL_TO_OBJECT(From), 1, &element); JS_ValueToNumber(Context, element, &To[1]);

			return true;
		}

	return false;
}

bool convert(JSContext* Context, const jsval From, k3d::vector3& To)
{
	if(JSVAL_IS_OBJECT(From))
		{
			jsval element;

			JS_GetElement(Context, JSVAL_TO_OBJECT(From), 0, &element); JS_ValueToNumber(Context, element, &To[0]);
			JS_GetElement(Context, JSVAL_TO_OBJECT(From), 1, &element); JS_ValueToNumber(Context, element, &To[1]);
			JS_GetElement(Context, JSVAL_TO_OBJECT(From), 2, &element); JS_ValueToNumber(Context, element, &To[2]);

			return true;
		}

	return false;
}

bool convert(JSContext* Context, const jsval From, unsigned long& To)
{
	double to;
	if(JS_TRUE == JS_ValueToNumber(Context, From, &to))
		{
			To = static_cast<unsigned long>(std::max(0.0, to));
			return true;
		}

	return false;
}

const std::string string_cast(JSContext* Context, const jsval From)
{
	if(JSVAL_IS_NULL(From) || JSVAL_IS_VOID(From))
		return std::string();

	return std::string(JS_GetStringBytes(JS_ValueToString(Context, From)));
}

const boost::any convert(JSContext* Context, const jsval From, const std::type_info& TargetType)
{
	if(TargetType == typeid(bool))
		{
			bool result;
			if(convert(Context, From, result))
				return boost::any(result);

			return boost::any();
		}

	if(TargetType == typeid(double))
		{
			double result;
			if(convert(Context, From, result))
				return boost::any(result);

			return boost::any();
		}

	if(TargetType == typeid(std::string))
		{
			return boost::any(string_cast(Context, From));
		}

	if(TargetType == typeid(k3d::vector3))
		{
			k3d::vector3 result;
			if(convert(Context, From, result))
				return boost::any(result);

			return boost::any();
		}

	if(TargetType == typeid(k3d::angle_axis))
		{
			if(!JSVAL_IS_OBJECT(From))
				return boost::any();

			jsuint length = 0;
			if(JS_TRUE != JS_GetArrayLength(Context, JSVAL_TO_OBJECT(From), &length))
				return boost::any();

			if(3 == length)
				{
					k3d::euler_angles euler(0, 0, 0, k3d::euler_angles::ZXYstatic);

					jsval element;
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 0, &element); JS_ValueToNumber(Context, element, &euler.n[1]);
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 1, &element); JS_ValueToNumber(Context, element, &euler.n[2]);
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 2, &element); JS_ValueToNumber(Context, element, &euler.n[0]);

					euler.n[0] = k3d::radians(euler.n[0]);
					euler.n[1] = k3d::radians(euler.n[1]);
					euler.n[2] = k3d::radians(euler.n[2]);

					return boost::any(k3d::angle_axis(euler));
				}
			else if(4 == length)
				{
					k3d::angle_axis result;

					jsval element;
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 0, &element); JS_ValueToNumber(Context, element, &result.angle);
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 1, &element); JS_ValueToNumber(Context, element, &result.axis[0]);
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 2, &element); JS_ValueToNumber(Context, element, &result.axis[1]);
					JS_GetElement(Context, JSVAL_TO_OBJECT(From), 3, &element); JS_ValueToNumber(Context, element, &result.axis[2]);

					result.angle = k3d::radians(result.angle);

					return boost::any(result);
				}
		}

	if(TargetType == typeid(unsigned long))
		{
			unsigned long result;
			if(convert(Context, From, result))
				return boost::any(result);

			return boost::any();
		}

	if(TargetType == typeid(k3d::iobject*))
		{
			if(JSVAL_IS_OBJECT(From))
				return boost::any(javascript::cast<k3d::iobject*>(Context, JSVAL_TO_OBJECT(From)));

			return boost::any();
		}

	std::cerr << error << __PRETTY_FUNCTION__ << " : unrecognized type" << std::endl;
	return boost::any();
}

} // namespace javascript

/// JavaScript class for a "generic" (wraps any interface) JavaScript object
JSClass generic_object_class =
{
	"k3d_generic_object", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
};

JSBool get_viewport_host(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iviewport_host* const viewport_host = javascript::cast<k3d::iviewport*>(Context, Object)->host();
	if(viewport_host)
		*Value = OBJECT_TO_JSVAL(create_generic_object(*viewport_host, Context));
	else
		*Value = JSVAL_NULL;

	return JS_TRUE;
}

JSBool set_viewport_host(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	return_val_if_fail(JSVAL_IS_OBJECT(*Value), JS_FALSE);

	k3d::iviewport_host* const viewport_host = javascript::cast<k3d::iviewport_host*>(Context, JSVAL_TO_OBJECT(*Value));
	javascript::cast<k3d::iviewport*>(Context, Object)->set_host(viewport_host);

	return JS_TRUE;
}

/// Adds k3d::iviewport-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_viewport_behavior(JSContext* Context, JSObject* Object)
{
	k3d::iviewport* const viewport = javascript::cast<k3d::iviewport*>(Context, Object);
	if(!viewport)
		return;

	JS_DefineProperty(Context, Object, "viewport_host", 0, &get_viewport_host, &set_viewport_host, JSPROP_ENUMERATE | JSPROP_PERMANENT);
};

JSBool get_scalar_curve(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::ibezier_channel<void>* const bezier_channel = javascript::cast<k3d::ibezier_channel<void>*>(Context, Object);
	return_val_if_fail(bezier_channel, JS_FALSE);

	k3d::ibezier_channel<void>::control_points_t control_points;
	bezier_channel->get_curve(control_points);

	// Create the generic object instance ...
	JSObject* const object = JS_NewObject(Context, &generic_object_class, 0, 0);
	return_val_if_fail(object, JS_FALSE);

	// Add control point and value properties ...
	jsval js_control_points = javascript::convert(Context, control_points);
	JS_SetProperty(Context, object, "control_points", &js_control_points);

	*Value = OBJECT_TO_JSVAL(object);

	return JS_TRUE;
}

JSBool set_scalar_curve(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	// Sanity checks ...
	k3d::ibezier_channel<void>* const bezier_channel = javascript::cast<k3d::ibezier_channel<void>*>(Context, Object);
	return_val_if_fail(bezier_channel, JS_FALSE);

	// Get control point and value properties ...
	return_val_if_fail(JSVAL_IS_OBJECT(*Value), JS_FALSE);
	JSObject* const object = JSVAL_TO_OBJECT(*Value);
	return_val_if_fail(object, JS_FALSE);

	jsval js_control_points = JSVAL_VOID;
	return_val_if_fail(JS_TRUE == JS_GetProperty(Context, object, "control_points", &js_control_points), JS_FALSE);
	jsuint js_control_points_length = 0;
	return_val_if_fail(JS_TRUE == JS_GetArrayLength(Context, JSVAL_TO_OBJECT(js_control_points), &js_control_points_length), JS_FALSE);

	// Convert control points ...
	k3d::ibezier_channel<void>::control_points_t control_points;
	for(jsuint i = 0; i < js_control_points_length; ++i)
		{
			jsval element; JS_GetElement(Context, JSVAL_TO_OBJECT(js_control_points), i, &element);
			k3d::vector2 control_point; javascript::convert(Context, element, control_point);
			control_points.push_back(control_point);
		}

	// Make it happen ...
	bezier_channel->set_curve(control_points);

	return JS_TRUE;
}

JSBool get_color_curve(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::ibezier_channel<k3d::vector3>* const bezier_channel = javascript::cast<k3d::ibezier_channel<k3d::vector3>*>(Context, Object);
	return_val_if_fail(bezier_channel, JS_FALSE);

	k3d::ibezier_channel<k3d::vector3>::control_points_t control_points;
	k3d::ibezier_channel<k3d::vector3>::values_t values;
	bezier_channel->get_curve(control_points, values);

	// Create the generic object instance ...
	JSObject* const object = JS_NewObject(Context, &generic_object_class, 0, 0);
	return_val_if_fail(object, JS_FALSE);

	// Add control point and value properties ...
	jsval js_control_points = javascript::convert(Context, control_points);
	JS_SetProperty(Context, object, "control_points", &js_control_points);

	jsval js_values = javascript::convert(Context, values);
	JS_SetProperty(Context, object, "values", &js_values);

	*Value = OBJECT_TO_JSVAL(object);

	return JS_TRUE;
}

JSBool set_color_curve(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	// Sanity checks ...
	k3d::ibezier_channel<k3d::vector3>* const bezier_channel = javascript::cast<k3d::ibezier_channel<k3d::vector3>*>(Context, Object);
	return_val_if_fail(bezier_channel, JS_FALSE);

	// Get control point and value properties ...
	return_val_if_fail(JSVAL_IS_OBJECT(*Value), JS_FALSE);
	JSObject* const object = JSVAL_TO_OBJECT(*Value);
	return_val_if_fail(object, JS_FALSE);

	jsval js_control_points = JSVAL_VOID;
	return_val_if_fail(JS_TRUE == JS_GetProperty(Context, object, "control_points", &js_control_points), JS_FALSE);
	jsuint js_control_points_length = 0;
	return_val_if_fail(JS_TRUE == JS_GetArrayLength(Context, JSVAL_TO_OBJECT(js_control_points), &js_control_points_length), JS_FALSE);

	jsval js_values = JSVAL_VOID;
	return_val_if_fail(JS_TRUE == JS_GetProperty(Context, object, "values", &js_values), JS_FALSE);
	jsuint js_values_length = 0;
	return_val_if_fail(JS_TRUE == JS_GetArrayLength(Context, JSVAL_TO_OBJECT(js_values), &js_values_length), JS_FALSE);

	// Convert control points ...
	k3d::ibezier_channel<k3d::vector3>::control_points_t control_points;
	for(jsuint i = 0; i < js_control_points_length; ++i)
		{
			jsval element; JS_GetElement(Context, JSVAL_TO_OBJECT(js_control_points), i, &element);
			k3d::vector2 control_point; javascript::convert(Context, element, control_point);
			control_points.push_back(control_point);
		}

	// Convert values ...
	k3d::ibezier_channel<k3d::vector3>::values_t values;
	for(jsuint i = 0; i < js_values_length; ++i)
		{
			jsval element; JS_GetElement(Context, JSVAL_TO_OBJECT(js_values), i, &element);
			k3d::vector3 value; javascript::convert(Context, element, value);
			values.push_back(value);
		}

	// Make it happen ...
	bezier_channel->set_curve(control_points, values);

	return JS_TRUE;
}

/// Adds k3d::ibezier_channel-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_bezier_channel_behavior(JSContext* Context, JSObject* Object)
{
	// See if this object supports a scalar channel (k3d::ibezier_channel<void>)
	k3d::ibezier_channel<void>* const scalar_bezier_channel = javascript::cast<k3d::ibezier_channel<void>*>(Context, Object);
	if(scalar_bezier_channel)
		{
			// Add properties ...
			JS_DefineProperty(Context, Object, "curve", 0, &get_scalar_curve, &set_scalar_curve, JSPROP_ENUMERATE | JSPROP_PERMANENT);
			return;
		}

	// See if this object supports a color channel (k3d::ibezier_channel<k3d::vector3>)
	k3d::ibezier_channel<k3d::vector3>* const color_bezier_channel = javascript::cast<k3d::ibezier_channel<k3d::vector3>*>(Context, Object);
	if(color_bezier_channel)
		{
			// Add properties ...
			JS_DefineProperty(Context, Object, "curve", 0, &get_color_curve, &set_color_curve, JSPROP_ENUMERATE | JSPROP_PERMANENT);
			return;
		}
}

/// k3d::istill_render_engine::render_preview() wrapper
JSBool still_render_engine_render_preview(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	javascript::cast<k3d::istill_render_engine*>(Context, Object)->render_preview();
	return JS_TRUE;
}

/// k3d::istill_render_engine::render_frame() wrapper
JSBool still_render_engine_render_frame(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	if(JSVAL_IS_STRING(argv[0]))
		return javascript::cast<k3d::istill_render_engine*>(Context, Object)->render_frame(
			boost::filesystem::path(javascript::string_cast(Context, argv[0]), boost::filesystem::native), JSVAL_TO_BOOLEAN(argv[1])) ? JS_TRUE : JS_FALSE;

	return JS_FALSE;
}

/// k3d::ianimation_render_engine::render_animation() wrapper
JSBool animation_render_engine_render_animation(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	if(JSVAL_IS_STRING(argv[0]))
		return javascript::cast<k3d::ianimation_render_engine*>(Context, Object)->render_animation(
			boost::filesystem::path(javascript::string_cast(Context, argv[0]), boost::filesystem::native), JSVAL_TO_BOOLEAN(argv[1])) ? JS_TRUE : JS_FALSE;

	return JS_FALSE;
}

/// Adds k3d::istill_render_engine-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_still_render_engine_behavior(JSContext* Context, JSObject* Object)
{
	k3d::istill_render_engine* const render_engine = javascript::cast<k3d::istill_render_engine*>(Context, Object);
	if(!render_engine)
		return;

	JS_DefineFunction(Context, Object, "RenderPreview", &still_render_engine_render_preview, 0, 0);
	JS_DefineFunction(Context, Object, "RenderFrame", &still_render_engine_render_frame, 2, 0);
}

/// Adds k3d::ianimation_render_engine-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_animation_render_engine_behavior(JSContext* Context, JSObject* Object)
{
	k3d::ianimation_render_engine* const render_engine = javascript::cast<k3d::ianimation_render_engine*>(Context, Object);
	if(!render_engine)
		return;

	JS_DefineFunction(Context, Object, "RenderAnimation", &animation_render_engine_render_animation, 2, 0);
}

/// k3d::icommand_node::execute_command wrapper
JSBool execute_command(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	const std::string command(javascript::string_cast(Context, argv[0]));
	return_val_if_fail(command.size(), JS_FALSE);

	const std::string arguments(javascript::string_cast(Context, argv[1]));

	return javascript::cast<k3d::icommand_node*>(Context, Object)->execute_command(command, arguments) ? JS_TRUE : JS_FALSE;
}

/// Adds command-node-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_command_node_behavior(JSContext* Context, JSObject* Object)
{
	// Test to see if it's a command-node ...
	k3d::icommand_node* const command_node = javascript::cast<k3d::icommand_node*>(Context, Object);
	if(!command_node)
		return;

	// Define functions ...
	JS_DefineFunction(Context, Object, "Command", &execute_command, 2, 0);

}

/// k3d::iproperty::value wrapper
JSBool get_property(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iproperty_collection* const property_collection = javascript::cast<k3d::iproperty_collection*>(Context, Object);
	k3d::iproperty* const property = property_collection->properties()[JSVAL_TO_INT(ID)];

	*Value = javascript::convert(Context, property->value());

	return JS_TRUE;
}

/// k3d::iproperty::set_value wrapper
JSBool set_property(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iproperty_collection* const property_collection = javascript::cast<k3d::iproperty_collection*>(Context, Object);
	k3d::iproperty* const property = property_collection->properties()[JSVAL_TO_INT(ID)];
	k3d::iwritable_property* const writable_property = dynamic_cast<k3d::iwritable_property*>(property);
	return_val_if_fail(writable_property, JS_FALSE);

	writable_property->set_value(javascript::convert(Context, *Value, property->type()));

	return JS_TRUE;
}

JSBool get_property_collection_properties(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	// Get the set of all properties ...
	const k3d::iproperty_collection::properties_t properties(javascript::cast<k3d::iproperty_collection*>(Context, Object)->properties());

	// convert the set to a Javascript array ...
	std::vector<jsval> values;
	for(k3d::iproperty_collection::properties_t::const_iterator property = properties.begin(); property != properties.end(); ++property)
		values.push_back(OBJECT_TO_JSVAL(create_property(**property, Context)));

	// Return that baby ...
	if(values.size())
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, values.size(), &values.front()));
	else
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 0, 0));

	return JS_TRUE;
}

/// Returns an object property by name
JSBool get_property_by_name(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	// Gotta have a name to work with ...
	const std::string name(JS_GetStringBytes(JS_ValueToString(Context, argv[0])));
	return_val_if_fail(name.size(), JS_FALSE);

	// Get the set of all properties ...
	const k3d::iproperty_collection::properties_t properties(javascript::cast<k3d::iproperty_collection*>(Context, Object)->properties());
	for(k3d::iproperty_collection::properties_t::const_iterator property = properties.begin(); property != properties.end(); ++property)
		{
			if(name == (*property)->name())
				{
					*Result = OBJECT_TO_JSVAL(create_property(**property, Context));
					return JS_TRUE;
				}
		}

	*Result = JSVAL_NULL;
	return JS_TRUE;
}

/// Adds property-collection-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_property_collection_behavior(JSContext* Context, JSObject* Object)
{
	// Test to see if it's a property collection ...
	k3d::iproperty_collection* const property_collection = javascript::cast<k3d::iproperty_collection*>(Context, Object);
	if(!property_collection)
		return;

	// Define functions ...
	JS_DefineFunction(Context, Object, "Property", &get_property_by_name, 1, 0);

	// Define properties ...
	const k3d::iproperty_collection::properties_t properties(property_collection->properties());

	unsigned char index = 0;
	for(k3d::iproperty_collection::properties_t::const_iterator property = properties.begin(); property != properties.end(); ++property)
		{
			if(dynamic_cast<k3d::iwritable_property*>(*property))
				JS_DefinePropertyWithTinyId(Context, Object, (*property)->name().c_str(), index++, 0, &get_property, &set_property, JSPROP_ENUMERATE | JSPROP_PERMANENT);
			else
				JS_DefinePropertyWithTinyId(Context, Object, (*property)->name().c_str(), index++, 0, &get_property, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
		}

	JS_DefineProperty(Context, Object, "properties", 0, &get_property_collection_properties, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

JSBool get_property_name(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iproperty* const property = javascript::cast<k3d::iproperty*>(Context, Object);
	*Value = STRING_TO_JSVAL(JS_NewStringCopyZ(Context, property->name().c_str()));
	return JS_TRUE;
}

JSBool get_property_description(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iproperty* const property = javascript::cast<k3d::iproperty*>(Context, Object);
	*Value = STRING_TO_JSVAL(JS_NewStringCopyZ(Context, property->description().c_str()));
	return JS_TRUE;
}

/// Adds k3d::iproperty specific behavior to a "generic" (wraps any interface) JavaScript object
void add_property_behavior(JSContext* Context, JSObject* Object)
{
	// Test to see that it's a property ...
	k3d::iproperty* const property = javascript::cast<k3d::iproperty*>(Context, Object);
	if(!property)
		return;

	// Define properties (!)
	JS_DefineProperty(Context, Object, "name", 0, &get_property_name, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
	JS_DefineProperty(Context, Object, "description", 0, &get_property_description, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

/// Wraps iobject::document()
JSBool get_document(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iobject* const object = javascript::cast<k3d::iobject*>(Context, Object);
	*Value = OBJECT_TO_JSVAL(create_document(object->document(), Context));
	return JS_TRUE;
}

/// Wraps iobject::factory()
JSBool get_factory(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iobject* const object = javascript::cast<k3d::iobject*>(Context, Object);
	*Value = OBJECT_TO_JSVAL(create_plugin_factory(object->factory(), Context));
	return JS_TRUE;
}

/// Adds iobject-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_document_object_behavior(JSContext* Context, JSObject* Object)
{
	// Test to see if it's a document object ...
	k3d::iobject* const object = javascript::cast<k3d::iobject*>(Context, Object);
	if(!object)
		return;

	// Define functions ...

	// Define properties ...
	JS_DefineProperty(Context, Object, "document", 0, &get_document, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
	JS_DefineProperty(Context, Object, "factory", 0, &get_factory, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

/// Adds k3d::iapplication_plugin_factory specific behavior to a "generic" (wraps any interface) JavaScript object
void add_application_plugin_factory_behavior(JSContext* Context, JSObject* Object)
{
	k3d::iapplication_plugin_factory* const application_plugin_factory = javascript::cast<k3d::iapplication_plugin_factory*>(Context, Object);
	if(!application_plugin_factory)
		return;

	// Define functions ...
//	JS_DefineFunction(Context, Object, "CreatePlugin", &create_application_plugin, 0, 0);

	// Define properties ...
	JS_DefineProperty(Context, Object, "application_plugin", BOOLEAN_TO_JSVAL(true), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

/// Adds k3d::idocument_plugin_factory specific behavior to a "generic" (wraps any interfa) JavaScript object
void add_document_plugin_factory_behavior(JSContext* Context, JSObject* Object)
{
	k3d::idocument_plugin_factory* const document_plugin_factory = javascript::cast<k3d::idocument_plugin_factory*>(Context, Object);
	if(!document_plugin_factory)
		return;

	// Define functions ...
//	JS_DefineFunction(Context, Object, "CreatePlugin", &create_document_plugin, 1, 0);

	// Define properties ...
	JS_DefineProperty(Context, Object, "document_plugin", BOOLEAN_TO_JSVAL(true), 0, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

JSBool get_plugin_factory_name(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iplugin_factory* const plugin_factory = javascript::cast<k3d::iplugin_factory*>(Context, Object);
	*Value = STRING_TO_JSVAL(JS_NewStringCopyZ(Context, plugin_factory->name().c_str()));
	return JS_TRUE;
}

JSBool get_plugin_factory_short_description(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iplugin_factory* const plugin_factory = javascript::cast<k3d::iplugin_factory*>(Context, Object);
	*Value = STRING_TO_JSVAL(JS_NewStringCopyZ(Context, plugin_factory->short_description().c_str()));
	return JS_TRUE;
}

JSBool get_plugin_factory_default_category(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	k3d::iplugin_factory* const plugin_factory = javascript::cast<k3d::iplugin_factory*>(Context, Object);
	*Value = STRING_TO_JSVAL(JS_NewStringCopyZ(Context, plugin_factory->default_category().c_str()));
	return JS_TRUE;
}

/// Adds k3d::iplugin_factory-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_plugin_factory_behavior(JSContext* Context, JSObject* Object)
{
	// Test to see if it's a plugin factory ...
	k3d::iplugin_factory* const plugin_factory = javascript::cast<k3d::iplugin_factory*>(Context, Object);
	if(!plugin_factory)
		return;

	// Define functions ...

	// Define properties ...
	JS_DefineProperty(Context, Object, "name", 0, &get_plugin_factory_name, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
	JS_DefineProperty(Context, Object, "short_description", 0, &get_plugin_factory_short_description, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
	JS_DefineProperty(Context, Object, "default_category", 0, &get_plugin_factory_default_category, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

/// Wraps k3d::iuser_interface::browser_navigate()
JSBool browser_navigate(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	javascript::cast<k3d::iuser_interface*>(Context, Object)->browser_navigate(javascript::string_cast(Context, argv[0]));
	return JS_TRUE;
}

/// Wraps k3d::iuser_interface::message()
JSBool message(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	javascript::cast<k3d::iuser_interface*>(Context, Object)->message(javascript::string_cast(Context, argv[0]), javascript::string_cast(Context, argv[1]));
	return JS_TRUE;
}

/// Wraps k3d::iuser_interface::error_message()
JSBool error_message(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	javascript::cast<k3d::iuser_interface*>(Context, Object)->error_message(javascript::string_cast(Context, argv[0]), javascript::string_cast(Context, argv[1]));
	return JS_TRUE;
}

/// Wraps k3d::iuser_interface::query_message()
JSBool query_message(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	std::vector<std::string> buttons;
	for(uintN i = 2; i < argc; i++)
		buttons.push_back(javascript::string_cast(Context, argv[i]));

	*Result = INT_TO_JSVAL(javascript::cast<k3d::iuser_interface*>(Context, Object)->query_message(javascript::string_cast(Context, argv[0]), javascript::string_cast(Context, argv[1]), 0, buttons));
	return JS_TRUE;
}

/// Wraps k3d::iuser_interface::get_file_path
JSBool get_file_path(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	boost::filesystem::path file;
	javascript::cast<k3d::iuser_interface*>(Context, Object)->get_file_path(javascript::string_cast(Context, argv[0]), javascript::string_cast(Context, argv[1]), JSVAL_TO_BOOLEAN(argv[2]), boost::filesystem::path(javascript::string_cast(Context, argv[3]), boost::filesystem::native), file);
	*Result = STRING_TO_JSVAL(JS_NewStringCopyZ(Context, file.native_file_string().c_str()));
	return JS_TRUE;
}

/// Wraps k3d::iuser_interface::show
JSBool show(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	if(!JSVAL_IS_OBJECT(argv[0]))
		return JS_FALSE;

	k3d::iunknown* const object = javascript::cast<k3d::iunknown*>(Context, JSVAL_TO_OBJECT(argv[0]));
	if(!object)
		return JS_FALSE;

	*Result = BOOLEAN_TO_JSVAL(javascript::cast<k3d::iuser_interface*>(Context, Object)->show(*object));
	return JS_TRUE;
}

/// Wraps k3d::iuser_interface::show_viewport
JSBool show_viewport(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval *Result)
{
	if(!JSVAL_IS_OBJECT(argv[0]))
		return JS_FALSE;

	k3d::iviewport* const viewport = javascript::cast<k3d::iviewport*>(Context, JSVAL_TO_OBJECT(argv[0]));
	return_val_if_fail(viewport, JS_FALSE);

	*Result = BOOLEAN_TO_JSVAL(javascript::cast<k3d::iuser_interface*>(Context, Object)->show_viewport(*viewport));
	return JS_TRUE;
}

/// Adds k3d::iuser_interface-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_user_interface_behavior(JSContext* Context, JSObject* Object)
{
	// See if this object supports k3d::iuser_interface behavior
	k3d::iuser_interface* const user_interface = javascript::cast<k3d::iuser_interface*>(Context, Object);
	if(!user_interface)
		return;

	// Add methods
	JS_DefineFunction(Context, Object, "BrowserNavigate", &browser_navigate, 1, 0);
	JS_DefineFunction(Context, Object, "Message", &message, 2, 0);
	JS_DefineFunction(Context, Object, "ErrorMessage", &error_message, 2, 0);
	JS_DefineFunction(Context, Object, "QueryMessage", &query_message, 5, 0);
	JS_DefineFunction(Context, Object, "GetFilePath", &get_file_path, 4, 0);
	JS_DefineFunction(Context, Object, "Show", &show, 1, 0);
	JS_DefineFunction(Context, Object, "ShowViewport", &show_viewport, 1, 0);
}

/// Wraps k3d::iobject_collection::CreateObject()
JSBool create_object(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	// Look for object types by name ...
	const std::string name(javascript::string_cast(Context, argv[0]));
	const k3d::iplugin_factory_collection::factories_t factories(k3d::plugins(name));

	// If we got exactly one object type, create an instance of it ...
	if(1 != factories.size())
		{
			std::cerr << error << __PRETTY_FUNCTION__ << ": couldn't find plugin to match [" << name << "]" << std::endl;
			return JS_FALSE;
		}

	// Create an object ...
	k3d::iobject* const object = k3d::create_document_plugin(**factories.begin(), *javascript::cast<k3d::idocument*>(Context, Object), "");
	if(!object)
		return JS_TRUE;

	// Return that baby...
	*Result = OBJECT_TO_JSVAL(create_document_object(*object, Context));

	return JS_TRUE;
}

/// Returns an array containing every object in the collection
JSBool get_all_objects(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	// Get the set of all objects in the document ...
	const k3d::iobject_collection::objects_t& objects(javascript::cast<k3d::idocument*>(Context, Object)->objects().collection());

	// convert the set to a Javascript array ...
	std::vector<jsval> values;
	for(k3d::iobject_collection::objects_t::const_iterator object = objects.begin(); object != objects.end(); ++object)
		values.push_back(OBJECT_TO_JSVAL(create_document_object(**object, Context)));

	// Return that baby ...
	if(values.size())
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, values.size(), &values.front()));
	else
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 0, 0));

	return JS_TRUE;
}

/// Returns an object by name
JSBool get_object_by_name(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	// Look for objects by name ...
	const k3d::objects_t objects(k3d::find_objects(javascript::cast<k3d::idocument*>(Context, Object)->objects(), javascript::string_cast(Context, argv[0])));

	// If we found something return it ...
	if(1 != objects.size())
		return JS_TRUE;

	*Result = OBJECT_TO_JSVAL(create_document_object(**objects.begin(), Context));

	return JS_TRUE;
}

/// Wraps k3d::iobject_collection::delete_objects
JSBool delete_object(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	k3d::objects_t objects;

	if(JSVAL_IS_STRING(argv[0]))
		{
			// Look for our victim by name ...
			objects = k3d::find_objects(javascript::cast<k3d::idocument*>(Context, Object)->objects(), javascript::string_cast(Context, argv[0]));
		}
	else if(JSVAL_IS_OBJECT(argv[0]))
		{
			// We got a direct reference to the object ...
			k3d::iobject* const object = javascript::cast<k3d::iobject*>(Context, JSVAL_TO_OBJECT(argv[0]));
			if(object)
				objects.insert(object);
		}

	k3d::delete_objects(*javascript::cast<k3d::idocument*>(Context, Object), objects);
	return JS_TRUE;
}

/// Adds k3d::iobject_collection-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_object_collection_behavior(JSContext* Context, JSObject* Object)
{
	// See if this object supports k3d::iobject_collection
	k3d::idocument* const document = javascript::cast<k3d::idocument*>(Context, Object);
	if(!document)
		return;

	// Add methods
	JS_DefineFunction(Context, Object, "CreateObject", &create_object, 1, 0);
	JS_DefineFunction(Context, Object, "Object", &get_object_by_name, 1, 0);
	JS_DefineFunction(Context, Object, "DeleteObject", &delete_object, 1, 0);

	// Add properties ...
	JS_DefineProperty(Context, Object, "objects", 0, &get_all_objects, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

JSBool set_dependency(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	// Lookup the two properties ...
	return_val_if_fail(JSVAL_IS_OBJECT(argv[0]), JS_FALSE);
	k3d::iproperty* const from = javascript::cast<k3d::iproperty*>(Context, JSVAL_TO_OBJECT(argv[0]));
	return_val_if_fail(from, JS_FALSE);

	return_val_if_fail(JSVAL_IS_OBJECT(argv[1]), JS_FALSE);
	k3d::iproperty* const to = JSVAL_IS_NULL(argv[1]) ? 0 : javascript::cast<k3d::iproperty*>(Context, JSVAL_TO_OBJECT(argv[1]));

	// Ensure that their types match ...
	if(from && to)
		return_val_if_fail(from->type() == to->type(), JS_FALSE);

	// No problemo, so setup the dependency ...
	k3d::idag::dependencies_t dependencies;
	dependencies[from] = to;
	javascript::cast<k3d::idocument*>(Context, Object)->dag().set_dependencies(dependencies);

	return JS_TRUE;
}

/// Adds k3d::idag-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_dag_behavior(JSContext* Context, JSObject* Object)
{
	k3d::idocument* const document = javascript::cast<k3d::idocument*>(Context, Object);
	if(!document)
		return;

	// Add methods ...
	JS_DefineFunction(Context, Object, "SetDependency", &set_dependency, 2, 0);
}

JSBool import_file(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	const boost::filesystem::path filepath(javascript::string_cast(Context, argv[0]), boost::filesystem::native);
	return_val_if_fail(!filepath.empty(), JS_FALSE);

	const std::string formatname(javascript::string_cast(Context, argv[1]));

	k3d::auto_ptr<k3d::igeometry_read_format> filter(
		formatname.size() ?
		k3d::file_filter<k3d::igeometry_read_format>(formatname) :
		k3d::auto_file_filter<k3d::igeometry_read_format>(filepath));
	if(!filter.get())
		{
			std::cerr << error << "Could not find geometry import plugin [" << formatname << "] for [" << filepath.native_file_string() << "]" << std::endl;
			return JS_FALSE;
		}

	return k3d::import_file(*javascript::cast<k3d::idocument*>(Context, Object), *filter, filepath) ? JS_TRUE : JS_FALSE;
}

JSBool export_file(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	const boost::filesystem::path filepath(javascript::string_cast(Context, argv[0]), boost::filesystem::native);
	return_val_if_fail(!filepath.empty(), JS_FALSE);

	const std::string formatname(javascript::string_cast(Context, argv[1]));

	k3d::auto_ptr<k3d::igeometry_write_format> filter(
		formatname.size() ?
		k3d::file_filter<k3d::igeometry_write_format>(formatname) :
		k3d::auto_file_filter<k3d::igeometry_write_format>(filepath));
	if(!filter.get())
		{
			std::cerr << error << "Could not find geometry export plugin [" << formatname << "] for [" << filepath.native_file_string() << "]" << std::endl;
			return JS_FALSE;
		}
		
	return k3d::export_file(*javascript::cast<k3d::idocument*>(Context, Object), *filter, filepath) ? JS_TRUE : JS_FALSE;
}

JSBool save(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	const boost::filesystem::path filepath(javascript::string_cast(Context, argv[0]), boost::filesystem::native);
	javascript::cast<k3d::idocument*>(Context, Object)->save(filepath);
	return JS_TRUE;
}

JSBool start_state_change_set(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	k3d::start_state_change_set(*javascript::cast<k3d::idocument*>(Context, Object));
	return JS_TRUE;
}

JSBool finish_state_change_set(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	k3d::finish_state_change_set(*javascript::cast<k3d::idocument*>(Context, Object), javascript::string_cast(Context, argv[0]));
	return JS_TRUE;
}

JSBool redraw_all(JSContext* Context, JSObject* Object, uintN argc, jsval* argv, jsval* Result)
{
	k3d::viewport::redraw_all(*javascript::cast<k3d::idocument*>(Context, Object), JSVAL_TO_BOOLEAN(argv[0]) ? k3d::iviewport::SYNCHRONOUS : k3d::iviewport::ASYNCHRONOUS);
	return JS_TRUE;
}

/// Adds k3d::idocument-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_document_behavior(JSContext* Context, JSObject* Object)
{
	// See if this object supports k3d::idocument
	k3d::idocument* const document = javascript::cast<k3d::idocument*>(Context, Object);
	if(!document)
		return;

	// Add methods
	JS_DefineFunction(Context, Object, "Import", &import_file, 2, 0);
	JS_DefineFunction(Context, Object, "Export", &export_file, 2, 0);
	JS_DefineFunction(Context, Object, "Save", &save, 1, 0);
	JS_DefineFunction(Context, Object, "StartChangeSet", &start_state_change_set, 0, 0);
	JS_DefineFunction(Context, Object, "FinishChangeSet", &finish_state_change_set, 1, 0);
	JS_DefineFunction(Context, Object, "RedrawAll", &redraw_all, 1, 0);
}

JSBool get_user_interface(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	if(k3d::application().user_interface())
		*Value = OBJECT_TO_JSVAL(create_user_interface(*k3d::application().user_interface(), Context));

	return JS_TRUE;
}

JSBool get_documents(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	// convert the set of open documents to a Javascript array ...
	std::vector<jsval> values;
	const k3d::iapplication::document_collection_t documents(k3d::application().documents());
	for(k3d::iapplication::document_collection_t::const_iterator document = documents.begin(); document != documents.end(); ++document)
		values.push_back(OBJECT_TO_JSVAL(create_document(**document, Context)));

	// Return that baby ...
	if(values.size())
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, values.size(), &values.front()));
	else
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 0, 0));

	return JS_TRUE;
}

JSBool new_document(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	k3d::idocument* const document = k3d::application().create_document();
	if(document)
		*Result = OBJECT_TO_JSVAL(create_document(*document, Context));

	return JS_TRUE;
}

JSBool open_document(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	k3d::idocument* const document = k3d::application().open_document(boost::filesystem::path(javascript::string_cast(Context, argv[0]), boost::filesystem::native));
	if(document)
		*Result = OBJECT_TO_JSVAL(create_document(*document, Context));

	return JS_TRUE;
}

JSBool close_document(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
/** \todo Implement this! */
/*
	if(!JSVAL_IS_OBJECT(argv[0]))
		return JS_FALSE;

	k3d::idocument* const document = CJavaScriptDocument::Interface(Context, JSVAL_TO_OBJECT(argv[0]));
	if(!document)
		return JS_FALSE;

	k3d::application().CloseDocument(*document);
*/
	return JS_TRUE;
}

JSBool command_node(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	const std::string nodepath = javascript::string_cast(Context, argv[0]);
	if(0 == nodepath.size())
		{
			JS_ReportError(Context, "Empty command node path");
			return JS_FALSE;
		}

	k3d::icommand_node* const node = k3d::get_command_node(nodepath);
	if(0 == node)
		{
			// Instead of returning a failure, we return success with a NULL object - this allows scripts
			// to test for nodes at runtime, e.g. testing to see if a particular window is open.
			return JS_TRUE;
		}

	*Result = OBJECT_TO_JSVAL(create_generic_object(*node, Context));
	return JS_TRUE;
}

/// Throws a C++ exception, for use in troubleshooting the application exception-handling - developers only!
JSBool throw_exception(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	// You asked for it!
	throw std::runtime_error(javascript::string_cast(Context, argv[0]));

	return JS_TRUE;
}

/// Returns true iff it's safe to exit the application
JSBool safe_to_close_application(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	*Result = BOOLEAN_TO_JSVAL(k3d::application().safe_to_close_signal().emit());
	return JS_TRUE;
}

/// Requests application exit
JSBool exit_application(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	*Result = BOOLEAN_TO_JSVAL(k3d::application().exit());
	return JS_TRUE;
}

/// Returns an array containing every plugin factory in the collection
JSBool get_plugin_factories(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	// Get the set of all factories in the application ...
	const k3d::iplugin_factory_collection::factories_t& factories(javascript::cast<k3d::iapplication*>(Context, Object)->plugins());

	// convert the set to a Javascript array ...
	std::vector<jsval> values;
	for(k3d::iplugin_factory_collection::factories_t::const_iterator factory = factories.begin(); factory != factories.end(); ++factory)
		values.push_back(OBJECT_TO_JSVAL(create_plugin_factory(**factory, Context)));

	// Return that baby ...
	if(values.size())
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, values.size(), &values.front()));
	else
		*Value = OBJECT_TO_JSVAL(JS_NewArrayObject(Context, 0, 0));

	return JS_TRUE;
}

/// Adds k3d::iapplication-specific behavior to a "generic" (wraps any interface) JavaScript object
void add_application_behavior(JSContext* Context, JSObject* Object)
{
	// See if this object supports k3d::iapplication
	k3d::iapplication* const application = javascript::cast<k3d::iapplication*>(Context, Object);
	if(!application)
		return;

	// Add methods
	JS_DefineFunction(Context, Object, "NewDocument", &new_document, 0, 0);
	JS_DefineFunction(Context, Object, "OpenDocument", &open_document, 1, 0);
	JS_DefineFunction(Context, Object, "CloseDocument", &close_document, 1, 0);
	JS_DefineFunction(Context, Object, "CommandNode", &command_node, 1, 0);
	JS_DefineFunction(Context, Object, "ThrowException", &throw_exception, 1, 0);
	JS_DefineFunction(Context, Object, "SafeToClose", &safe_to_close_application, 0, 0);
	JS_DefineFunction(Context, Object, "Exit", &exit_application, 0, 0);

	// Add properties
	JS_DefineProperty(Context, Object, "ui", 0, &get_user_interface, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
	JS_DefineProperty(Context, Object, "plugin_factories", 0, &get_plugin_factories, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
	JS_DefineProperty(Context, Object, "documents", 0, &get_documents, 0, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
}

JSBool get_selected(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	*Value = BOOLEAN_TO_JSVAL(javascript::cast<k3d::iselectable*>(Context, Object)->is_selected());
	return JS_TRUE;
}

JSBool set_selected(JSContext* Context, JSObject* Object, jsval ID, jsval* Value)
{
	if(JSVAL_TO_BOOLEAN(*Value))
		{
			javascript::cast<k3d::iselectable*>(Context, Object)->select();
		}
	else
		{
			javascript::cast<k3d::iselectable*>(Context, Object)->deselect();
		}

	return JS_TRUE;
}

/// Adds k3d::iselectable behavior to a "generic" (wraps any interface) JavaScript object
void add_selectable_behavior(JSContext* Context, JSObject* Object)
{
	// See if this object supports k3d::iselectable
	k3d::iselectable* const selectable = javascript::cast<k3d::iselectable*>(Context, Object);
	if(!selectable)
		return;

	JS_DefineProperty(Context, Object, "selected", 0, &get_selected, &set_selected, JSPROP_ENUMERATE | JSPROP_PERMANENT);
}

JSBool play_script_file(JSContext* Context, JSObject* Object, uintN argc, jsval *argv, jsval* Result)
{
	// Get the file path ...
	const std::string filepath(javascript::string_cast(Context, argv[0]));
	return_val_if_fail(filepath.size(), JS_FALSE);

	// Setup our stream ...
	std::ifstream file(filepath.c_str());

	bool recognized = false;
	bool executed = false;

	// Setup (optional) document and object contexts ...
	k3d::iobject* const document_object = javascript::cast<k3d::iobject*>(Context, Object);
	k3d::idocument* const document = document_object ? &document_object->document() : javascript::cast<k3d::idocument*>(Context, Object);

	k3d::iscript_engine::context_t context;
	if(document)
		context.push_back(document);
	if(document_object)
		context.push_back(document_object);

	k3d::execute_script(file, filepath, context, recognized, executed);
	*Result = BOOLEAN_TO_JSVAL(recognized && executed);
	return JS_TRUE;
}

/// Adds scripted behavior to a "generic" (wraps any interface) JavaScript object
void add_scripted_behavior(JSContext* Context, JSObject* Object)
{
	// Add methods
	JS_DefineFunction(Context, Object, "PlayScriptFile", &play_script_file, 1, 0);
}

/// Creates a generic (wraps any interface) JavaScript object
JSObject* raw_create_generic_object(k3d::iunknown& Object, JSContext* Context)
{
	// Create the generic object instance ...
	JSObject* const object = JS_NewObject(Context, &generic_object_class, 0, 0);

	// Set the wrapped-object's interface ...
	JS_SetPrivate(Context, object, &Object);

	return object;
}

JSObject* create_application(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_application_behavior(Context, object);
	add_command_node_behavior(Context, object);
	add_property_collection_behavior(Context, object);
	add_scripted_behavior(Context, object);

	return object;
}

JSObject* create_document(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_command_node_behavior(Context, object);
	add_dag_behavior(Context, object);
	add_document_behavior(Context, object);
	add_object_collection_behavior(Context, object);
	add_property_collection_behavior(Context, object);
	add_scripted_behavior(Context, object);

	return object;
}

JSObject* create_plugin_factory(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_application_plugin_factory_behavior(Context, object);
	add_document_plugin_factory_behavior(Context, object);
	add_plugin_factory_behavior(Context, object);

	return object;
}

JSObject* create_property(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_property_behavior(Context, object);

	return object;
}

JSObject* create_document_object(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_bezier_channel_behavior(Context, object);
	add_command_node_behavior(Context, object);
	add_document_object_behavior(Context, object);
	add_property_collection_behavior(Context, object);
	add_still_render_engine_behavior(Context, object);
	add_animation_render_engine_behavior(Context, object);
	add_scripted_behavior(Context, object);
	add_selectable_behavior(Context, object);
	add_viewport_behavior(Context, object);

	return object;
}

JSObject* create_generic_object(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_application_behavior(Context, object);
	add_application_plugin_factory_behavior(Context, object);
	add_bezier_channel_behavior(Context, object);
	add_command_node_behavior(Context, object);
	add_dag_behavior(Context, object);
	add_document_behavior(Context, object);
	add_document_object_behavior(Context, object);
	add_document_plugin_factory_behavior(Context, object);
	add_object_collection_behavior(Context, object);
	add_plugin_factory_behavior(Context, object);
	add_property_behavior(Context, object);
	add_property_collection_behavior(Context, object);
	add_still_render_engine_behavior(Context, object);
	add_animation_render_engine_behavior(Context, object);
	add_scripted_behavior(Context, object);
	add_selectable_behavior(Context, object);
	add_user_interface_behavior(Context, object);
	add_viewport_behavior(Context, object);

	return object;
}

JSObject* create_user_interface(k3d::iunknown& Object, JSContext* Context)
{
	JSObject* const object = raw_create_generic_object(Object, Context);

	// Add behavior to the object
	add_command_node_behavior(Context, object);
	add_property_collection_behavior(Context, object);
	add_user_interface_behavior(Context, object);

	return object;
}

} // namespace libk3djavascript


