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

#ifndef _WRAPPER_ABSTRACTION_HPP_
#define _WRAPPER_ABSTRACTION_HPP_

#include <memory>

#include <alib/array>

#include <abstraction/OperationAbstraction.hpp>
#include <common/AbstractionHelpers.hpp>

namespace abstraction {

class UnspecifiedType {
};

template < size_t NumberOfParams >
class WrapperAbstractionImpl : public OperationAbstraction {
	std::shared_ptr < OperationAbstraction > m_abstraction;
	ext::array < std::pair < std::shared_ptr < abstraction::Value >, bool >, NumberOfParams > m_params;

protected:
	ext::array < std::pair < std::shared_ptr < abstraction::Value >, bool >, NumberOfParams > & getParams ( ) {
		return m_params;
	}

	virtual std::shared_ptr < abstraction::OperationAbstraction > evalAbstractionFunction ( ) = 0;

	std::shared_ptr < OperationAbstraction > & getAbstraction ( ) {
		return m_abstraction;
	}

	const std::shared_ptr < OperationAbstraction > & getAbstraction ( ) const {
		return m_abstraction;
	}

	void attachInputsToAbstraction ( ) {
		for ( size_t index = 0; index < NumberOfParams; ++ index )
			this->m_abstraction->attachInput ( m_params [ index ].first, index, m_params [ index ].second );
	}

public:
	WrapperAbstractionImpl ( ) {
		for ( size_t i = 0; i < NumberOfParams; ++ i ) {
			m_params [ i ].first = nullptr;
			m_params [ i ].second = false;
		}
	}

private:
	void attachInput ( const std::shared_ptr < abstraction::Value > & input, size_t index, bool move ) override {
		if ( index >= m_params.size ( ) )
			throw std::invalid_argument ( "Parameter index " + ext::to_string ( index ) + " out of bounds.");

		m_params [ index ].first = input;
		m_params [ index ].second = move;
	}

	void detachInput ( size_t index ) override {
		if ( index >= m_params.size ( ) )
			throw std::invalid_argument ( "Parameter index " + ext::to_string ( index ) + " out of bounds.");

		m_params [ index ].first = nullptr;
		m_params [ index ].second = false;
	}

public:
	bool inputsAttached ( ) const override {
		for ( const std::pair < std::shared_ptr < abstraction::Value >, bool > & param : m_params )
			if ( ! param.first )
				return false;

		return true;
	}

	std::shared_ptr < abstraction::Value > eval ( ) override {
		if ( ! inputsAttached ( ) )
			return nullptr;

		m_abstraction = this->evalAbstractionFunction ( );

		this->attachInputsToAbstraction ( );

		return this->getAbstraction ( )->eval ( );
	}

	size_t numberOfParams ( ) const override {
		return NumberOfParams;
	}

	ext::type_index getReturnTypeIndex ( ) const override {
		if ( this->m_abstraction )
			return this->m_abstraction->getReturnTypeIndex ( );
		else
			throw std::domain_error ( "ReturnTypeIndex not avaiable before evaluation." );
	}

	ext::set < abstraction::ParamQualifiers::ParamQualifier > getReturnTypeQualifiers ( ) const override {
		if ( this->m_abstraction )
			return this->m_abstraction->getReturnTypeQualifiers ( );
		else
			throw std::domain_error ( "ReturnTypeQualifiers not avaiable before evaluation." );
	}

	std::shared_ptr < abstraction::OperationAbstraction > getProxyAbstraction ( ) override {
		if ( this->m_abstraction )
			return this->m_abstraction->getProxyAbstraction ( );
		else
			throw std::domain_error ( "Proxy abstraction not avaiable before evaluation." );
	}

};

} /* namespace abstraction */

extern template class abstraction::WrapperAbstractionImpl < 1 >;
extern template class abstraction::WrapperAbstractionImpl < 2 >;
extern template class abstraction::WrapperAbstractionImpl < 3 >;

namespace abstraction {

template < class ... ParamTypes >
class WrapperAbstraction : public WrapperAbstractionImpl < sizeof ... ( ParamTypes ) > {
	std::function < std::shared_ptr < abstraction::OperationAbstraction > ( ParamTypes ... ) > m_WrapperFinder;

public:
	WrapperAbstraction ( std::function < std::shared_ptr < abstraction::OperationAbstraction > ( ParamTypes ... ) > wrapperFinder ) : m_WrapperFinder ( std::move ( wrapperFinder ) ) {
	}

	std::shared_ptr < abstraction::OperationAbstraction > evalAbstractionFunction ( ) override {
		return abstraction::apply < ParamTypes ... > ( m_WrapperFinder, this->getParams ( ) );
	}

	ext::type_index getParamTypeIndex ( size_t index ) const override {
		return abstraction::paramType < ParamTypes ... > ( index );
	}

	ext::set < abstraction::ParamQualifiers::ParamQualifier > getParamTypeQualifiers ( size_t index ) const override {
		return abstraction::paramTypeQualifiers < ParamTypes ... > ( index );
	}

};

} /* namespace abstraction */

#endif /* _WRAPPER_ABSTRACTION_HPP_ */
