/*
 * \brief  Representation of CPU affinities
 * \author Norman Feske
 * \date   2013-08-07
 */

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

#include <base/node.h>

namespace Genode { class Affinity; }


/**
 * Affinity to CPU nodes
 *
 * The entity of CPU nodes is expected to form a grid where the Euclidean
 * distance between nodes roughly correlate to the locality of their
 * respective resources. Closely interacting processes are supposed to
 * perform best when using nodes close to each other. To allow a relatively
 * simple specification of such constraints, the affinity of a subsystem
 * (e.g., a process) to CPU nodes is expressed as a rectangle within the
 * grid of available CPU nodes. The dimensions of the grid are represented
 * by 'Affinity::Space'. The rectangle within the grid is represented by
 * 'Affinity::Location'.
 */
class Genode::Affinity
{
	public:

		class Location;

		/**
		 * Bounds of the affinity name space
		 *
		 * An 'Affinity::Space' defines the bounds of a Cartesian
		 * coordinate space that expresses the entity of available CPU
		 * nodes. The dimension values do not necessarily correspond to
		 * physical CPU numbers. They solely represent the range the
		 * 'Affinity::Location' is relative to.
		 */
		class Space
		{
			private:

				unsigned _width, _height;

			public:

				Space() : _width(0), _height(0) { }

				/**
				 * Construct a two-dimensional affinity space
				 */
				Space(unsigned width, unsigned height)
				: _width(width), _height(height) { }

				/**
				 * Constuct one-dimensional affinity space
				 */
				Space(unsigned size) : _width(size), _height(1) { }

				unsigned width()  const { return _width; }
				unsigned height() const { return _height; }
				unsigned total()  const { return _width*_height; }

				Space multiply(Space const &other) const
				{
					return Space(_width*other.width(), _height*other.height());
				}

				/**
				 * Return the location of the Nth CPU within the affinity
				 * space
				 *
				 * This method returns a valid location even if the index
				 * is larger than the number of CPUs in the space. In this
				 * case, the x and y coordinates are wrapped by the bounds
				 * of the space.
				 */
				inline Location location_of_index(int index) const;

				static Space from_node(auto const &node)
				{
					return Affinity::Space(node.attribute_value("width",  0U),
					                       node.attribute_value("height", 0U));
				}

				/**
				 * API-compatibility wrapper for 'from_node'
				 *
				 * \noapi
				 * \deprecated
				 */
				static Space from_xml(Xml_node const &node) { return from_node(node); }
		};


		/**
		 * Location within 'Space'
		 */
		class Location
		{
			private:

				int      _xpos  = 0,  _ypos  = 0;
				unsigned _width = 0, _height = 0;

			public:

				/**
				 * Default constructor creates invalid location
				 */
				Location() { }

				/**
				 * Constructor to express the affinity to a single CPU
				 */
				Location(int xpos, int ypos)
				: _xpos(xpos), _ypos(ypos), _width(1), _height(1) { }

				/**
				 * Constructor to express the affinity to a set of CPUs
				 */
				Location(int xpos, int ypos, unsigned width, unsigned height)
				: _xpos(xpos), _ypos(ypos), _width(width), _height(height) { }

				int      xpos()   const { return _xpos; }
				int      ypos()   const { return _ypos; }
				unsigned width()  const { return _width; }
				unsigned height() const { return _height; }

				Location multiply_position(Space const &space) const
				{
					return Location(_xpos*space.width(), _ypos*space.height(),
					                _width, _height);
				}

				Location transpose(int dx, int dy) const
				{
					return Location(_xpos + dx, _ypos + dy, _width, _height);
				}

				/**
				 * Return true if the location resides within 'space'
				 */
				bool within(Space const &space) const
				{
					int const x1 = _xpos,  x2 = _xpos + _width  - 1,
					          y1 = _ypos,  y2 = _ypos + _height - 1;

					return x1 >= 0 && x1 <= x2 && (unsigned)x2 < space.width()
					    && y1 >= 0 && y1 <= y2 && (unsigned)y2 < space.height();
				}

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

					return Location(node.attribute_value("xpos",   0U),
					                node.attribute_value("ypos",   0U),
					                node.attribute_value("width",  default_width),
					                node.attribute_value("height", default_height));
				}

				/**
				 * API-compatibility wrapper for 'from_node'
				 *
				 * \noapi
				 * \deprecated
				 */
				static Location from_xml(auto &&... args) { return from_node(args...); }
		};

	private:

		Space    _space    { };
		Location _location { };

	public:

		Affinity(Space const &space, Location const &location)
		: _space(space), _location(location) { }

		Affinity() { }

		Space    space()    const { return _space; }
		Location location() const { return _location; }
		bool     valid()    const { return _location.within(_space); }

		static Affinity from_node(auto const &node)
		{
			Affinity::Space    space    { };
			Affinity::Location location { };

			node.with_optional_sub_node("affinity", [&] (auto const &node) {
				node.with_optional_sub_node("space", [&] (auto const &node) {
					space = Space::from_node(node); });
				node.with_optional_sub_node("location", [&] (auto const &node) {
					location = Location::from_node(space, node); });
			});

			return Affinity(space, location);
		}

		/**
		 * API-compatibility wrapper for 'from_node'
		 *
		 * \noapi
		 * \deprecated
		 */
		static Affinity from_xml(Xml_node const &node) { return from_node(node); }

		static Affinity unrestricted()
		{
			return Affinity(Space(1, 1), Location(0, 0, 1, 1));
		}

		/**
		 * Return location scaled to specified affinity space
		 */
		Location scale_to(Space const &space) const
		{
			if (_space.total() == 0)
				return Location();

			/*
			 * Calculate coordinates of rectangle corners
			 *
			 * P1 is the upper left corner, inside the rectangle.
			 * P2 is the lower right corner, outside the rectangle.
			 */
			int const x1 = _location.xpos(),
			          y1 = _location.ypos(),
			          x2 = _location.width()  + x1,
			          y2 = _location.height() + y1;

			/* scale corner positions */
			int const scaled_x1 = (x1*space.width())  / _space.width(),
			          scaled_y1 = (y1*space.height()) / _space.height(),
			          scaled_x2 = (x2*space.width())  / _space.width(),
			          scaled_y2 = (y2*space.height()) / _space.height();

			/* make sure to not scale the location size to zero */
			return Location(scaled_x1, scaled_y1,
			                max(scaled_x2 - scaled_x1, 1),
			                max(scaled_y2 - scaled_y1, 1));
		}
};


Genode::Affinity::Location Genode::Affinity::Space::location_of_index(int index) const
{
	return Location(index % _width, (index / _width) % _height, 1, 1);
}

#endif /* _INCLUDE__BASE__AFFINITY_H_ */
