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

#ifndef _VALUE_HOLDER_HPP_
#define _VALUE_HOLDER_HPP_

#include <alib/memory>
#include <alib/typeindex>
#include <alib/typeinfo>
#include <abstraction/ValueHolderInterface.hpp>

namespace abstraction {

template < class Type >
class ValueImpl : public ValueHolderInterface < std::decay_t < Type > > {
	std::optional < std::decay_t < Type > > m_data;

public:
	using ValueHolderInterface < Type >::ValueHolderInterface;

	void setValue ( const Type & data ) override {
		m_data = std::move ( const_cast < Type && > ( data ) );
	}

	Type && getValue ( ) override {
		return std::move ( m_data.value ( ) );
	}
};

template < class Type >
class ReferenceImpl : public ValueHolderInterface < Type > {
	std::optional < std::reference_wrapper < Type > > m_data;

public:
	using ValueHolderInterface < Type >::ValueHolderInterface;

	void setValue ( const Type & data ) override {
		m_data = std::reference_wrapper < Type > ( const_cast < Type & > ( data ) );
	}

	Type && getValue ( ) override {
		return std::move ( m_data->get ( ) );
	}
};

template < class Type >
class ValueHolder : public std::conditional_t < std::is_reference_v < Type >, ReferenceImpl < std::decay_t < Type > >, ValueImpl < std::decay_t < Type > > > {
	bool m_isTemporary;

	abstraction::TypeQualifiers::TypeQualifierSet getTypeQualifiers ( ) const override {
		return abstraction::TypeQualifiers::typeQualifiers < Type > ( );
	}

	ext::type_index getTypeIndex ( ) const override {
		return ext::type_index ( typeid ( std::decay_t < Type > ) );
	}

	bool isTemporary ( ) const override {
		return m_isTemporary;
	}

	std::shared_ptr < abstraction::Value > asValue ( bool move, bool temporary ) override {
		( void ) move;
		( void ) temporary;

		if constexpr ( std::is_abstract_v < std::decay_t < Type > > )
			throw std::domain_error ( "Cannot declare value of abstract class." );
		else if constexpr ( ! std::is_assignable_v < std::decay_t < Type > &, std::decay_t < Type > > )
			throw std::domain_error ( "Cannot assign value." );
		else
			return std::make_shared < abstraction::ValueHolder < std::decay_t < Type > > > ( retrieveValue < std::decay_t < Type > > ( this->shared_from_this ( ), move ), temporary );
	}

public:
	ValueHolder ( Type && value, bool temporary ) : m_isTemporary ( temporary ) {
		if ( TypeQualifiers::isLvalueRef ( this->getTypeQualifiers ( ) ) && this->isTemporary ( ) )
			throw std::domain_error ( "Lvalue references cannot be temporaries." );

		this->setValue ( std::forward < Type > ( value ) );
	}
};

} /* namespace abstraction */

#endif /* _VALUE_HOLDER_HPP_ */
