/*
 * OperatorRegistry.hpp
 *
 *  Created on: 11. 7. 2017
 *	  Author: Jan Travnicek
 */

#pragma once

#include <alib/functional>
#include <alib/memory>
#include <alib/vector>
#include <alib/list>
#include <alib/string>
#include <alib/set>
#include <alib/map>
#include <alib/typeinfo>

#include <abstraction/OperationAbstraction.hpp>

#include <common/TypeQualifiers.hpp>
#include <common/AlgorithmCategories.hpp>
#include <common/Operators.hpp>

#include "AlgorithmRegistryInfo.hpp"
#include "BaseRegistryEntry.hpp"

namespace abstraction {

class OperatorRegistry {
public:
	class BinaryEntry : public BaseRegistryEntry {
		AlgorithmFullInfo m_entryInfo;

	public:
		explicit BinaryEntry ( AlgorithmFullInfo entryInfo ) : m_entryInfo ( std::move ( entryInfo ) ) {
		}

		const AlgorithmFullInfo & getEntryInfo ( ) const {
			return m_entryInfo;
		}
	};

	class PrefixEntry : public BaseRegistryEntry {
		AlgorithmFullInfo m_entryInfo;

	public:
		explicit PrefixEntry ( AlgorithmFullInfo entryInfo ) : m_entryInfo ( std::move ( entryInfo ) ) {
		}

		const AlgorithmFullInfo & getEntryInfo ( ) const {
			return m_entryInfo;
		}
	};

	class PostfixEntry : public BaseRegistryEntry {
		AlgorithmFullInfo m_entryInfo;

	public:
		explicit PostfixEntry ( AlgorithmFullInfo entryInfo ) : m_entryInfo ( std::move ( entryInfo ) ) {
		}

		const AlgorithmFullInfo & getEntryInfo ( ) const {
			return m_entryInfo;
		}
	};

private:
	template < class Return, class FirstParam, class SecondParam >
	class BinaryOperator : public BinaryEntry {
		std::function < Return ( FirstParam, SecondParam ) > m_callback;

	public:
		explicit BinaryOperator ( std::function < Return ( FirstParam, SecondParam ) > callback ) : BinaryEntry ( AlgorithmFullInfo::operatorEntryInfo < Return, FirstParam, SecondParam > ( ) ), m_callback ( std::move ( callback ) ) {
		}

		std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const override;
	};

	template < class Return, class Param >
	class PrefixOperator : public PrefixEntry {
		std::function < Return ( Param ) > m_callback;

	public:
		explicit PrefixOperator ( std::function < Return ( Param ) > callback ) : PrefixEntry ( AlgorithmFullInfo::operatorEntryInfo < Return, Param > ( ) ), m_callback ( std::move ( callback ) ) {
		}

		std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const override;
	};

	template < class Return, class Param >
	class PostfixOperator : public PostfixEntry {
		std::function < Return ( Param ) > m_callback;

	public:
		explicit PostfixOperator ( std::function < Return ( Param ) > callback ) : PostfixEntry ( AlgorithmFullInfo::operatorEntryInfo < Return, Param > ( ) ), m_callback ( std::move ( callback ) ) {
		}

		std::shared_ptr < abstraction::OperationAbstraction > getAbstraction ( ) const override;
	};

	static ext::map < Operators::BinaryOperators, ext::list < std::unique_ptr < BinaryEntry > > > & getBinaryEntries ( );

	static bool isRegisteredBinary ( Operators::BinaryOperators type, const AlgorithmBaseInfo & entryInfo );

	static void registerBinaryInternal ( Operators::BinaryOperators type, std::unique_ptr < BinaryEntry > value );

	static void unregisterBinaryInternal ( Operators::BinaryOperators type, const AlgorithmBaseInfo & entryInfo );



	static ext::map < Operators::PrefixOperators, ext::list < std::unique_ptr < PrefixEntry > > > & getPrefixEntries ( );

	static bool isRegisteredPrefix ( Operators::PrefixOperators type, const AlgorithmBaseInfo & entryInfo );

	static void registerPrefixInternal ( Operators::PrefixOperators type, std::unique_ptr < PrefixEntry > value );

	static void unregisterPrefixInternal ( Operators::PrefixOperators type, const AlgorithmBaseInfo & entryInfo );



	static ext::map < Operators::PostfixOperators, ext::list < std::unique_ptr < PostfixEntry > > > & getPostfixEntries ( );

	static bool isRegisteredPostfix ( Operators::PostfixOperators type, const AlgorithmBaseInfo & entryInfo );

