// 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

/** \file
		\brief Implements CJavaScriptEngine, an implementation of k3d::iscript_engine that supports the JavaScript language
		\author Tim Shead (tshead@k-3d.com)
*/

#include "object_model.h"

#include <k3dsdk/application.h>
#include <k3dsdk/classes.h>
#include <k3dsdk/command_node.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/idocument.h>
#include <k3dsdk/iobject.h>
#include <k3dsdk/irenderman.h>
#include <k3dsdk/iscript_engine.h>
#include <k3dsdk/module.h>
#include <k3dsdk/string_modifiers.h>

#include <jsapi.h>

/// Namespace reserved for the JavaScript script engine plugin module, to protect public symbols from name clashes with other modules
namespace libk3djavascript
{

// Forward declarations
extern void initialize_javascript_streams(JSContext*, JSObject*);
extern JSObject* create_renderman_engine(k3d::ri::irender_engine& Engine, JSContext* Context);

/// Defines a "magic token" for automatic identification of scripts
const std::string magic_token("//javascript");
/// Defines a "global" object
static JSClass global_class = { "global", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub };

class engine :
	public k3d::iscript_engine,
	public k3d::ideletable
{
public:
	engine() :
		m_runtime(JS_NewRuntime(0x1000000)),
		m_context(JS_NewContext(m_runtime, 0x1000)),
		m_global_object(JS_NewObject(m_context, &global_class, NULL, NULL)),
		m_halt_request(false)
	{
		// Sanity checks ...
		assert_warning(m_runtime);
		assert_warning(m_context);
		assert_warning(m_global_object);

		// Store a reference to ourselves in the interpreter context ...
		JS_SetContextPrivate(m_context, this);

		// Set the branch callback, so we can handle halt requests ...
		JS_SetBranchCallback(m_context, raw_branch_callback);

		// Set the error reporter so we can handle script errors as needed ...
		JS_SetErrorReporter(m_context, raw_error_reporter);

		// Initialize core JS objects ...
		JS_InitStandardClasses(m_context, m_global_object);

		// Create the application object ...
		JSObject* const application = libk3djavascript::create_application(k3d::application(), m_context);
		assert_warning(application);
		jsval application_value = OBJECT_TO_JSVAL(application);
		JS_SetProperty(m_context, m_global_object, "Application", &application_value);
		
		// Setup support for stream objects
		initialize_javascript_streams(m_context, m_global_object);
	}

	virtual ~engine()
	{
		// Cleanup execution context
		if(m_context)
			JS_DestroyContext(m_context);
			
		// Shut down the interpreter
		if(m_runtime)
			JS_Finish(m_runtime);
	}

	const std::string language()
	{
		return "JavaScript";
	}

	bool can_execute(const std::string& Script)
	{
		return Script.substr(0, magic_token.size()) == magic_token;
	}

	bool execute(const std::string& ScriptName, const std::string& Script, const context_t& Context)
	{
		// Sanity checks ...
		return_val_if_fail(Script.size(), false);
		return_val_if_fail(ScriptName.size(), false);

		// Setup the execution context ...
		jsval null_value = JSVAL_NULL;
		JS_SetProperty(m_context, m_global_object, "Document", &null_value);
		JS_SetProperty(m_context, m_global_object, "Object", &null_value);
		JS_SetProperty(m_context, m_global_object, "RIB", &null_value);

		for(context_t::const_iterator context_object = Context.begin(); context_object != Context.end(); ++context_object)
			{
				if(dynamic_cast<k3d::idocument*>(*context_object))
					{
						JSObject* const document = libk3djavascript::create_document(*dynamic_cast<k3d::idocument*>(*context_object), m_context);
						return_val_if_fail(document, false);

						jsval document_value = OBJECT_TO_JSVAL(document);
						JS_SetProperty(m_context, m_global_object, "Document", &document_value);
					}
				else if(dynamic_cast<k3d::iobject*>(*context_object))
					{
						JSObject* const object = libk3djavascript::create_document_object(*dynamic_cast<k3d::iobject*>(*context_object), m_context);
						return_val_if_fail(object, false);

						jsval object_value = OBJECT_TO_JSVAL(object);
						JS_SetProperty(m_context, m_global_object, "Object", &object_value);
					}
				else if(dynamic_cast<k3d::ri::irender_engine*>(*context_object))
					{
						JSObject* const renderman_engine = libk3djavascript::create_renderman_engine(*dynamic_cast<k3d::ri::irender_engine*>(*context_object), m_context);
						return_val_if_fail(renderman_engine, false);
						
						jsval renderman_engine_value = OBJECT_TO_JSVAL(renderman_engine);
						JS_SetProperty(m_context, m_global_object, "RIB", &renderman_engine_value);
					}
			}
						
		// Evaluate the script ...
		jsval result_value;
		return JS_TRUE == JS_EvaluateScript(m_context, m_global_object, Script.c_str(), Script.size(), ScriptName.c_str(), 0, &result_value) ? true : false;
	}

	bool halt()
	{
		m_halt_request = true;
		return true;
	}

	void bless_script(std::string& Script)
	{
		// Don't keep adding magic tokens to code that already has one ...
		if(can_execute(Script))
			return;

		const std::string token = magic_token + "\n\n";

		Script.insert(Script.begin(), token.begin(), token.end());
	}

	bool convert_command(k3d::icommand_node& CommandNode, const std::string& Name, const std::string& Arguments, std::string& Result)
	{
		Result = "Application.CommandNode(\"";
		Result += k3d::command_node_path(CommandNode);
		Result += "\").Command(\"";
		Result += Name;
		Result += "\", \"";
		Result += k3d::replace_all("\"", "\\\"", Arguments); // Ensure that all quotation marks are escaped properly
		Result += "\");";

		return true;
	}

private:
	/// Provides a callback that is called by the JavaScript engine periodically to handle halt requests
	static JSBool raw_branch_callback(JSContext* Context, JSScript* Script)
	{
		return reinterpret_cast<engine*>(JS_GetContextPrivate(Context))->branch_callback(Context, Script);
	}

	/// Provides a callback that is called by the JavaScript engine periodically to handle halt requests
	JSBool branch_callback(JSContext* Context, JSScript* Script)
	{
		if(!m_halt_request)
			return JS_TRUE;

		m_halt_request = false;
		return JS_FALSE;
	}

	/// Provides an error-report callback that can be called by the JavaScript engine during error conditions
	static void raw_error_reporter(JSContext* Context, const char *Message, JSErrorReport *Report)
	{
		reinterpret_cast<engine*>(JS_GetContextPrivate(Context))->error_reporter(Message, Report);
	}

	/// Provides an error-report callback that can be called by the JavaScript engine during error conditions
	void error_reporter(const char *Message, JSErrorReport *Report)
	{
		std::cerr << error << "JavaScript error in " << std::string(Report->filename ? Report->filename : "unknown") << ": " << Report->lineno+1 << std::endl;
		std::cerr << error << std::string(Message ? Message : "unknown") << std::endl;
	}

	/// The top-level javascript interpreter object
	JSRuntime* const m_runtime;
	/// A Javascript execution context
	JSContext* const m_context;
	/// The global object within an execution context
	JSObject* const m_global_object;
	/// Stores a request to halt execution of a running script
	bool m_halt_request;
};

k3d::iplugin_factory& engine_factory()
{
	static k3d::plugin_factory<k3d::application_plugin<engine>, k3d::interface_list<k3d::iscript_engine> > factory(
		k3d::classes::JavaScriptEngine(),
		"JavaScript",
		"JavaScript scripting engine",
		"ScriptEngines");

	return factory;
}

} // namespace libk3djavascript

K3D_MODULE_START(k3d::uuid(0x53627a79, 0xd0d3403b, 0x98a7ea6e, 0x3724c0e5), Registry)
	Registry.register_factory(libk3djavascript::engine_factory());
K3D_MODULE_END


