/*
 * Components.hpp
 *
 *  Created on: Mar 16, 2016
 *      Author: Jan Travnicek
 */

#ifndef _COMPONENTS_HPP_
#define _COMPONENTS_HPP_

#include <alib/typeinfo>
#include <exception/CommonException.h>
#include <registry/AlgorithmRegistry.hpp>

namespace component {

class Value;

} /* namespace component */

namespace core {

/**
 * Pack of containt check functions for element component
 */
template < class Derived, class ComponentType, class ComponentName >
class ElementConstraint {
public:
	/**
	 * Checks whether a concrete element is available in context of the datatype instance using this class
	 *
	 * To be implemented by all template instantiations explicitly
	 *
	 * @param element to check
	 * @return true if element is available
	 *         false if element is not available
	 */
	static bool available ( const Derived & object, const ComponentType & element );

	/**
	 * Checks whether a concrete element is valid in context of the datatype instance using this class
	 *
	 * To be implemented by all template instantiations explicitly
	 *
	 * @param element to check
	 * @throw CommonException if the element in any way invalid
	 */
	static void valid ( const Derived & object, const ComponentType & element );
};

template < class Derived, class ComponentType, class ComponentCategory, class ComponentName >
class Component;

/**
 * Represents an element.
 * @param Derived class representing datatype using this set.
 * @param ComponentType the type of element.
 * @param ComponentName arbitrary type used to distinguish different components.
 */
template < class Derived, class ComponentType, class ComponentName >
class Component < Derived, ComponentType, component::Value, ComponentName > {
	/**
	 * The element.
	 */
	ComponentType m_data;

	/**
	 * Checks whether element can be set. Calls valid and available functions.
	 * @throws CommonException if element cannot be added.
	 */
	void checkSet ( const ComponentType & element ) {
		ElementConstraint < Derived, ComponentType, ComponentName >::valid ( static_cast < const Derived & > ( * this ), element );

		if ( ! ElementConstraint < Derived, ComponentType, ComponentName >::available ( static_cast < const Derived & > ( * this ), element ) ) {
			std::string elementTypeName ( ext::to_string < ComponentName * > ( ) );
			elementTypeName.back ( ) = ' ';
			throw exception::CommonException ( elementTypeName + ext::to_string ( element ) + " is not available." );
		}
	}

protected:
	/**
	 * Checks the state of the element.
	 */
	void checkState ( ) {
		checkSet ( m_data );
	}

public:
	/**
	 * Constructs a notable element.
	 * @throw CommonException if element is not available in context of datatype where the class is used
	 */
	Component ( ComponentType element ) : m_data ( std::move ( element ) ) {
	}

	/**
	 * Changes the notable element.
	 * @param new value of the element
	 * @return bool true if the element was set
	 *              false if the element was the same as already present
	 * @throw CommonException if the new element is not available in context of datatype where the class is used
	 */
	bool set ( ComponentType element ) {
		checkSet ( element );

		if ( m_data == element ) return false;

		m_data = std::move ( element );
		return true;
	}

	/**
	 * Returns the current notable element of ComponentName.
	 * @return the notable element
	 */
	ComponentType & get ( ) {
		return m_data;
	}

	/**
	 * Returns the current notable element of ComponentName.
	 * @return the notable element
	 */
	const ComponentType & get ( ) const {
		return m_data;
	}

	/**
	 * Allows access to this sub-component using its name.
	 * @param AccessedComponentName type used to distinguish different elements
	 * @return this
	 */
	template < class AccessedComponentName, typename std::enable_if < std::is_same < AccessedComponentName, ComponentName >::value >::type * = nullptr >
	const Component < Derived, ComponentType, component::Value, ComponentName > & accessComponent ( ) const {
		return * this;
	}

	/**
	 * Allows access to this sub-component using its name.
	 * @param AccessedComponentName type used to distinguish different elements
	 * @return this
	 */
	template < class AccessedComponentName, typename std::enable_if < std::is_same < AccessedComponentName, ComponentName >::value >::type * = nullptr >
	Component < Derived, ComponentType, component::Value, ComponentName > & accessComponent ( ) {
		return * this;
	}

	/**
	 * Register this component's accessors to abstraction
	 */
	static void registerComponent ( ) {
		std::array < std::string, 0 > emptyNames;
		std::array < std::string, 1 > elementNames = { { "element" } };

		ComponentType & ( Derived::* getMethod ) ( ) = & Component < Derived, ComponentType, component::Value, ComponentName >::get;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( getMethod, "get", emptyNames );

		const ComponentType & ( Derived::* constGetMethod ) ( ) const = & Component < Derived, ComponentType, component::Value, ComponentName >::get;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( constGetMethod, "get", emptyNames );

		bool ( Derived::* setMethod ) ( ComponentType ) = & Component < Derived, ComponentType, component::Value, ComponentName >::set;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( setMethod, "set", elementNames );
	}

