/*
 * 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 {
public:
	using Value::Value;

	virtual void setValue ( const Type & ) = 0;

	virtual Type && getValue ( ) = 0;

};

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

	std::shared_ptr < ValueHolderInterface < Type > > interface = std::dynamic_pointer_cast < ValueHolderInterface < 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_reference_v < ParamType > ) {
		if ( interface->isTemporary ( ) && ! interface->isRef ( ) && interface->getTemporaryReferences ( ) == 0 )
			throw std::domain_error ( "Taking reference to a temporary." );
	}*/

	if constexpr ( std::is_rvalue_reference_v < ParamType > ) {
		if ( param->isTemporary ( ) || move )
			return std::move ( interface->getValue ( ) );
		else
			throw std::domain_error ( "Cannot bind without move" );
	} else if constexpr ( std::is_lvalue_reference_v < ParamType > && std::is_const_v < std::remove_reference_t < ParamType > > ) {
		Type && res = interface->getValue ( );
		return res;
	} else if constexpr ( std::is_lvalue_reference_v < ParamType > ) {
		if ( ( param->isTemporary ( ) || move ) && ! TypeQualifiers::isRef ( param->getTypeQualifiers ( ) ) )
			throw std::domain_error ( "Cannot bind temporary to non-const reference" );
		Type && res = interface->getValue ( );
		return res;
	} else if constexpr ( std::is_copy_constructible_v < Type > && std::is_move_constructible_v < Type > ) {
		if ( ! TypeQualifiers::isConst ( param->getTypeQualifiers ( ) ) && ( param->isTemporary ( ) || move ) )
			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 ( ! TypeQualifiers::isConst ( param->getTypeQualifiers ( ) ) && ( param->isTemporary ( ) || 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 ( ! TypeQualifiers::isConst ( param->getTypeQualifiers ( ) ) && ( param->isTemporary ( ) || 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 ( ! TypeQualifiers::isConst ( param->getTypeQualifiers ( ) ) )
			throw std::domain_error ( "Value not move constructible" );
		else
			throw std::domain_error ( "Value not copy constructible" );
	}
}

} /* namespace abstraction */

#endif /* _VALUE_HOLDER_INTERFACE_HPP_ */
