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

#pragma once

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

namespace component {

class Set;

} /* namespace component */

namespace core {

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

	/**
	 * Checks whether a concrete element is available in context of the datatype where the set is used
	 *
	 * 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 where the set is used
	 *
	 * 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 );
};

/**
 * Represents a set of elements.
 * @param Derived class representing datatype using this set.
 * @param ComponentType underlying type of data in the set.
 * @param ComponentName arbitrary type used to distinguish different components.
 */
template < class Derived, class SetComponentType, class ComponentType, class ComponentName >
class SetComponent {
	/**
	 * The set.
	 */
	SetComponentType m_data;

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

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

	/**
	 * Checks whether element can be removed from the set. Calls used function.
	 * @throws CommonException if element cannot be removed.
	 */
	void checkRemove ( const ComponentType & element ) {
		if ( SetConstraint < Derived, ComponentType, ComponentName >::used ( static_cast < const Derived & > ( * this ), element ) ) {
			std::string elementTypeName ( ext::to_string < ComponentName > ( ) );
			throw exception::CommonException ( elementTypeName + "element " + ext::to_string ( element ) + " is used." );
		}
	}

protected:
	/**
	 * Checks the state of the set.
	 */
	void checkState ( ) {
		for ( const ComponentType & element : m_data )
			checkAdd ( element );
	}

public:
	/**
	 * Constructs a set containing given elements.
	 * @throw CommonException if elements are not available in context of datatype where the set is used
	 */
	SetComponent ( SetComponentType data ) : m_data ( std::move ( data ) ) {
	}

	/**
	 * Constructs a empty set.
	 */
	SetComponent ( ) = default;

	/**
	 * Adds an elements to the set.
	 * @param element to add to the set
	 * @throw CommonException if element is not available in context of datatype where the set is used
	 * @return true if element was indeed added
	 *         false if element was present in the set
	 */
	bool add ( ComponentType element ) {
		checkAdd ( element );
		return m_data.insert ( std::move ( element ) ).second;
	}

	/**
	 * Adds a set of elements to the set.
	 * @param elements to add to the set
	 * @throw CommonException if one of the elements is not available in context of datatype where the set is used
	 */
	Derived & add ( SetComponentType data ) {
		for ( ComponentType element : ext::make_mover ( data ) )
			add ( std::move ( element ) );
		return static_cast < Derived & > ( * this );
	}

	/**
	 * Changes the set.
	 * @param elements by which to replace those currently in the set
	 * @throw CommonException if one of the removed elements is used in context of datatype where the set is used
	 *        CommonException if one of the added elements is not available in context of datatype where the set is used
	 */
	Derived & set ( SetComponentType data ) {
		auto removeCheckLambda = [ this ] ( const auto & value ) {
			this->checkRemove ( value );
		};

		auto addCheckLambda = [ this ] ( const auto & value ) {
			this->checkAdd ( value );
		};

		ext::set_symmetric_difference ( m_data.begin ( ), m_data.end ( ), data.begin ( ), data.end ( ), ext::make_callback_iterator ( removeCheckLambda ), ext::make_callback_iterator ( addCheckLambda ) );

		m_data = std::move ( data );
		return static_cast < Derived & > ( * this );
	}

	/**
	 * @return the set.
	 */
	SetComponentType & get ( ) {
		return m_data;
	}

	/**
	 * @return the set.
	 */
	const SetComponentType & get ( ) const {
		return m_data;
	}

	/**
	 * Removes an element from the set if not used.
	 * @throw CommonException if element is used in context of datatype where the set is used
	 * @return true if element was indeed removed
	 *         false if element was not present in the set
	 */
	bool remove ( const ComponentType & element ) {
		checkRemove ( element );
		return m_data.erase ( element );
	}

	/**
	 * Removes a set of elements from alphabet if not used.
	 * @throw CommonException if element is used in context of datatype where the set is used
	 */
	Derived & remove ( const SetComponentType & data ) {
		for ( const ComponentType & element : data )
			remove ( element );
		return static_cast < Derived & > ( * this );
	}

	/**
	 * Component emptiness checker.
	 * @return true if set is an empty
	 */
	bool empty ( ) const {
		return m_data.empty ( );
	}

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

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

};

template < class Derived, class ComponentType, class ComponentName >
class Component < Derived, ComponentType, component::Set, ComponentName > : public SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName > {
public:
	/**
	 * Allow value construction of this component.
	 */
	Component ( ComponentType data ) : SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName > ( std::move ( data ) ) {
	}

	/**
	 * Allow default construction of this component.
	 */
	Component ( ) = default;

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

		ComponentType & ( Derived::* getMethod ) ( ) = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::get;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( getMethod, "get", emptyNames );

		const ComponentType & ( Derived::* constGetMethod ) ( ) const = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::get;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( constGetMethod, "get", emptyNames );

		Derived & ( Derived::* setMethod ) ( ComponentType ) = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::set;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( setMethod, "set", dataNames );

		bool ( Derived::* addElement ) ( typename ComponentType::value_type ) = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::add;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( addElement, "add", elementNames );

		Derived & ( Derived::* addSet ) ( ComponentType ) = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::add;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( addSet, "add", dataNames );

		bool ( Derived::* removeElement ) ( const typename ComponentType::value_type & ) = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::remove;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( removeElement, "remove", elementNames );

		Derived & ( Derived::* removeSet ) ( const ComponentType & ) = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::remove;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( removeSet, "remove", dataNames );

		bool ( Derived::* emptyMethod ) ( ) const = & SetComponent < Derived, ComponentType, typename ComponentType::value_type, ComponentName >::empty;
		abstraction::AlgorithmRegistry::registerMethod < ComponentName > ( emptyMethod, "empty", emptyNames );
	}

	/**
	 * 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" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, Derived, typename ComponentType::value_type > ( "add" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, Derived, ComponentType > ( "add" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, Derived, const typename ComponentType::value_type & > ( "remove" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, Derived, const ComponentType & > ( "remove" );
		abstraction::AlgorithmRegistry::unregisterMethod < ComponentName, const Derived & > ( "empty" );
	}
};

} /* namespace core */

