// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE

#include "uiscripting.h"
#include "detail/converters.h"
#include "detail/scriptobject.h"
#include "detail/uidescscriptobject.h"
#include "detail/drawcontextobject.h"
#include "detail/drawable.h"
#include "detail/iscriptcontextinternal.h"
#include "detail/scriptingviewfactory.h"
#include "../uidescription/uiattributes.h"
#include "../uidescription/uiviewfactory.h"
#include "../uidescription/uidescriptionaddonregistry.h"
#include "../lib/iviewlistener.h"
#include "../lib/cresourcedescription.h"
#include "../lib/cvstguitimer.h"
#include "../lib/platform/platformfactory.h"
#include "../lib/platform/iplatformresourceinputstream.h"

#include "tiny-js/TinyJS.h"
#include "tiny-js/TinyJS_Functions.h"
#include "tiny-js/TinyJS_MathFunctions.h"

#include <iostream>
#include <sstream>

//------------------------------------------------------------------------
namespace VSTGUI {
using namespace std::literals;
using namespace TJS;

namespace ScriptingInternal {

struct ViewScriptObject;

//------------------------------------------------------------------------
class ScriptContext : public IScriptContextInternal
{
public:
	using OnScriptExceptionFunc = UIScripting::OnScriptExceptionFunc;
	using ReadScriptContentsFunc = UIScripting::ReadScriptContentsFunc;

	ScriptContext (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
				   ReadScriptContentsFunc&& readContentsFunc);
	~ScriptContext () noexcept;

	void init (const std::string& initScript);
	void onViewCreated (CView* view, const std::string& script) override;

	void reset ();

private:
	std::string eval (std::string_view script) const override;

	struct Impl;
	std::unique_ptr<Impl> impl;
};

//------------------------------------------------------------------------
struct TimerScriptObject : ScriptObject
{
	template<typename Proc>
	TimerScriptObject (uint64_t fireTime, CScriptVar* _callback, Proc timerProc)
	{
		auto cb = owning (_callback);
		auto t = makeOwned<CVSTGUITimer> (
			[timerProc, cb] (auto timer) {
				if (!timerProc (cb))
					timer->stop ();
			},
			static_cast<uint32_t> (fireTime), false);
		scriptVar->setCustomData (t);
		addFunc ("invalid"sv, [t] (auto v) mutable {
			t = nullptr;
			v->setCustomData (nullptr);
		});
		addFunc ("start"sv, [t] (auto) { t->start (); });
		addFunc ("stop"sv, [t] (auto) { t->stop (); });
		setOnDestroy ([cb] (auto) { cb->release (); });
	}
};

//------------------------------------------------------------------------
struct ScriptContext::Impl : ViewListenerAdapter,
							 ViewEventListenerAdapter,
							 ViewContainerListenerAdapter,
							 IControlListener,
							 ScriptingInternal::IViewScriptObjectContext
{
	using ViewScriptObject = ScriptingInternal::ViewScriptObject;
	using ScriptAddChildScoped = ScriptingInternal::ScriptAddChildScoped;
	using ScriptObject = ScriptingInternal::ScriptObject;
	using TimerScriptObject = ScriptingInternal::TimerScriptObject;
	using UIDescScriptObject = ScriptingInternal::UIDescScriptObject;

	IUIDescription* uiDesc {nullptr};

	std::unique_ptr<CTinyJS> jsContext;
	OnScriptExceptionFunc onScriptException;
	ReadScriptContentsFunc readScriptContents;
	using ViewScriptMap = ScriptingInternal::ViewScriptMap;
	ViewScriptMap viewScriptMap;

	UIDescScriptObject uiDescObject;

	Impl (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
		  ReadScriptContentsFunc&& readContentsFunc)
	: uiDesc (uiDesc)
	, onScriptException (std::move (onExceptionFunc))
	, readScriptContents (std::move (readContentsFunc))
	{
		init ();
	}

	~Impl () noexcept { reset (true); }

	void init ()
	{
		jsContext = std::make_unique<CTinyJS> ();
		uiDescObject = UIDescScriptObject (uiDesc, jsContext.get ());

		registerFunctions (jsContext.get ());
		registerMathFunctions (jsContext.get ());

		jsContext->getRoot ()->addChild ("uiDesc"sv, uiDescObject);

		jsContext->addNative (
			"function createTimer(context, fireTime, callback)"sv, [this] (CScriptVar* var) {
				auto context = var->getParameter ("context"sv);
				auto fireTime = var->getParameter ("fireTime"sv);
				auto callback = var->getParameter ("callback"sv);
				if (!fireTime->isInt ())
				{
					throw CScriptException ("Expect integer as first parameter on timer creation");
				}
				if (!callback->isFunction ())
				{
					throw CScriptException (
						"Expect function as second parameter on timer creation");
				}
				auto timerObj = TimerScriptObject (
					fireTime->getInt (), callback->deepCopy (), [this, context] (auto callback) {
						using namespace ScriptingInternal;
						ScriptAddChildScoped scs (*jsContext->getRoot (), "timerContext"sv,
												  context);
						return evalScript (callback, "timerCallback (timerContext);"sv,
										   "timerCallback");
					});
				var->setReturnVar (timerObj);
			});
		jsContext->addNative (
			"function iterateSubViews(view, context, callback)"sv, [this] (CScriptVar* var) {
				auto view = var->getParameter ("view"sv);
				auto context = var->getParameter ("context"sv);
				auto callback = var->getParameter ("callback"sv);
				if (!view->isObject ())
				{
					throw CScriptException ("Expect object as first parameter on iterateSubViews");
				}
				if (!callback->isFunction ())
				{
					throw CScriptException (
						"Expect function as second parameter on iterateSubViews");
				}
				auto it =
					std::find_if (viewScriptMap.begin (), viewScriptMap.end (),
								  [&] (const auto& el) { return el.second->getVar () == view; });
				if (it == viewScriptMap.end ())
					throw CScriptException ("View not found in iterateSubViews");
				auto container = it->first->asViewContainer ();
				if (!container)
					return; // no sub views
				container->forEachChild ([&] (auto child) {
					using namespace ScriptingInternal;
					auto childScriptObject = viewScriptMap.find (child);
					if (childScriptObject != viewScriptMap.end ())
					{
						ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
												  childScriptObject->second->getVar ());
						ScriptAddChildScoped scs2 (*jsContext->getRoot (), "context"sv, context);
						evalScript (callback, "callback (child, context);"sv, "callback");
					}
					else
					{
						auto scriptObj =
							addView (child, std::make_unique<ViewScriptObject> (child, this));
						ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
												  scriptObj->getVar ());
						ScriptAddChildScoped scs2 (*jsContext->getRoot (), "context"sv, context);
						evalScript (callback, "callback (child, context);"sv, "callback");
					}
				});
			});
		jsContext->addNative ("function log(obj)",
							  [this] (CScriptVar* var) { log (var->getParameter ("obj"sv)); });

