/*
 * \brief  Runtime for hosting GUI dialogs in child components
 * \author Norman Feske
 * \date   2023-03-24
 */

/*
 * Copyright (C) 2023 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _INCLUDE__DIALOG__SANDBOXED_RUNTIME_H_
#define _INCLUDE__DIALOG__SANDBOXED_RUNTIME_H_

#include <util/dictionary.h>
#include <util/color.h>
#include <os/dynamic_rom_session.h>
#include <base/session_object.h>
#include <report_session/report_session.h>
#include <sandbox/sandbox.h>
#include <dialog/types.h>
#include <input/seq_number_generator.h>

namespace Dialog { struct Sandboxed_runtime; }


class Dialog::Sandboxed_runtime : Noncopyable
{
	public:

		class View;

		struct Event_handler_base : Interface, Noncopyable
		{
			virtual void handle_event(Event const &event) = 0;
		};

		template <typename T> class Event_handler;

		using Start_name = String<128>;

	private:

		Env       &_env;
		Allocator &_alloc;
		Sandbox   &_sandbox;

		struct Optional_event_handler
		{
			Event_handler_base *_ptr;

			void handle_event(Event const &event)
			{
				if (_ptr) _ptr->handle_event(event);
			}

		} _optional_event_handler { };

		using Views = Dictionary<View, Top_level_dialog::Name>;

		Input::Seq_number_generator _seq_number_generator { };

		Views _views { };

		struct Gui_session;
		struct Report_session;

		using Gui_service    = Sandbox::Local_service<Gui_session>;
		using Rom_service    = Sandbox::Local_service<Dynamic_rom_session>;
		using Report_service = Sandbox::Local_service<Report_session>;

		void _handle_gui_service();
		void _handle_rom_service();
		void _handle_report_service();

		struct Service_handler : Sandbox::Local_service_base::Wakeup
		{
			Sandboxed_runtime &_runtime;

			using Member = void (Sandboxed_runtime::*) ();
			Member _member;

			void wakeup_local_service() override
			{
				(_runtime.*_member)();
			}

			Service_handler(Sandboxed_runtime &runtime, Member member)
			: _runtime(runtime), _member(member) { }
		};

		Service_handler _gui_handler    { *this, &Sandboxed_runtime::_handle_gui_service };
		Service_handler _rom_handler    { *this, &Sandboxed_runtime::_handle_rom_service };
		Service_handler _report_handler { *this, &Sandboxed_runtime::_handle_report_service };

		Gui_service    _gui_service;
		Rom_service    _rom_service;
		Report_service _report_service;

		struct Menu_view_state
		{
			Start_name const name;
			Ram_quota  const initial_ram  { 4*1024*1024 };
			Cap_quota  const initial_caps { 130 };

			Ram_quota ram  = initial_ram;
			Cap_quota caps = initial_caps;

			unsigned version = 0;

			void trigger_restart()
			{
				version++;
				ram  = initial_ram;
				caps = initial_caps;
			}

			/**
			 * Adapt runtime state information to the child
			 *
			 * This method responds to RAM and cap-resource requests by increasing
			 * the resource quotas as needed.
			 *
			 * \param  child  child node of the sandbox state report
			 * \return true if runtime must be reconfigured so that the changes
			 *         can take effect
			 */
			bool apply_child_state_report(Node const &child)
			{
				bool result = false;

				if (child.attribute_value("name", Start_name()) != name)
					return false;

				child.with_optional_sub_node("ram", [&] (Node const &node) {
					if (node.has_attribute("requested")) {
						ram.value *= 2;
						result = true; } });

				child.with_optional_sub_node("caps", [&] (Node const &node) {
					if (node.has_attribute("requested")) {
						caps.value += 100;
						result = true; } });

				return result;
			}

			void gen_start_node(Generator &, Views const &) const;

		} _menu_view_state;

		class Report_session : public Session_object<Report::Session>
		{
			public:

				struct Handler : Interface, Genode::Noncopyable
				{
					virtual void handle_report() = 0;
				};

			private:

				Attached_ram_dataspace _client_ds;
				Attached_ram_dataspace _local_ds;

				Constructible<Node> _node { }; /* points inside _local_ds */

				Handler &_handler;


				/*******************************
				 ** Report::Session interface **
				 *******************************/

				Dataspace_capability dataspace() override { return _client_ds.cap(); }

				void submit(size_t length) override
				{
					size_t const num_bytes = min(_client_ds.size(), length);

					memcpy(_local_ds.local_addr<char>(), _client_ds.local_addr<char>(),
					       num_bytes);

					_node.destruct();

					_node.construct(Const_byte_range_ptr(_local_ds.local_addr<char>(),
					                                     num_bytes));

					_handler.handle_report();
				}

				void response_sigh(Signal_context_capability) override { }

				size_t obtain_response() override { return 0; }

			public:

				template <typename... ARGS>
				Report_session(Env &env, Handler &handler,
				               Entrypoint &ep, Resources const &resources,
				               ARGS &&... args)
				:
					Session_object(ep, resources, args...),
					_client_ds(env.ram(), env.rm(), resources.ram_quota.value/2),
					_local_ds (env.ram(), env.rm(), resources.ram_quota.value/2),
					_handler(handler)
				{ }

				template <typename FN>
				void with_node(FN const &fn) const
				{
					if (_node.constructed())
						fn(*_node);
					else
						fn(Node());
				}
		};

		template <typename T>
		struct Hover_handler : Report_session::Handler
		{
			T &_obj;
			void (T::*_member) ();

			Hover_handler(T &obj, void (T::*member)())
			: _obj(obj), _member(member) { }

			void handle_report() override
			{
				(_obj.*_member)();
			}
		};

		Top_level_dialog::Name _hovered_dialog { };

		Hover_handler<Sandboxed_runtime> _hover_handler {
			*this, &Sandboxed_runtime::_handle_hover };

		void _handle_hover();

		Constructible<Report_session> _hover_report_session { };

		Event::Seq_number _hover_seq_number { };

	public:

		struct Attr { Start_name name; };

		Sandboxed_runtime(Env &, Allocator &, Sandbox &,
		                  Attr const &attr = { "view" });

		/**
		 * Respond to sandbox state changes
		 *
		 * \return true  if the sandbox configuration needs to be updated
		 */
		bool apply_sandbox_state(Node const &);

		void gen_start_nodes(Generator &) const;
};


