/*
 * \brief  Utilities
 * \author Norman Feske
 * \date   2010-05-04
 */

/*
 * Copyright (C) 2010-2017 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 _SRC__INIT__UTIL_H_
#define _SRC__INIT__UTIL_H_

namespace Init {

	static inline void warn_insuff_quota(size_t const avail)
	{
		warning("specified quota exceeds available quota, "
		        "proceeding with a quota of ", avail);
	}


	/**
	 * Return sub string of label with the leading child name stripped out
	 *
	 * \return character pointer to the scoped part of the label,
	 *         or nullptr if the label is not correctly prefixed with the child's
	 *         name
	 */
	inline char const *skip_label_prefix(char const *child_name, char const *label)
	{
		size_t const child_name_len = strlen(child_name);

		if (strcmp(child_name, label, child_name_len) != 0)
			return nullptr;

		label += child_name_len;

		/*
		 * Skip label separator. This condition should be always satisfied.
		 */
		if (strcmp(" -> ", label, 4) != 0)
			return nullptr;

		return label + 4;
	}


	/**
	 * Return true if service XML node matches service request
	 *
	 * \param args          session arguments, inspected for the session label
	 * \param child_name    name of the originator of the session request
	 * \param service_name  name of the requested service
	 */
	inline bool service_node_matches(Xml_node           const  service_node,
	                                 Session_label      const &label,
	                                 Child_policy::Name const &child_name,
	                                 Service::Name      const &service_name)
	{
		bool const service_matches =
			service_node.has_type("any-service") ||
			(service_node.has_type("service") &&
			 service_node.attribute_value("name", Service::Name()) == service_name);

		if (!service_matches)
			return false;

		typedef String<Session_label::capacity()> Label;

		char const *unscoped_attr   = "unscoped_label";
		char const *label_last_attr = "label_last";

		bool const route_depends_on_child_provided_label =
			service_node.has_attribute("label") ||
			service_node.has_attribute("label_prefix") ||
			service_node.has_attribute("label_suffix") ||
			service_node.has_attribute(label_last_attr);

		if (service_node.has_attribute(unscoped_attr)) {

			/*
			 * If an 'unscoped_label' attribute is provided, don't consider any
			 * scoped label attribute.
			 */
			if (route_depends_on_child_provided_label)
				warning("service node contains both scoped and unscoped label attributes");

			return label == service_node.attribute_value(unscoped_attr, Label());
		}

		if (service_node.has_attribute(label_last_attr))
			return service_node.attribute_value(label_last_attr, Label()) == label.last_element();

		if (!route_depends_on_child_provided_label)
			return true;

		char const * const scoped_label = skip_label_prefix(
			child_name.string(), label.string());

		if (!scoped_label)
			return false;

		Session_label const session_label(scoped_label);

		return !Xml_node_label_score(service_node, session_label).conflict();
	}


	/**
	 * Check if service name is ambiguous
	 *
	 * \return true  if the same service is provided multiple
	 *               times
	 *
	 * \deprecated
	 */
	template <typename T>
	inline bool is_ambiguous(Registry<T> const &services, Service::Name const &name)
	{
		/* count number of services with the specified name */
		unsigned cnt = 0;
		services.for_each([&] (T const &service) {
			cnt += (service.name() == name); });

		return cnt > 1;
	}

	/**
	 * Find service with certain values in given registry
	 *
	 * \param services   service registry
	 * \param name       name of wanted service
	 * \param filter_fn  function that applies additional filters
	 *
	 * \throw Service_denied
	 */
	template <typename T, typename FILTER_FN>
	inline T &find_service(Registry<T> &services,
	                       Service::Name const &name,
	                       FILTER_FN const &filter_fn)
	{
		T *service = nullptr;
		services.for_each([&] (T &s) {

			if (service || s.name() != name || filter_fn(s))
				return;

			service = &s;
		});

		if (!service)
			throw Service_denied();

		if (service->abandoned())
			throw Service_denied();

		return *service;
	}


	inline void generate_ram_info(Xml_generator &xml, Pd_session const &pd)
	{
		typedef String<32> Value;
		xml.attribute("quota", Value(pd.ram_quota()));
		xml.attribute("used",  Value(pd.used_ram()));
		xml.attribute("avail", Value(pd.avail_ram()));
	}

	inline void generate_caps_info(Xml_generator &xml, Pd_session const &pd)
	{
		typedef String<32> Value;
		xml.attribute("quota", Value(pd.cap_quota()));
		xml.attribute("used",  Value(pd.used_caps()));
		xml.attribute("avail", Value(pd.avail_caps()));
	}

	/**
	 * Read priority-levels declaration from config
	 */
	inline Prio_levels prio_levels_from_xml(Xml_node config)
	{
		long const prio_levels = config.attribute_value("prio_levels", 0UL);

		if (prio_levels && ((prio_levels >= (long)sizeof(prio_levels)*8) ||
		                    (prio_levels != (1L << log2(prio_levels))))) {
			warning("prio levels is not power of two, priorities are disabled");
			return Prio_levels { 0 };
		}
		return Prio_levels { prio_levels };
	}


	inline long priority_from_xml(Xml_node start_node, Prio_levels prio_levels)
	{
		long const default_priority = Cpu_session::DEFAULT_PRIORITY;

		long priority = start_node.attribute_value("priority", default_priority);

		/*
		 * All priority declarations in the config file are
		 * negative because child priorities can never be higher
		 * than parent priorities. To simplify priority
		 * calculations, we use inverted values. Lower values
		 * correspond to higher priorities.
		 */
		priority = -priority;

		if (priority && (priority >= prio_levels.value)) {
			long new_prio = prio_levels.value ? prio_levels.value - 1 : 0;

			Service::Name const name = start_node.attribute_value("name", Service::Name());
			warning(name, ": invalid priority, upgrading "
			        "from ", -priority, " to ", -new_prio);
			return new_prio;
		}

		return priority;
	}


	inline Affinity::Location
	affinity_location_from_xml(Affinity::Space const &space, Xml_node start_node)
	{
		typedef Affinity::Location Location;
		try {
			Xml_node node = start_node.sub_node("affinity");

			/* if no position value is specified, select the whole row/column */
			unsigned long const
				default_width  = node.has_attribute("xpos") ? 1 : space.width(),
				default_height = node.has_attribute("ypos") ? 1 : space.height();

			unsigned long const
				width  = node.attribute_value<unsigned long>("width",  default_width),
				height = node.attribute_value<unsigned long>("height", default_height);

			long const x1 = node.attribute_value<long>("xpos", 0),
			           y1 = node.attribute_value<long>("ypos", 0),
			           x2 = x1 + width  - 1,
			           y2 = y1 + height - 1;

			/* clip location to space boundary */
			return Location(max(x1, 0L), max(y1, 0L),
			                min((unsigned)(x2 - x1 + 1), space.width()),
			                min((unsigned)(y2 - y1 + 1), space.height()));
		}
		catch (...) { return Location(0, 0, space.width(), space.height()); }
	}


	/**
	 * Read affinity-space parameters from config
	 *
	 * If no affinity space is declared, construct a space with a single element,
	 * width and height being 1. If only one of both dimensions is specified, the
	 * other dimension is set to 1.
	 */
	inline Affinity::Space affinity_space_from_xml(Xml_node config)
	{
		try {
			Xml_node node = config.sub_node("affinity-space");
			return Affinity::Space(node.attribute_value<unsigned long>("width",  1),
			                       node.attribute_value<unsigned long>("height", 1));
		} catch (...) {
			return Affinity::Space(1, 1); }
	}
}

#endif /* _SRC__INIT__UTIL_H_ */