		jsContext->addNative ("function makeTransformMatrix()", [] (CScriptVar* var) {
			auto tm = makeTransformMatrixObject ();
			var->setReturnVar (tm);
			tm->release ();
		});
	}

	void reset (bool terminate = false)
	{
		for (auto it = viewScriptMap.begin (); it != viewScriptMap.end ();)
		{
			viewRemoved (it->first);
			uninstallListeners (it->first);
			it = eraseViewFromMap (it);
		}
		if (!terminate)
			init ();
	}

	void initWithScript (const std::string* script)
	{
		if (script)
		{
			auto scriptContent = getScriptFileContent (*script);
			if (!scriptContent.empty ())
				script = &scriptContent;
			evalScript (*script);
		}
	}
	CScriptVar* getRoot () const override { return jsContext->getRoot (); }
	IUIDescription* getUIDescription () const override { return uiDesc; }

	void installListeners (CView* view)
	{
		view->registerViewListener (this);
		view->registerViewEventListener (this);
		if (auto viewContainer = view->asViewContainer ())
			viewContainer->registerViewContainerListener (this);
		else if (auto control = dynamic_cast<CControl*> (view))
			control->registerControlListener (this);
	}

	void uninstallListeners (CView* view)
	{
		view->unregisterViewListener (this);
		view->unregisterViewEventListener (this);
		if (auto viewContainer = view->asViewContainer ())
			viewContainer->unregisterViewContainerListener (this);
		if (auto control = dynamic_cast<CControl*> (view))
			control->unregisterControlListener (this);
	}

	ScriptObject evalScript (std::string_view script) noexcept override
	{
		try
		{
			auto result = jsContext->evaluateComplex (script);
#if 0 // DEBUG
			if (result.getVar () && !result.getVar ()->isUndefined ())
			{
				DebugPrint ("%s\n", result.getVar ()->getString ().data ());
			}
#endif
			return result.getVar ();
		}
		catch (const CScriptException& exc)
		{
#if DEBUG
			DebugPrint ("Scripting Exception: %s\n", exc.text.data ());
#endif
			if (onScriptException)
				onScriptException (exc.text);
			return {};
		}
	}

