/*
 * \brief  Utilities for handling server-side session policies
 * \author Norman Feske
 * \date   2011-09-13
 */

/*
 * Copyright (C) 2011-2016 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__OS__SESSION_POLICY_H_
#define _INCLUDE__OS__SESSION_POLICY_H_

#include <util/arg_string.h>
#include <os/config.h>
#include <base/session_label.h>

namespace Genode {

	struct Xml_node_label_score;
	class  Session_policy;
}


/**
 * Score for matching an Xml_node against a label
 *
 * The score is based on the attributes 'label', 'label_prefix', and
 * 'label_suffix'.
 */
struct Genode::Xml_node_label_score
{
	bool  label_present = true;
	bool prefix_present = true;
	bool suffix_present = true;

	bool label_match = false;

	/*
	 * The match values contain the number of matching characters + 1.
	 * If 0, there is a conflict. If 1, an empty string matched.
	 */
	enum { CONFLICT = 0 };
	unsigned prefix_match = CONFLICT;
	unsigned suffix_match = CONFLICT;

	Xml_node_label_score() { }

	template <size_t N>
	Xml_node_label_score(Xml_node node, String<N> const &label)
	:
		label_present (node.has_attribute("label")),
		prefix_present(node.has_attribute("label_prefix")),
		suffix_present(node.has_attribute("label_suffix"))
	{
		if (label_present)
			label_match = node.attribute_value("label", String<N>()) == label;

		if (prefix_present) {
			typedef String<N> Prefix;
			Prefix const prefix = node.attribute_value("label_prefix", Prefix());

			if (!strcmp(label.string(), prefix.string(), prefix.length() - 1))
				prefix_match = prefix.length()-1;
		}

		if (suffix_present) {
			typedef String<N> Suffix;
			Suffix const suffix = node.attribute_value("label_suffix", Suffix());

			if (label.length() >= suffix.length()) {
				unsigned const offset = label.length() - suffix.length();

				if (!strcmp(label.string() + offset, suffix.string()))
					suffix_match = suffix.length()-1;
			}
		}
	}

	bool conflict() const
	{
		return (label_present  && !label_match)
		    || (prefix_present && !prefix_match)
		    || (suffix_present && !suffix_match);
	}

	/**
	 * Return true if this node's score is higher than 'other'
	 */
	bool stronger(Xml_node_label_score const &other) const
	{
		/* something must match */
		if (!(label_present || prefix_present || suffix_present))
			return false;

		/* if we are in conflict, we have a lower score than any other node */
		if (conflict())
			return false;

		/* there are no conflicts */

		/* we have a higher score than another conflicting node */
		if (other.conflict())
			return true;

		if (label_present && !other.label_present)
			return true;

		if (other.label_present)
			return false;

		/* labels are equally good */

		if (prefix_present && !other.prefix_present)
			return true;

		if (!prefix_present && other.prefix_present)
			return false;

		if (prefix_present && other.prefix_present) {

			if (prefix_match > other.prefix_match)
				return true;

			if (prefix_match < other.prefix_match)
				return false;
		}

		/* prefixes are equally good */

		if (suffix_present && !other.suffix_present)
			return true;

		if (!suffix_present && other.suffix_present)
			return false;

		if (suffix_present && other.suffix_present) {

			if (suffix_match > other.suffix_match)
				return true;

			if (suffix_match < other.suffix_match)
				return false;
		}

		/* nodes are equally good */

		return false;
	}
};


/**
 * Query server-side policy for a session request
 */
class Genode::Session_policy : public Xml_node
{
	public:

		/**
		 * Exception type
		 */
		class No_policy_defined { };

	private:

		/**
		 * Query session policy from session label
		 */
		template <size_t N>
		static Xml_node _query_policy(String<N> const &label, Xml_node config)
		{
			/*
			 * Find policy node that matches best
			 */
			Xml_node             best_match("<none/>");
			Xml_node_label_score best_score;

			/*
			 * Functor to be applied to each policy node
			 */
			auto lambda = [&] (Xml_node policy) {
				Xml_node_label_score const score(policy, label);
				if (score.stronger(best_score)) {
					best_match = policy;
					best_score = score;
				}
			};

			config.for_each_sub_node("policy", lambda);

			if (!best_match.has_type("none"))
				return best_match;

			try { return config.sub_node("default-policy"); }
			catch (...) { }

			throw No_policy_defined();
		}

	public:

		/**
		 * Constructor
		 *
		 * \param label   label used as the selector of a policy
		 * \param config  XML node that contains the policies as sub nodes,
		 *                using the component's top-level config node by
		 *                default
		 *
		 * \throw No_policy_defined  the server configuration has no
		 *                           policy defined for the specified label
		 *
		 * On construction, the 'Session_policy' looks up the 'policy' XML node
		 * that matches the label provided as argument. The server-side
		 * policies are defined in one or more policy subnodes of the server's
		 * 'config' node. Each policy node has a label attribute. If the policy
		 * label matches the first part of the label as delivered as session
		 * argument, the policy matches. If multiple policies match, the one
		 * with the longest label is selected.
		 */
		template <size_t N>
		explicit Session_policy(String<N> const &label,
		                        Xml_node config = Genode::config()->xml_node())
		:
			Xml_node(_query_policy(label, config))
		{ }
};

#endif /* _INCLUDE__OS__SESSION_POLICY_H_ */