class Dialog::Sandboxed_runtime::View : private Views::Element
{
	private:

		/* needed for privately inheriting 'Views::Element' */
		friend class Dictionary<View, Top_level_dialog::Name>;
		friend class Genode::Avl_node<View>;
		friend class Avl_tree<View>;
		friend class Sandboxed_runtime;

		Env &_env;

		Sandboxed_runtime &_runtime;

		Top_level_dialog &_dialog;

		bool _dialog_hovered = false;

		/* sequence numbers to correlate hover info with click/clack events */
		Constructible<Event::Seq_number> _click_seq_number { };
		Constructible<Event::Seq_number> _clack_seq_number { };

		bool _click_delivered = false; /* used to deliver each click only once */

		bool _dragged() const
		{
			return _click_seq_number.constructed()
			   &&  _click_seq_number->value == _runtime._seq_number_generator.value()
			   &&  _click_delivered;
		}

		bool _hover_observable_without_click = false;

		void _with_dialog_hover(auto const &fn) const
		{
			bool done = false;

			if (_runtime._hover_report_session.constructed())
				_runtime._hover_report_session->with_node([&] (Node const &hover) {
					hover.with_optional_sub_node("dialog", [&] (Node const &dialog) {
						fn(dialog);
						done = true; }); });

			if (!done)
				fn(Node());
		}

		struct Rom_producer : Dynamic_rom_session::Producer
		{
			View const &_view;

			Rom_producer(View const &view)
			:
				Dynamic_rom_session::Producer("dialog"),
				_view(view)
			{ }

			void generate(Generator &g) override
			{
				_view._with_dialog_hover([&] (Node const &hover) {

					Event::Dragged const dragged { _view._dragged() };

					bool const supply_hover = _view._hover_observable_without_click
					                       || dragged.value;

					static Node omitted_hover { };

					At const at { { _view._runtime._seq_number_generator.value() },
					              supply_hover ? hover : omitted_hover };

					Scope<> top_level_scope(g, at, dragged, { _view._dialog.name });

					_view._dialog.view(top_level_scope);
				});
			}
		} _dialog_producer { *this };

		Dynamic_rom_session _dialog_rom_session {
			_env.ep(), _env.ram(), _env.rm(), _dialog_producer };

		void _gen_menu_view_dialog(Generator &) const;
		void _gen_menu_view_routes(Generator &) const;

		void _handle_input_event(Input::Event const &);

		void _handle_hover();
		void _leave();
		void _try_handle_click_and_clack();

		Registry<Gui_session> _gui_sessions { };

	public:

		int      xpos       { };
		int      ypos       { };
		unsigned min_width  { };
		unsigned min_height { };
		bool     opaque     { };
		Color    background { };

		View(Sandboxed_runtime &runtime, Top_level_dialog &dialog)
		:
			Views::Element(runtime._views, dialog.name),
			_env(runtime._env), _runtime(runtime), _dialog(dialog)
		{ }

		~View();

		void refresh() { _dialog_rom_session.trigger_update(); }

		bool if_hovered(auto const &fn) const
		{
			bool result = false;
			if (_dialog_hovered)
				_with_dialog_hover([&] (Node const &location) {
					result = fn(Hovered_at { Event::Seq_number { }, location }); });
			return result;
		}
};


template <typename T>
class Dialog::Sandboxed_runtime::Event_handler : Event_handler_base
{
	private:

		Sandboxed_runtime &_runtime;

		T &_obj;

		void (T::*_member) (Event const &);
		void handle_event(Event const &event) override { (_obj.*_member)(event); }

	public:

		Event_handler(Sandboxed_runtime &runtime, T &obj, void (T::*member)(Event const &))
		:
			_runtime(runtime), _obj(obj), _member(member)
		{
			/* register event handler at runtime */
			_runtime._optional_event_handler._ptr = this;
		}

		~Event_handler() { _runtime._optional_event_handler._ptr = nullptr; }
};

#endif /* _INCLUDE__DIALOG__SANDBOXED_RUNTIME_H_ */
