/*
 * \brief  Utilities for handling the 'session_requests' ROM
 * \author Emery Hemingway
 * \date   2018-04-08
 */

/*
 * Copyright (C) 2018 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 __SESSION_REQUESTS_H_
#define __SESSION_REQUESTS_H_

#include <base/attached_rom_dataspace.h>
#include <base/session_state.h>
#include <base/component.h>

namespace Genode {
	struct Session_request_handler;
	class Session_requests_rom;
};


struct Genode::Session_request_handler : Interface
{
	virtual void handle_session_create(Session_state::Name const &,
	                                   Parent::Server::Id,
	                                   Session_state::Args const &) = 0;
	virtual void handle_session_upgrade(Parent::Server::Id,
	                                    Session_state::Args const &) { }
	virtual void handle_session_close(Parent::Server::Id) = 0;
};


class Genode::Session_requests_rom : public Signal_handler<Session_requests_rom>
{
	private:

		Parent                  &_parent;
		Session_request_handler &_requests_handler;

		Attached_rom_dataspace  _parent_rom;

		void _process()
		{
			_parent_rom.update();
			Node const &requests = _parent_rom.node();

			using Args = Session_state::Args;

			auto args_from_request = [] (Node const &request)
			{
				return request.with_sub_node("args",
					[] (Node const &node) { return Args(Node::Quoted_content(node)); },
					[&] {
						Genode::error("failed to parse request ", request);
						return Args();
					});
			};

			auto const create_fn = [&] (Node const &request)
			{
				Parent::Server::Id const id {
					request.attribute_value("id", ~0UL) };

				using Name = Session_state::Name;
				using Args = Session_state::Args;

				Name const name = request.attribute_value("service", Name());
				Args const args = args_from_request(request);
				if (args.length() <= 1)
					return;

				try { _requests_handler.handle_session_create(name, id, args); }
				catch (Service_denied) {
					_parent.session_response(id, Parent::Session_response::DENIED); }
				catch (Insufficient_ram_quota) {
					_parent.session_response(id, Parent::Session_response::INSUFFICIENT_RAM); }
				catch (Insufficient_cap_quota) {
					_parent.session_response(id, Parent::Session_response::INSUFFICIENT_CAPS); }
				catch (...) {
					error("unhandled exception while creating session");
					_parent.session_response(id, Parent::Session_response::DENIED);
					throw;
				}
			};

			auto const upgrade_fn = [&] (Node const &request)
			{
				Parent::Server::Id const id {
					request.attribute_value("id", ~0UL) };

				Args const args = args_from_request(request);
				if (args.length() > 1)
					_requests_handler.handle_session_upgrade(id, args);
			};

			auto const close_fn = [&] (Node const &request)
			{
				Parent::Server::Id const id {
					request.attribute_value("id", ~0UL) };
				_requests_handler.handle_session_close(id);
			};

			/* close sessions to free resources */
			requests.for_each_sub_node("close", close_fn);

			/* service existing sessions */
			requests.for_each_sub_node("upgrade", upgrade_fn);

			/* create new sessions */
			requests.for_each_sub_node("create", create_fn);
		}

	public:

		Session_requests_rom(Genode::Env &env,
		                     Session_request_handler &requests_handler)
		: Signal_handler<Session_requests_rom>(env.ep(), *this, &Session_requests_rom::_process),
		  _parent(env.parent()),
		  _requests_handler(requests_handler),
		  _parent_rom(env, "session_requests")
		{
			_parent_rom.sigh(*this);
		}

		/**
		 * Post a signal to this requests handler
		 */
		void schedule() {
			Signal_transmitter(*this).submit(); }
};

#endif
