/*
 * PackingAbstraction.hpp
 *
 *  Created on: 29. 5. 2018
 *	  Author: Jan Travnicek
 */

#ifndef _PACKING_ABSTRACTION_HPP_
#define _PACKING_ABSTRACTION_HPP_

#include <alib/memory>
#include <alib/array>

#include <abstraction/OperationAbstraction.hpp>
#include <abstraction/Value.hpp>

namespace abstraction {

class PackingAbstractionImpl : public OperationAbstraction {
protected:
	class LazyValue : public Value {
		std::shared_ptr < Value > cache;
		std::shared_ptr < abstraction::OperationAbstraction > m_lifeReference;

	public:
		LazyValue ( const std::shared_ptr < abstraction::OperationAbstraction > & ref );

		std::shared_ptr < abstraction::Value > asValue ( bool move, bool isTemporary ) override;

		ext::type_index getTypeIndex ( ) const override;

		abstraction::TypeQualifiers::TypeQualifierSet getTypeQualifiers ( ) const override;

		std::shared_ptr < abstraction::Value > getProxyAbstraction ( ) override;

		const std::shared_ptr < abstraction::OperationAbstraction > & getLifeReference ( ) const;

		bool isTemporary ( ) const override;
	};

	struct ConnectionTarget {
		size_t targetId;
		size_t paramPosition;
	};

private:
	ext::vector < std::shared_ptr < LazyValue > > m_abstractions;

public:
	PackingAbstractionImpl ( ext::vector < std::shared_ptr < abstraction::OperationAbstraction > > abstractions ) {
		for ( std::shared_ptr < abstraction::OperationAbstraction > & abstraction : abstractions )
			m_abstractions.push_back ( std::make_shared < LazyValue > ( std::move ( abstraction ) ) );
	}

	void setInnerConnection ( size_t sourceId, size_t targetId, size_t paramPosition ) {
		m_abstractions [ targetId ]->getLifeReference ( )->attachInput ( m_abstractions [ sourceId ], paramPosition );
	}

	void clearInnerConnection ( size_t targetId, size_t paramPosition ) {
		m_abstractions [ targetId ]->getLifeReference ( )->detachInput ( paramPosition );
	}

	bool inputsAttached ( ) const override {
		for ( const std::shared_ptr < LazyValue > & operation : m_abstractions )
			if ( ! operation->getLifeReference ( )->inputsAttached ( ) )
				return false;

		return true;
	}

protected:
	const ext::vector < std::shared_ptr < LazyValue > > & getAbstractions ( ) const {
		return m_abstractions;
	}

};

template < size_t NumberOfParams >
class PackingAbstraction : public PackingAbstractionImpl {
	ext::array < ext::vector < ConnectionTarget >, NumberOfParams > m_connections;
	size_t m_resultId;

public:
	void setOuterConnection ( size_t sourceId, size_t targetId, size_t paramPosition ) {
		m_connections [ sourceId ].push_back ( ConnectionTarget { targetId, paramPosition } );
	}

	PackingAbstraction ( ext::vector < std::shared_ptr < abstraction::OperationAbstraction > > abstractions, size_t resultId ) : PackingAbstractionImpl ( std::move ( abstractions ) ), m_resultId ( resultId ) {
	}

private:
	void attachInput ( const std::shared_ptr < abstraction::Value > & input, size_t index ) override {
		try {
			for ( const ConnectionTarget & target : m_connections [ index ] )
				getAbstractions ( ) [ target.targetId ]->getLifeReference ( )->attachInput ( input, target.paramPosition );
		} catch ( ... ) {
			this->detachInput ( index );
			throw;
		}
	}

	void detachInput ( size_t index ) override {
		for ( const ConnectionTarget & target : m_connections [ index ] )
			getAbstractions ( ) [ target.targetId ]->getLifeReference ( )->detachInput ( target.paramPosition );
	}

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

		return getAbstractions ( ) [ m_resultId ]->getProxyAbstraction ( );
	}

	size_t numberOfParams ( ) const override {
		return NumberOfParams;
	}

	ext::type_index getParamTypeIndex ( size_t index ) const override {
		return getAbstractions ( ) [ m_connections.at ( index ) [ 0 ].targetId ]->getLifeReference ( )->getParamTypeIndex ( m_connections.at ( index ) [ 0 ].paramPosition );
	}

	abstraction::TypeQualifiers::TypeQualifierSet getParamTypeQualifiers ( size_t index ) const override {
		return getAbstractions ( ) [ m_connections.at ( index ) [ 0 ].targetId ]->getLifeReference ( )->getParamTypeQualifiers ( m_connections.at ( index ) [ 0 ].paramPosition );
	}

	ext::type_index getReturnTypeIndex ( ) const override {
		return getAbstractions ( ) [ m_resultId ]->getLifeReference ( )->getReturnTypeIndex ( );
	}

	abstraction::TypeQualifiers::TypeQualifierSet getReturnTypeQualifiers ( ) const override {
		return getAbstractions ( ) [ m_resultId ]->getLifeReference ( )->getReturnTypeQualifiers ( );
	}

	std::shared_ptr < abstraction::OperationAbstraction > getProxyAbstraction ( ) override {
		return getAbstractions ( ) [ m_resultId ]->getLifeReference ( )->getProxyAbstraction ( );
	}

};

} /* namespace abstraction */

extern template class abstraction::PackingAbstraction < 2 >;
extern template class abstraction::PackingAbstraction < 3 >;

#endif /* _PACKING_ABSTRACTION_HPP_ */
