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

#ifndef _VALUE_HOLDER_INTERFACE_HPP_
#define _VALUE_HOLDER_INTERFACE_HPP_

#include <abstraction/Value.hpp>

namespace abstraction {

template < class Type >
class ValueHolderInterface : public Value {
	bool m_isConst;
	bool m_isRvalueRef;
	bool m_isLvalueRef;
	bool m_isAutoMove;

public:
	ValueHolderInterface ( const std::shared_ptr < abstraction::OperationAbstraction > & ref, bool isConst, bool isRvalueRef, bool isLvalueRef, bool isAutoMove ) : Value ( ref ), m_isConst ( isConst ), m_isRvalueRef ( isRvalueRef ), m_isLvalueRef ( isLvalueRef ), m_isAutoMove ( isAutoMove ) {
	}

	virtual Type && getValue ( ) const = 0;

	bool isConst ( ) const {
		return m_isConst;
	}

	bool isRvalueRef ( ) const {
		return m_isRvalueRef;
	}

	bool isLvalueRef ( ) const {
		return m_isLvalueRef;
	}

	bool isAutoMove ( ) const {
		return m_isAutoMove;
	}

	void setConst ( bool value ) {
		m_isConst = value;
	}

	void setRvalueRef ( bool value ) {
		m_isRvalueRef = value;
	}

	void setLvalueRef ( bool value ) {
		m_isLvalueRef = value;
	}

	void setAutoMove ( bool value ) {
		m_isAutoMove = value;
	}

};

template < class ParamType >
ParamType retrieveValue ( const std::shared_ptr < abstraction::Value > & param, bool move ) {
	(void) move;
	using Type = std::decay_t < ParamType >;

	std::shared_ptr < ValueHolderInterface < std::decay_t < Type > > > interface = std::dynamic_pointer_cast < ValueHolderInterface < std::decay_t < Type > > > ( param->getProxyAbstraction ( ) );
	if ( ! interface )
		throw std::invalid_argument ( "Abstraction does not provide value of type " + ext::to_string < ParamType > ( ) + " but " + param->getType ( ) + "." );

	if constexpr ( std::is_rvalue_reference_v < ParamType > ) {
		if ( move || interface->isAutoMove ( ) )
			return std::move ( interface->getValue ( ) );
		else
			throw std::domain_error ( "Cannot bind without move" );
	} else if constexpr ( std::is_lvalue_reference_v < ParamType > ) {
		Type && res = interface->getValue ( );
		return res;
	} else if constexpr ( std::is_copy_constructible_v < Type > && std::is_move_constructible_v < Type > ) {
		if ( ! interface->isConst ( ) && ( move || interface->isAutoMove ( ) ) )
			return std::move ( interface->getValue ( ) );
		else {
			const Type & res = interface->getValue ( );
			return res;
		}
	} else if constexpr ( std::is_copy_constructible_v < Type > && ! std::is_move_constructible_v < Type > ) {
		if ( ! interface->isConst ( ) && move )
			throw std::domain_error ( "Value not move constructible" );
		else {
			Type && res = interface->getValue ( );
			return res;
		}
	} else if constexpr ( ! std::is_copy_constructible_v < Type > && std::is_move_constructible_v < Type > ) {
		if ( ! interface->isConst ( ) && move )
			return std::move ( interface->getValue ( ) );
		else
			throw std::domain_error ( "Value not copy constructible" );
	} else { // ! std::is_copy_constructible_v < Type > && ! std::is_move_constructible_v < Type >
		if ( ! interface->isConst ( ) && move )
			throw std::domain_error ( "Value not move constructible" );
		else
			throw std::domain_error ( "Value not copy constructible" );
	}
}

} /* namespace abstraction */

#endif /* _VALUE_HOLDER_INTERFACE_HPP_ */