	/**
	 * Register this component's accessors to abstraction
	 */
	static void unregisterComponent ( ) {
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, Derived & > ( "get" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, const Derived & > ( "get" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, Derived &, ComponentType > ( "set" );
	}

};

/**
 * Auxiliary class allowing simple access to the alphabets.
 */
template < class ... Data >
class Components {
public:
	/**
	 * To silent the compiler about nonexistent access method in Components base
	 */
	template < class T, typename std::enable_if < ! std::is_same < T, T >::value >::type * = nullptr >
	void accessComponent ( );

	/**
	 * To silent the compiler about nonexistent access method in Components base
	 */
	static void registerComponent ( ) {
	}

	/**
	 * To silent the compiler about nonexistent access method in Components base
	 */
	static void unregisterComponent ( ) {
	}
};

/**
 * Auxiliary class allowing simple access to the alphabets.
 */
template < class Derived, class ComponentType, class ComponentCategory, class ComponentName, class ... Next >
class Components < Derived, ComponentType, ComponentCategory, ComponentName, Next ... > : public Component < Derived, ComponentType, ComponentCategory, ComponentName >, public Components < Derived, Next ... > {
public:
	/**
	 * Allow less initialization values then number of components. They need to allow to be defaulty constructed then.
	 */
	Components ( ) = default;

	/**
	 * Construct an alphabet pack from two alphabets.
	 */
	template < class ComponentValue, class ... NextValues >
	Components ( ComponentValue param, NextValues ... nextParams ) : Component < Derived, ComponentType, ComponentCategory, ComponentName > ( std::move ( param ) ), Components < Derived, Next ... > ( std::move ( nextParams ) ... ) {
		Component < Derived, ComponentType, ComponentCategory, ComponentName >::checkState ( );
	}

	/**
	 * Make access method from Component class part of methods set of Components class
	 */
	using Component < Derived, ComponentType, ComponentCategory, ComponentName >::accessComponent;

	/**
	 * Make access method from Components base class part of methods set of this Components class
	 */
	using Components < Derived, Next ... >::accessComponent;

	/**
	 * Dispatcher for registration functions in subclasses.
	 */
	static void registerComponent ( ) {
		Component < Derived, ComponentType, ComponentCategory, ComponentName >::registerComponent ( );
		Components < Derived, Next ... >::registerComponent ( );
	}

	/**
	 * Dispatcher for registration functions in subclasses.
	 */
	static void unregisterComponent ( ) {
		Component < Derived, ComponentType, ComponentCategory, ComponentName >::unregisterComponent ( );
		Components < Derived, Next ... >::unregisterComponent ( );
	}
};

/**
 * Auxiliary class allowing simple access to the alphabets.
 */
template < class Derived, class ComponentType, class ComponentCategory, class ComponentName, class ... ComponentNames, class ... Next >
class Components < Derived, ComponentType, ComponentCategory, std::tuple < ComponentName, ComponentNames ... >, Next ... > : public Component < Derived, ComponentType, ComponentCategory, ComponentName >, public Components < Derived, ComponentType, ComponentCategory, std::tuple < ComponentNames ... >, Next ... > {
public:
	/**
	 * Allow less initialization values then number of components. They need to allow to be defaulty constructed then.
	 */
	Components ( ) = default;

	/**
	 * Construct an alphabet pack from two alphabets.
	 */
	template < class ComponentValue, class ... NextValues >
	Components ( ComponentValue param, NextValues ... nextParams ) : Component < Derived, ComponentType, ComponentCategory, ComponentName > ( std::move ( param ) ), Components < Derived, ComponentType, ComponentCategory, std::tuple < ComponentNames ... >, Next ... > ( std::move ( nextParams ) ... ) {
		Component < Derived, ComponentType, ComponentCategory, ComponentName >::checkState ( );
	}

	/**
	 * Make access method from Component class part of methods set of Components class
	 */
	using Component < Derived, ComponentType, ComponentCategory, ComponentName >::accessComponent;

	/**
	 * Make access method from Components base class part of methods set of this Components class
	 */
	using Components < Derived, ComponentType, ComponentCategory, std::tuple < ComponentNames ... >, Next ... >::accessComponent;

	/**
	 * Dispatcher for registration functions in subclasses.
	 */
	static void registerComponent ( ) {
		Component < Derived, ComponentType, ComponentCategory, ComponentName >::registerComponent ( );
		Components < Derived, ComponentType, ComponentCategory, std::tuple < ComponentNames ... >, Next ... >::registerComponent ( );
	}

	/**
	 * Dispatcher for registration functions in subclasses.
	 */
	static void unregisterComponent ( ) {
		Component < Derived, ComponentType, ComponentCategory, ComponentName >::unregisterComponent ( );
		Components < Derived, ComponentType, ComponentCategory, std::tuple < ComponentNames ... >, Next ... >::unregisterComponent ( );
	}
};

/**
 * Auxiliary class allowing simple access to the alphabets.
 */
template < class Derived, class ComponentType, class ComponentCategory, class ... Next >
class Components < Derived, ComponentType, ComponentCategory, std::tuple < >, Next ... > : public Components < Derived, Next ... > {
public:
	/**
	 * Allow less initialization values then number of components. They need to allow to be defaulty constructed then.
	 */
	Components ( ) = default;

	/**
	 * Construct an alphabet pack from two alphabets.
	 */
	template < class ... NextValues >
	Components ( NextValues ... nextParams ) : Components < Derived, Next ... > ( std::move ( nextParams ) ... ) {
	}

	/**
	 * Make access method from Components base class part of methods set of this Components class
	 */
	using Components < Derived, Next ... >::accessComponent;

	/**
	 * Dispatcher for registration functions in subclasses.
	 */
	static void registerComponent ( ) {
		Components < Derived, Next ... >::registerComponent ( );
	}

	/**
	 * Dispatcher for registration functions in subclasses.
	 */
	static void unregisterComponent ( ) {
		Components < Derived, Next ... >::unregisterComponent ( );
	}
};

} /* namespace core */

#include <core/components/setComponents.hpp>

#endif /* _COMPONENTS_HPP_ */
