#ifndef _CLI_DECLARE_RUNNABLE_COMMAND_H_
#define _CLI_DECLARE_RUNNABLE_COMMAND_H_

#include <alib/foreach>

#include <ast/Command.h>
#include <environment/Environment.h>
#include <ast/Statement.h>
#include <common/CastHelper.h>
#include <registration/AlgoRegistration.hpp>

namespace cli {

class DeclareRunnableCommand : public Command {
	std::string m_name;
	std::vector < std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > > m_params;
	std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > m_result;
	std::shared_ptr < Command > m_body;

public:
	DeclareRunnableCommand ( std::string name, std::vector < std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > > params, std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > result, std::shared_ptr < Command > body ) : m_name ( std::move ( name ) ), m_params ( std::move ( params ) ), m_result ( std::move ( result ) ), m_body ( std::move ( body ) ) {
	}

	DeclareRunnableCommand ( std::string name, std::vector < std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > > params, std::shared_ptr < Command > body ) : m_name ( std::move ( name ) ), m_params ( std::move ( params ) ), m_result ( std::make_pair ( abstraction::TypeQualifiers::TypeQualifierSet::NONE, std::make_unique < ImmediateArg > ( "void" ) ) ), m_body ( std::move ( body ) ) {
	}

	CommandResult run ( Environment & environment ) const override {
		std::vector < std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::string > > params;
		ext::vector < ext::tuple < std::string, abstraction::TypeQualifiers::TypeQualifierSet, std::string > > paramSpecs;

		for ( const std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::unique_ptr < Arg > > & param : m_params ) {
			params.push_back ( std::make_pair ( param.first, param.second->eval ( environment ) ) );
			paramSpecs.push_back ( ext::make_tuple ( std::string ( "auto" ), param.first, param.second->eval ( environment ) ) );
		}

		ext::pair < std::string, abstraction::TypeQualifiers::TypeQualifierSet > resultSpec = ext::make_pair ( m_result.second->eval ( environment ), m_result.first );
		std::shared_ptr < Command > body = m_body;

		std::function < std::shared_ptr < abstraction::Value > ( std::vector < std::shared_ptr < abstraction::Value > > ) > callback = [ = ] ( std::vector < std::shared_ptr < abstraction::Value > > actualParams ) {
			Environment newEnvironment;
			newEnvironment.setResult ( std::make_shared < abstraction::Void > ( ) );
			for ( const ext::tuple < const std::shared_ptr < abstraction::Value > &, const std::pair < abstraction::TypeQualifiers::TypeQualifierSet, std::string > & > & params_bind : ext::make_tuple_foreach ( actualParams, params ) ) {
				std::shared_ptr < abstraction::Value > res = std::get < 0 > ( params_bind )->clone ( std::get < 1 > ( params_bind ).first, false );
				newEnvironment.setVariable ( std::get < 1 > ( params_bind ).second, res );
			}

			body->run ( newEnvironment );
			std::shared_ptr < abstraction::Value > result = newEnvironment.getResult ( );

			if ( resultSpec.first == "void" && result->getType ( ) != "void" )
				throw std::logic_error ( "Non void type returned from void routine" );

			if ( result->getType ( ) != "void" ) {
				bool isResultValue = result->getTypeQualifiers ( ) && abstraction::TypeQualifiers::TypeQualifierSet::LREF ? false : true;
				result = result->clone ( resultSpec.second, isResultValue );
			}

			return result;
		};

		abstraction::AlgorithmRegistry::registerRaw ( m_name, callback, resultSpec, paramSpecs );
		return CommandResult::OK;
	}
};

} /* namespace cli */

#endif /* _CLI_DECLARE_RUNNABLE_COMMAND_H_ */