	ScriptObject evalScript (CScriptVar* object, std::string_view script,
							 const std::string& objectName = "view") noexcept
	{
		if (!object || script.empty ())
			return {};
		vstgui_assert (object->getRefs () > 0);
		object->addRef ();
		ScriptAddChildScoped scs (*jsContext->getRoot (), objectName, object);
		auto result = evalScript (script);
		object->release ();
		return result;
	}

	template<typename Proc>
	void callWhenScriptHasFunction (CView* view, std::string_view funcName, Proc proc)
	{
		auto it = viewScriptMap.find (view);
		if (it == viewScriptMap.end ())
			return;
		if (it->second->getVar ()->findChild (funcName))
			proc (this, it->second);
	}

	void viewAttached (CView* view) override
	{
		callWhenScriptHasFunction (view, "onAttached"sv, [] (auto This, auto& obj) {
			static constexpr auto script = R"(view.onAttached(view);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void viewRemoved (CView* view) override
	{
		callWhenScriptHasFunction (view, "onRemoved"sv, [] (auto This, auto& obj) {
			static constexpr auto script = R"(view.onRemoved(view);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void viewSizeChanged (CView* view, const CRect& oldSize) override
	{
		callWhenScriptHasFunction (view, "onSizeChanged"sv, [&] (auto This, auto& obj) {
			auto newSize = view->getViewSize ();
			ScriptObject newSizeObject = ScriptingInternal::makeScriptRect (newSize);
			ScriptAddChildScoped scs (*jsContext->getRoot (), "newSize"sv, newSizeObject);
			static constexpr auto script = R"(view.onSizeChanged(view, newSize);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void viewLostFocus (CView* view) override
	{
		callWhenScriptHasFunction (view, "onLostFocus"sv, [&] (auto This, auto& obj) {
			static constexpr auto script = R"(view.onLostFocus(view);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void viewTookFocus (CView* view) override
	{
		callWhenScriptHasFunction (view, "onTookFocus"sv, [&] (auto This, auto& obj) {
			static constexpr auto script = R"(view.onTookFocus(view);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void viewOnMouseEnabled (CView* view, bool state) override
	{
		callWhenScriptHasFunction (view, "onMouseEnabled"sv, [&] (auto This, auto& obj) {
			static constexpr auto scriptEnabled = R"(view.onMouseEnabled(view, true);)"sv;
			static constexpr auto scriptDisabled = R"(view.onMouseEnabled(view, false);)"sv;
			This->evalScript (obj->getVar (), state ? scriptEnabled : scriptDisabled);
		});
	}

	void callViewAddedOrRemoved (CViewContainer* container, CView* view, std::string_view function,
								 std::string_view script)
	{
		callWhenScriptHasFunction (container, function, [&] (auto This, auto& obj) {
			auto childScriptObject = viewScriptMap.find (view);
			if (childScriptObject != viewScriptMap.end ())
			{
				ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv,
										  childScriptObject->second->getVar ());
				This->evalScript (obj->getVar (), script);
			}
			else
			{
				auto scriptObj = addView (view, std::make_unique<ViewScriptObject> (view, this));
				ScriptAddChildScoped scs (*jsContext->getRoot (), "child"sv, scriptObj->getVar ());
				This->evalScript (obj->getVar (), script);
			}
		});
	}

	void viewContainerViewAdded (CViewContainer* container, CView* view) override
	{
		callViewAddedOrRemoved (container, view, "onViewAdded"sv,
								R"(view.onViewAdded(view, child);)"sv);
	}

	void viewContainerViewRemoved (CViewContainer* container, CView* view) override
	{
		callViewAddedOrRemoved (container, view, "onViewRemoved"sv,
								R"(view.onViewRemoved(view, child);)"sv);
	}

	void checkEventConsumed (ScriptObject& obj, Event& event)
	{
		if (auto consume = obj->findChild ("consumed"))
		{
			if (consume->getVar ()->isInt () && consume->getVar ()->getInt ())
				event.consumed = true;
		}
	}

	void callEventFunction (CScriptVar* var, Event& event, std::string_view script) noexcept
	{
		auto scriptEvent = ScriptingInternal::makeScriptEvent (event);
		ScriptAddChildScoped scs (*jsContext->getRoot (), "event"sv, scriptEvent);
		evalScript (var, script);
		checkEventConsumed (scriptEvent, event);
	}

	void viewOnEvent (CView* view, Event& event) override
	{
		auto applyEventMouseLocalPosition = [&] (auto proc) {
			auto& mouseEvent = castMousePositionEvent (event);
			auto oldPos = mouseEvent.mousePosition;
			mouseEvent.mousePosition -= view->getViewSize ().getTopLeft ();
			proc ();
			mouseEvent.mousePosition = oldPos;
		};
		switch (event.type)
		{
			case EventType::MouseEnter:
			{
				applyEventMouseLocalPosition ([&] () {
					callWhenScriptHasFunction (view, "onMouseEnter"sv, [&] (auto This, auto& obj) {
						static constexpr auto script = R"(view.onMouseEnter(view, event);)"sv;
						callEventFunction (obj->getVar (), event, script);
					});
				});
				break;
			}
			case EventType::MouseExit:
			{
				applyEventMouseLocalPosition ([&] () {
					callWhenScriptHasFunction (view, "onMouseExit"sv, [&] (auto This, auto& obj) {
						static constexpr auto script = R"(view.onMouseExit(view, event);)"sv;
						callEventFunction (obj->getVar (), event, script);
					});
				});
				break;
			}
			case EventType::MouseDown:
			{
				applyEventMouseLocalPosition ([&] () {
					callWhenScriptHasFunction (view, "onMouseDown"sv, [&] (auto This, auto& obj) {
						static constexpr auto script = R"(view.onMouseDown(view, event);)"sv;
						callEventFunction (obj->getVar (), event, script);
					});
				});
				break;
			}
			case EventType::MouseUp:
			{
				applyEventMouseLocalPosition ([&] () {
					callWhenScriptHasFunction (view, "onMouseUp"sv, [&] (auto This, auto& obj) {
						static constexpr auto script = R"(view.onMouseUp(view, event);)"sv;
						callEventFunction (obj->getVar (), event, script);
					});
				});
				break;
			}
			case EventType::MouseMove:
			{
				applyEventMouseLocalPosition ([&] () {
					callWhenScriptHasFunction (view, "onMouseMove"sv, [&] (auto This, auto& obj) {
						static constexpr auto script = R"(view.onMouseMove(view, event);)"sv;
						callEventFunction (obj->getVar (), event, script);
					});
				});
				break;
			}
			case EventType::MouseWheel:
			{
				applyEventMouseLocalPosition ([&] () {
					callWhenScriptHasFunction (view, "onMouseWheel"sv, [&] (auto This, auto& obj) {
						static constexpr auto script = R"(view.onMouseWheel(view, event);)"sv;
						callEventFunction (obj->getVar (), event, script);
					});
				});
				break;
			}
			case EventType::KeyDown:
			{
				callWhenScriptHasFunction (view, "onKeyDown"sv, [&] (auto This, auto& obj) {
					static constexpr auto script = R"(view.onKeyDown(view, event);)"sv;
					callEventFunction (obj->getVar (), event, script);
				});
				break;
			}
			case EventType::KeyUp:
			{
				callWhenScriptHasFunction (view, "onKeyUp"sv, [&] (auto This, auto& obj) {
					static constexpr auto script = R"(view.onKeyUp(view, event);)"sv;
					callEventFunction (obj->getVar (), event, script);
				});
				break;
			}
			default:
				break;
		}
	}

	void valueChanged (CControl* control) override
	{
		callWhenScriptHasFunction (control, "onValueChanged"sv, [&] (auto This, auto& obj) {
			ScriptObject controlValue;
			controlValue->setDouble (control->getValue ());
			ScriptAddChildScoped scs (*jsContext->getRoot (), "value"sv, controlValue);
			static constexpr auto script = R"(view.onValueChanged(view, value);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void controlBeginEdit (CControl* control) override
	{
		callWhenScriptHasFunction (control, "onBeginEdit"sv, [] (auto This, auto& obj) {
			static constexpr auto script = R"(view.onBeginEdit(view);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}
	void controlEndEdit (CControl* control) override
	{
		callWhenScriptHasFunction (control, "onEndEdit"sv, [] (auto This, auto& obj) {
			static constexpr auto script = R"(view.onEndEdit(view);)"sv;
			This->evalScript (obj->getVar (), script);
		});
	}

	void viewWillDelete (CView* view) override { removeView (view); }

	ViewScriptObject* addView (CView* view, std::unique_ptr<ViewScriptObject>&& scriptObject)
	{
		installListeners (view);
		viewScriptMap[view] = std::move (scriptObject);
		return viewScriptMap[view].get ();
	}

	ViewScriptMap::iterator eraseViewFromMap (ViewScriptMap::iterator el)
	{
		return viewScriptMap.erase (el);
	}

	ViewScriptMap::iterator removeView (CView* view) override
	{
		uninstallListeners (view);
		auto it = viewScriptMap.find (view);
		if (it != viewScriptMap.end ())
			return eraseViewFromMap (it);
		return viewScriptMap.end ();
	}

	ViewScriptObject* addView (CView* view) override
	{
		auto it = viewScriptMap.find (view);
		if (it != viewScriptMap.end ())
			return it->second.get ();
		return addView (view, std::make_unique<ViewScriptObject> (view, this));
	}

	void addView (CView* view, const std::string* script) noexcept
	{
		addView (view);
		if (script)
		{
			auto scriptContent = getScriptFileContent (*script);
			if (!scriptContent.empty ())
				script = &scriptContent;
			evalScript (viewScriptMap[view]->getVar (), *script);
		}
	}

	std::string getScriptFileContent (const std::string& script) const
	{
		constexpr std::string_view scriptFileCommand ("//scriptfile ");
		std::string_view scriptView (script);
		if (scriptView.length () > scriptFileCommand.length ())
		{
			if (scriptView.substr (0, scriptFileCommand.length ()) == scriptFileCommand)
			{
				auto scriptFileStr = scriptView.substr (scriptFileCommand.length ());
				if (scriptFileStr.length () < 255)
				{
					return readScriptContents (scriptFileStr);
				}
			}
		}
		return {};
	}

	void log (CScriptVar* var) const
	{
#if DEBUG
		static constexpr size_t bufferSize = 512;

		struct DebugOutputStream : private std::streambuf,
								   public std::ostream
		{

			DebugOutputStream () : std::ostream (this) { data.reserve (bufferSize); }
			~DebugOutputStream () noexcept
			{
				if (!data.empty ())
					flush ();
			}

		private:
			int overflow (int c) override
			{
				if (data.length () >= bufferSize)
					flush ();
				data += static_cast<char> (c);
				return 0;
			}
			void flush ()
			{
				DebugPrint ("%s", data.data ());
				data.clear ();
				data.reserve (bufferSize);
			}
			std::string data;
		};
		DebugOutputStream logStream;
		var->getJSON (logStream);
		logStream << "\n";
#endif
	}
};

//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
ScriptContext::ScriptContext (IUIDescription* uiDesc, OnScriptExceptionFunc&& onExceptionFunc,
							  ReadScriptContentsFunc&& readContentsFunc)
{
	impl =
		std::make_unique<Impl> (uiDesc, std::move (onExceptionFunc), std::move (readContentsFunc));
}

//------------------------------------------------------------------------
ScriptContext::~ScriptContext () noexcept {}

//------------------------------------------------------------------------
void ScriptContext::init (const std::string& initScript) { impl->initWithScript (&initScript); }

//------------------------------------------------------------------------
void ScriptContext::onViewCreated (CView* view, const std::string& script)
{
	impl->addView (view, &script);
}

//------------------------------------------------------------------------
void ScriptContext::reset () { impl->reset (); }

//------------------------------------------------------------------------
std::string ScriptContext::eval (std::string_view script) const
{
	if (!impl->jsContext)
		return {};
	try
	{
		auto result = impl->jsContext->evaluateComplex (script);
		std::stringstream stream;
		result.getVar ()->getJSON (stream);
		return stream.str ();
	}
	catch (const CScriptException& exc)
	{
		if (impl->onScriptException)
			impl->onScriptException (exc.text);
	}
	return {};
}

//------------------------------------------------------------------------
} // ScriptingInternal

//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
struct UIScripting::Impl
{
	using JSViewFactoryPtr = std::unique_ptr<ScriptingInternal::JavaScriptViewFactory>;
	using ScriptContextPtr = std::unique_ptr<ScriptingInternal::ScriptContext>;

	std::unordered_map<const IUIDescription*, std::pair<JSViewFactoryPtr, ScriptContextPtr>> map;

	static OnScriptExceptionFunc onScriptExceptionFunc;
	static ReadScriptContentsFunc readScriptContentsFunc;

	static std::string readScriptContentsFromResource (std::string_view filename);
};

//------------------------------------------------------------------------
UIScripting::OnScriptExceptionFunc UIScripting::Impl::onScriptExceptionFunc;
UIScripting::ReadScriptContentsFunc UIScripting::Impl::readScriptContentsFunc;

//------------------------------------------------------------------------
std::string UIScripting::Impl::readScriptContentsFromResource (std::string_view filename)
{
	std::string fileStr (filename);
	CResourceDescription desc (fileStr.data ());
	if (auto stream = getPlatformFactory ().createResourceInputStream (desc))
	{
		std::string fileContent;
		while (true)
		{
			char buffer[256];
			auto numRead = stream->readRaw (buffer, static_cast<uint32_t> (std::size (buffer)));
			if (numRead == kStreamIOError)
				return {};
			fileContent.append (buffer, numRead);
			if (numRead < std::size (buffer))
				break;
		}
		return fileContent;
	}
	return {};
};

//------------------------------------------------------------------------
UIScripting::UIScripting () { impl = std::make_unique<Impl> (); }

//------------------------------------------------------------------------
UIScripting::~UIScripting () noexcept = default;

//------------------------------------------------------------------------
void UIScripting::afterParsing (IUIDescription* desc) {}

//------------------------------------------------------------------------
void UIScripting::beforeSaving (IUIDescription* desc) {}

//------------------------------------------------------------------------
void UIScripting::onDestroy (IUIDescription* desc)
{
	auto it = impl->map.find (desc);
	if (it != impl->map.end ())
		impl->map.erase (it);
}

//------------------------------------------------------------------------
auto UIScripting::onCreateTemplateView (const IUIDescription* desc, const CreateTemplateViewFunc& f)
	-> CreateTemplateViewFunc
{
	return [=] (auto name, auto controller) {
		return f (name, controller);
	};
}

//------------------------------------------------------------------------
IViewFactory* UIScripting::getViewFactory (IUIDescription* desc, IViewFactory* originalFactory)
{
	using namespace ScriptingInternal;

	auto it = impl->map.find (desc);
	if (it != impl->map.end ())
		return it->second.first.get ();
	auto onScriptException = Impl::onScriptExceptionFunc;
	if (!onScriptException)
		onScriptException = [] (std::string_view reason) {
			std::cerr << reason << '\n';
		};
	auto readScriptContentsFunc = [] (auto filename) {
		std::string result;
		if (Impl::readScriptContentsFunc)
			result = Impl::readScriptContentsFunc (filename);
		if (result.empty ())
		{
			result = Impl::readScriptContentsFromResource (filename);
		}
		return result;
	};
	auto scripting = std::make_unique<ScriptContext> (desc, std::move (onScriptException),
													  std::move (readScriptContentsFunc));
	auto viewFactory = std::make_unique<JavaScriptViewFactory> (scripting.get (), originalFactory);
	auto result =
		impl->map.emplace (desc, std::make_pair (std::move (viewFactory), std::move (scripting)));
	return result.first->second.first.get ();
}

//------------------------------------------------------------------------
void UIScripting::onEditingStart (IUIDescription* desc)
{
	auto it = impl->map.find (desc);
	if (it != impl->map.end ())
	{
		it->second.second->reset ();
		it->second.first->setScriptingDisabled (true);
	}
}

//------------------------------------------------------------------------
void UIScripting::onEditingEnd (IUIDescription* desc)
{
	auto it = impl->map.find (desc);
	if (it != impl->map.end ())
		it->second.first->setScriptingDisabled (false);
}

//------------------------------------------------------------------------
void UIScripting::init (const OnScriptExceptionFunc& onExceptionFunc,
						const ReadScriptContentsFunc& readScriptContentsFunc)
{
	if (onExceptionFunc)
		Impl::onScriptExceptionFunc = onExceptionFunc;
	if (readScriptContentsFunc)
		Impl::readScriptContentsFunc = readScriptContentsFunc;
	UIDescriptionAddOnRegistry::add (std::make_unique<UIScripting> ());
	static ScriptingInternal::JavaScriptDrawableViewCreator jsViewCreator;
	UIViewFactory::registerViewCreator (jsViewCreator);
	static ScriptingInternal::JavaScriptDrawableControlCreator jsControlCreator;
	UIViewFactory::registerViewCreator (jsControlCreator);
}

//------------------------------------------------------------------------
} // VSTGUI
