/*
 * \brief  Service management framework
 * \author Norman Feske
 * \date   2006-07-12
 */

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

#ifndef _INCLUDE__BASE__SERVICE_H_
#define _INCLUDE__BASE__SERVICE_H_

#include <root/client.h>
#include <base/printf.h>
#include <util/list.h>
#include <ram_session/client.h>
#include <base/env.h>

namespace Genode {

	/**
	 * Client role
	 *
	 * A client is someone who applies for a service. If the service is not
	 * available yet, we enqueue the client into a wait queue and wake him up
	 * as soon as the requested service gets available.
	 */
	class Client : public List<Client>::Element
	{
		private:

			Cancelable_lock _service_apply_lock;
			const char     *_apply_for;

		public:

			/**
			 * Constructor
			 */
			Client(): _service_apply_lock(Lock::LOCKED), _apply_for(0) { }

			virtual ~Client() { }

			/**
			 * Set/Request service name that we are currently applying for
			 */
			void apply_for(const char *apply_for) { _apply_for = apply_for; }
			const char *apply_for() { return _apply_for; }

			/**
			 * Service wait queue support
			 */
			void sleep()  { _service_apply_lock.lock(); }
			void wakeup() { _service_apply_lock.unlock(); }
	};


	/**
	 * Server role
	 *
	 * A server is a process that provides one or multiple services. For the
	 * most part, this class is used as an opaque key to represent the server
	 * role.
	 */
	class Server
	{
		private:

			Ram_session_capability _ram;

		public:

			/**
			 * Constructor
			 *
			 * \param ram  RAM session capability of the server process used,
			 *             for quota transfers from/to the server
			 */
			Server(Ram_session_capability ram): _ram(ram) { }

			/**
			 * Return RAM session capability of the server process
			 */
			Ram_session_capability ram_session_cap() const { return _ram; }
	};


	class Service : public List<Service>::Element
	{
		public:

			enum { MAX_NAME_LEN = 32 };

		private:

			char _name[MAX_NAME_LEN];

		public:

			/*********************
			 ** Exception types **
			 *********************/

			class Invalid_args   { };
			class Unavailable    { };
			class Quota_exceeded { };

			/**
			 * Constructor
			 *
			 * \param name  service name
			 */
			Service(const char *name) { strncpy(_name, name, sizeof(_name)); }

			virtual ~Service() { }

			/**
			 * Return service name
			 */
			const char *name() const { return _name; }

			/**
			 * Create session
			 *
			 * \param args  session-construction arguments
			 *
			 * \throw Invalid_args
			 * \throw Unavailable
			 * \throw Quota_exceeded
			 */
			virtual Session_capability session(const char *args) = 0;

			/**
			 * Extend resource donation to an existing session
			 */
			virtual void upgrade(Session_capability session, const char *args) = 0;

			/**
			 * Close session
			 */
			virtual void close(Session_capability session) { }

			/**
			 * Return server providing the service
			 */
			virtual Server *server() const { return 0; }

			/**
			 * Return the RAM session to be used for trading resources
			 */
			Ram_session_capability ram_session_cap()
			{
				if (server())
					return server()->ram_session_cap();
				return Ram_session_capability();
			}
	};


	/**
	 * Representation of a locally implemented service
	 */
	class Local_service : public Service
	{
		private:

			Root *_root;

		public:

			Local_service(const char *name, Root *root)
			: Service(name), _root(root) { }

			Session_capability session(const char *args)
			{
				try { return _root->session(args); }
				catch (Root::Invalid_args)   { throw Invalid_args(); }
				catch (Root::Unavailable)    { throw Unavailable(); }
				catch (Root::Quota_exceeded) { throw Quota_exceeded(); }
			}

			void upgrade(Session_capability session, const char *args) {
				_root->upgrade(session, args); }

			void close(Session_capability session) {
				_root->close(session); }
	};


	/**
	 * Representation of a service provided by our parent
	 */
	class Parent_service : public Service
	{
		public:

			Parent_service(const char *name) : Service(name) { }

			Session_capability session(const char *args)
			{
				try { return env()->parent()->session(name(), args); }
				catch (Parent::Unavailable) {
					PWRN("parent has no service \"%s\"", name());
					throw Unavailable();
				}
				catch (Parent::Quota_exceeded) { throw Quota_exceeded(); }
			}

			void upgrade(Session_capability session, const char *args) {
				env()->parent()->upgrade(session, args); }

			void close(Session_capability session) {
				env()->parent()->close(session); }
	};


	/**
	 * Representation of a service that is implemented in a child
	 */
	class Child_service : public Service
	{
		private:

			Root_capability _root_cap;
			Root_client     _root;
			Server         *_server;

		public:

			/**
			 * Constructor
			 *
			 * \param name    name of service
			 * \param root    capability to root interface
			 * \param server  server process providing the service
			 */
			Child_service(const char     *name,
			              Root_capability root,
			              Server         *server)
			: Service(name), _root_cap(root), _root(root), _server(server) { }

			Server *server() const { return _server; }

			Session_capability session(const char *args)
			{
				if (!_root_cap.valid())
					throw Unavailable();

				try { return _root.session(args); }
				catch (Root::Invalid_args)   { throw Invalid_args();   }
				catch (Root::Unavailable)    { throw Unavailable();    }
				catch (Root::Quota_exceeded) { throw Quota_exceeded(); }
			}

			void upgrade(Session_capability sc, const char *args)
			{
				if (!_root_cap.valid())
					throw Unavailable();

				try { _root.upgrade(sc, args); }
				catch (Root::Invalid_args)   { throw Invalid_args();   }
				catch (Root::Unavailable)    { throw Unavailable();    }
				catch (Root::Quota_exceeded) { throw Quota_exceeded(); }
			}

			void close(Session_capability sc) { _root.close(sc); }
	};


	/**
	 * Container for holding service representations
	 */
	class Service_registry
	{
		protected:

			Lock          _service_wait_queue_lock;
			List<Client>  _service_wait_queue;
			List<Service> _services;

		public:

			/**
			 * Probe for service with specified name
			 *
			 * \param name    service name
			 * \param server  server providing the service,
			 *                default (0) for any server
			 */
			Service *find(const char *name, Server *server = 0)
			{
				if (!name) return 0;

				Lock::Guard lock_guard(_service_wait_queue_lock);

				for (Service *s = _services.first(); s; s = s->next())
					if (strcmp(s->name(), name) == 0
					 && (!server || s->server() == server)) return s;

				return 0;
			}

			/**
			 * Check if service name is ambiguous
			 *
			 * \return true  if the same service is provided multiple
			 *               times
			 */
			bool is_ambiguous(const char *name)
			{
				Lock::Guard lock_guard(_service_wait_queue_lock);

				/* count number of services with the specified name */
				unsigned cnt = 0;
				for (Service *s = _services.first(); s; s = s->next())
					cnt += (strcmp(s->name(), name) == 0);
				return cnt > 1;
			}

			/**
			 * Return first service provided by specified server
			 */
			Service *find_by_server(Server *server)
			{
				Lock::Guard lock_guard(_service_wait_queue_lock);

				for (Service *s = _services.first(); s; s = s->next())
					if (s->server() == server)
						return s;

				return 0;
			}

			/**
			 * Wait for service
			 *
			 * This function is called by the clients's thread
			 * when requesting a session creation. It blocks
			 * if the requested service is not available.
			 *
			 * \return  service structure that matches the request or
			 *          0 if the waiting was canceled.
			 */
			Service *wait_for_service(const char *name, Client *client, const char *client_name)
			{
				Service *service;

				client->apply_for(name);

				_service_wait_queue_lock.lock();
				_service_wait_queue.insert(client);
				_service_wait_queue_lock.unlock();

				do {
					service = find(name);

					/*
					 * The service that we are seeking is not available today.
					 * Lets sleep a night over it.
					 */
					if (!service) {
						printf("%s: service %s not yet available - sleeping\n",
						       client_name, name);

						try {
							client->sleep();
							printf("%s: service %s got available\n", client_name, name);
						} catch (Blocking_canceled) {
							printf("%s: cancel waiting for service\n", client_name);
							break;
						}
					}

				} while (!service);

				/* we got what we needed, stop applying */
				_service_wait_queue_lock.lock();
				_service_wait_queue.remove(client);
				_service_wait_queue_lock.unlock();

				client->apply_for(0);

				return service;
			}

			/**
			 * Register service
			 *
			 * This function is called by the server's thread.
			 */
			void insert(Service *service)
			{
				/* make new service known */
				_services.insert(service);

				/* wake up applicants waiting for the service */
				Lock::Guard lock_guard(_service_wait_queue_lock);
				for (Client *c = _service_wait_queue.first(); c; c = c->next())
					if (strcmp(service->name(), c->apply_for()) == 0)
						c->wakeup();
			}

			/**
			 * Unregister service
			 */
			void remove(Service *service) { _services.remove(service); }
	};
}

#endif /* _INCLUDE__BASE__SERVICE_H_ */