	static void registerPostfixInternal ( Operators::PostfixOperators type, std::unique_ptr < PostfixEntry > value );

	static void unregisterPostfixInternal ( Operators::PostfixOperators type, const AlgorithmBaseInfo & entryInfo );

public:
	template < class FirstParam, class SecondParam >
	static void unregisterBinary ( Operators::BinaryOperators type ) {
		unregisterBinaryInternal ( type, AlgorithmBaseInfo::operatorEntryInfo < FirstParam, SecondParam > ( ) );
	}

	template < class Return, class FirstParam, class SecondParam >
	static void registerBinary ( Operators::BinaryOperators type, std::function < Return ( FirstParam, SecondParam ) > func ) {
		registerBinaryInternal ( type, std::make_unique < BinaryOperator < Return, FirstParam, SecondParam > > ( func ) );
	}

	template < Operators::BinaryOperators Type, class FirstParam, class SecondParam >
	static void registerBinary ( ) {
		if constexpr ( Type == Operators::BinaryOperators::LOGICAL_AND ) {
			std::function < decltype ( std::declval < FirstParam > ( ) && std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a && b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::LOGICAL_OR ) {
			std::function < decltype ( std::declval < FirstParam > ( ) || std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a || b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::BINARY_AND ) {
			std::function < decltype ( std::declval < FirstParam > ( ) & std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a & b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::BINARY_OR ) {
			std::function < decltype ( std::declval < FirstParam > ( ) | std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a | b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::BINARY_XOR ) {
			std::function < decltype ( std::declval < FirstParam > ( ) ^ std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a ^ b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::ADD ) {
			std::function < decltype ( std::declval < FirstParam > ( ) + std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a + b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::SUB ) {
			std::function < decltype ( std::declval < FirstParam > ( ) - std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a - b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::MUL ) {
			std::function < decltype ( std::declval < FirstParam > ( ) * std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a * b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::MOD ) {
			std::function < decltype ( std::declval < FirstParam > ( ) % std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a % b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::DIV ) {
			std::function < decltype ( std::declval < FirstParam > ( ) / std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a / b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::EQUALS ) {
			std::function < decltype ( std::declval < FirstParam > ( ) == std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a == b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::NOT_EQUALS ) {
			std::function < decltype ( std::declval < FirstParam > ( ) != std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a != b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::LESS ) {
			std::function < decltype ( std::declval < FirstParam > ( ) < std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a < b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::LESS_OR_EQUAL ) {
			std::function < decltype ( std::declval < FirstParam > ( ) <= std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a <= b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::MORE ) {
			std::function < decltype ( std::declval < FirstParam > ( ) > std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a > b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::MORE_OR_EQUAL ) {
			std::function < decltype ( std::declval < FirstParam > ( ) >= std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) { return a >= b; };
			registerBinary ( Type, callback );
		} else if constexpr ( Type == Operators::BinaryOperators::ASSIGN ) {
			std::function < decltype ( std::declval < FirstParam > ( ) = std::declval < SecondParam > ( ) ) ( FirstParam, SecondParam ) > callback = [ ] ( FirstParam a, SecondParam b ) -> decltype ( auto ) { return a = b; };
			registerBinary ( Type, callback );
		} else {
			static_assert ( "Unhandled binary operator." );
		}
	}

	static std::shared_ptr < abstraction::OperationAbstraction > getBinaryAbstraction ( Operators::BinaryOperators type, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > & typeQualifiers, AlgorithmCategories::AlgorithmCategory category );

	static ext::list < ext::pair < Operators::BinaryOperators, AlgorithmFullInfo > > listBinaryOverloads ( );


	template < class Param >
	static void unregisterPrefix ( Operators::PrefixOperators type ) {
		unregisterPrefixInternal ( type, AlgorithmBaseInfo::operatorEntryInfo < Param > ( ) );
	}

	template < class Return, class Param >
	static void registerPrefix ( Operators::PrefixOperators type, std::function < Return ( Param ) > func ) {
		registerPrefixInternal ( type, std::make_unique < PrefixOperator < Return, Param > > ( func ) );
	}

	template < Operators::PrefixOperators Type, class Param >
	static void registerPrefix ( ) {
		if constexpr ( Type == Operators::PrefixOperators::PLUS ) {
			std::function < decltype ( + std::declval < Param > ( ) ) ( Param ) > callback = [ ] ( Param a ) { return + a; };
			registerPrefix ( Type, callback );
		} else if constexpr ( Type == Operators::PrefixOperators::MINUS ) {
			std::function < decltype ( - std::declval < Param > ( ) ) ( Param ) > callback = [ ] ( Param a ) -> decltype ( auto ) { return - a; };
			registerPrefix ( Type, callback );
		} else if constexpr ( Type == Operators::PrefixOperators::LOGICAL_NOT ) {
			std::function < decltype ( ! std::declval < Param > ( ) ) ( Param ) > callback = [ ] ( Param a ) -> decltype ( auto ) { return ! a; };
			registerPrefix ( Type, callback );
		} else if constexpr ( Type == Operators::PrefixOperators::BINARY_NEG ) {
			std::function < decltype ( ~ std::declval < Param > ( ) ) ( Param ) > callback = [ ] ( Param a ) -> decltype ( auto ) { return ~ a; };
			registerPrefix ( Type, callback );
		} else if constexpr ( Type == Operators::PrefixOperators::INCREMENT ) {
			std::function < decltype ( ++ std::declval < Param > ( ) ) ( Param ) > callback = [ ] ( Param a ) -> decltype ( auto ) { return ++ a; };
			registerPrefix ( Type, callback );
		} else if constexpr ( Type == Operators::PrefixOperators::DECREMENT ) {
			std::function < decltype ( -- std::declval < Param > ( ) ) ( Param ) > callback = [ ] ( Param a ) -> decltype ( auto ) { return -- a; };
			registerPrefix ( Type, callback );
		} else {
			static_assert ( "Unhandled prefix operator." );
		}
	}

	static std::shared_ptr < abstraction::OperationAbstraction > getPrefixAbstraction ( Operators::PrefixOperators type, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > & typeQualifiers, AlgorithmCategories::AlgorithmCategory category );

	static ext::list < ext::pair < Operators::PrefixOperators, AlgorithmFullInfo > > listPrefixOverloads ( );


	template < class Param >
	static void unregisterPostfix ( Operators::PostfixOperators type ) {
		unregisterPostfixInternal ( type, AlgorithmBaseInfo::operatorEntryInfo < Param > ( ) );
	}

	template < class Return, class Param >
	static void registerPostfix ( Operators::PostfixOperators type, std::function < Return ( Param ) > func ) {
		registerPostfixInternal ( type, std::make_unique < PostfixOperator < Return, Param > > ( func ) );
	}

	template < Operators::PostfixOperators Type, class Param >
	static void registerPostfix ( ) {
		if constexpr ( Type == Operators::PostfixOperators::INCREMENT ) {
			std::function < decltype ( std::declval < Param > ( ) ++ ) ( Param ) > callback = [ ] ( Param a ) { std::decay_t < Param > tmp = a; a ++; return tmp; };
			registerPostfix ( Type, callback );
		} else if constexpr ( Type == Operators::PostfixOperators::DECREMENT ) {
			std::function < decltype ( std::declval < Param > ( ) -- ) ( Param ) > callback = [ ] ( Param a ) { std::decay_t < Param > tmp = a; a --; return tmp; };
			registerPostfix ( Type, callback );
		} else {
			static_assert ( "Unhandled postfix operator." );
		}
	}

	static std::shared_ptr < abstraction::OperationAbstraction > getPostfixAbstraction ( Operators::PostfixOperators type, const ext::vector < std::string > & paramTypes, const ext::vector < abstraction::TypeQualifiers::TypeQualifierSet > & typeQualifiers, AlgorithmCategories::AlgorithmCategory category );

	static ext::list < ext::pair < Operators::PostfixOperators, AlgorithmFullInfo > > listPostfixOverloads ( );

};

} /* namespace abstraction */

#include <abstraction/MemberAbstraction.hpp>
#include <abstraction/AlgorithmAbstraction.hpp>
#include <abstraction/WrapperAbstraction.hpp>

namespace abstraction {

template < class Return, class FirstParam, class SecondParam >
std::shared_ptr < abstraction::OperationAbstraction > OperatorRegistry::BinaryOperator < Return, FirstParam, SecondParam >::getAbstraction ( ) const {
	return std::make_shared < abstraction::AlgorithmAbstraction < Return, FirstParam, SecondParam > > ( m_callback );
}

template < class Return, class Param >
std::shared_ptr < abstraction::OperationAbstraction > OperatorRegistry::PrefixOperator < Return, Param >::getAbstraction ( ) const {
	return std::make_shared < abstraction::AlgorithmAbstraction < Return, Param > > ( m_callback );
}

template < class Return, class Param >
std::shared_ptr < abstraction::OperationAbstraction > OperatorRegistry::PostfixOperator < Return, Param >::getAbstraction ( ) const {
	return std::make_shared < abstraction::AlgorithmAbstraction < Return, Param > > ( m_callback );
}

} /* namespace abstraction */

