#include "externals.hh"
#include "ops.hh"

using namespace ops;
using namespace ebcl;


/*= OPCODE INFORMATIONS ========================================================*/

namespace {

struct T_OpInfo
{
	char const* name;
	uint32_t nArgs;
	int32_t sdMain{ 0 };
	int32_t sdFPU{ 0 };
	uint32_t fpuReq{ 0 };

	template< typename ... Mods >
	constexpr T_OpInfo( char const* name ,
			uint32_t nArgs ,
			Mods&&... mods );

	constexpr T_OpInfo( char const* name );
};


template< typename ... Mods >
constexpr void ApplyMods_( T_OpInfo& op , Mods&&... mods );

template< >
constexpr void ApplyMods_( T_OpInfo& )
{ }

template< typename M0 , typename ... Mods >
constexpr void ApplyMods_(
		T_OpInfo& op ,
		M0&& mod ,
		Mods&&... mods )
{
	mod.applyTo( op );
	ApplyMods_( op , std::forward< Mods >( mods ) ... );
}

template< typename ... Mods >
constexpr T_OpInfo::T_OpInfo(
		char const* name ,
		uint32_t nArgs ,
		Mods&&... mods )
	: name( name ) , nArgs( nArgs )
{
	ApplyMods_( *this , std::forward< Mods >( mods ) ... );
}

constexpr T_OpInfo::T_OpInfo( char const* name )
	: T_OpInfo( name , 0 )
{}


struct OpStackMain
{
	int32_t value;
	explicit constexpr OpStackMain( int32_t v ) : value( v ) { }
	constexpr void applyTo( T_OpInfo& op )
		{ op.sdMain = value; }
};

struct OpStackFPU
{
	int32_t value;
	explicit constexpr OpStackFPU( int32_t v ) : value( v ) { }
	constexpr void applyTo( T_OpInfo& op )
		{ op.sdFPU = value; }
};


static T_KeyValueTable< E_OpType , T_OpInfo > OpInfoTable_{ ([]() {
	T_KeyValueTable< E_OpType , T_OpInfo > infos;

	infos.add( E_OpType::OP_END , T_OpInfo{ "end" } );
	//
	infos.add( E_OpType::OP_CALL , T_OpInfo{ "call" , 1 } );
	infos.add( E_OpType::OP_RET , T_OpInfo{ "ret" , 1 , OpStackMain{ -1 } } );
	//
	infos.add( E_OpType::OP_SKIP , T_OpInfo{ "skip" , 1 } );
	infos.add( E_OpType::OP_COND_SKIP , T_OpInfo{ "cond-skip" , 2 } );
	//
	infos.add( E_OpType::OP_RES_STACK , T_OpInfo{ "res-stack" , 1 } );
	infos.add( E_OpType::OP_PUSH , T_OpInfo{ "push" , 0 , OpStackMain{ 1 } } );
	infos.add( E_OpType::OP_POP , T_OpInfo{ "pop" , 1 } );
	infos.add( E_OpType::OP_DUP , T_OpInfo{ "dup" , 1 , OpStackMain{ 1 } } );
	//
	infos.add( E_OpType::OP_LOAD , T_OpInfo{ "load" , 1 } );
	infos.add( E_OpType::OP_STORE , T_OpInfo{ "store" , 1 } );
	infos.add( E_OpType::OP_SLOAD , T_OpInfo{ "load-stack" , 1 } );
	infos.add( E_OpType::OP_CONST , T_OpInfo{ "const" , 1 } );
	infos.add( E_OpType::OP_OFFSET , T_OpInfo{ "offset" , 1 } );
	//
	infos.add( E_OpType::OP_GET_INPUT , T_OpInfo{ "get-input" , 1 , OpStackFPU{ 1 } } );
	infos.add( E_OpType::OP_FP_LOAD , T_OpInfo{ "fp-load" , 1 , OpStackFPU{ 1 } } );
	infos.add( E_OpType::OP_FP_SLOAD , T_OpInfo{ "fp-load-stack" , 1 , OpStackFPU{ 1 } } );
	infos.add( E_OpType::OP_FP_STORE , T_OpInfo{ "fp-store" , 1 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_SSTORE , T_OpInfo{ "fp-store-stack" , 1 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_SSTORE_INT , T_OpInfo{ "fp-store-stack-int" , 1 , OpStackFPU{ -1 } } );
	//
	infos.add( E_OpType::OP_FP_CMP , T_OpInfo{ "fp-cmp" , 1 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_ADD , T_OpInfo{ "fp-add" , 0 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_SUB , T_OpInfo{ "fp-sub" , 0 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_MUL , T_OpInfo{ "fp-mul" , 0 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_DIV , T_OpInfo{ "fp-div" , 0 , OpStackFPU{ -1 } } );
	infos.add( E_OpType::OP_FP_POW , T_OpInfo{ "fp-pow" , 0 , OpStackFPU{ -1 } } );
	//
	infos.add( E_OpType::OP_FP_NEG , T_OpInfo{ "fp-neg" , 0 } );
	infos.add( E_OpType::OP_FP_INV , T_OpInfo{ "fp-inv" , 0 } );
	infos.add( E_OpType::OP_FP_NOT , T_OpInfo{ "fp-not" , 0 } );
	infos.add( E_OpType::OP_FP_SIN , T_OpInfo{ "fp-sin" , 0 } );
	infos.add( E_OpType::OP_FP_COS , T_OpInfo{ "fp-cos" , 0 } );
	infos.add( E_OpType::OP_FP_TAN , T_OpInfo{ "fp-tan" , 0 } );
	infos.add( E_OpType::OP_FP_SQRT , T_OpInfo{ "fp-sqrt" , 0 } );
	infos.add( E_OpType::OP_FP_EXP , T_OpInfo{ "fp-exp" , 0 } );
	infos.add( E_OpType::OP_FP_LN , T_OpInfo{ "fp-ln" , 0 } );
	//
	infos.add( E_OpType::OP_GEN_ASSETS , T_OpInfo{ "gen-assets" , 2 } );
	infos.add( E_OpType::OP_INIT_PIPELINE , T_OpInfo{ "pipeline" , 1 , OpStackMain{ -1 } } );
	infos.add( E_OpType::OP_INIT_PROGRAM , T_OpInfo{ "program" , 1 } );
	infos.add( E_OpType::OP_INIT_TEXTURE , T_OpInfo{ "texture" , 2 , OpStackMain{ -3 } } );
	infos.add( E_OpType::OP_INIT_SAMPLER , T_OpInfo{ "sampler" , 2 , OpStackMain{ -3 } } );
	infos.add( E_OpType::OP_FB_ATTACH , T_OpInfo{ "fb-attach" , 2 , OpStackMain{ -1 } } );
	//
	infos.add( E_OpType::OP_USE_FRAMEBUFFER , T_OpInfo{ "use-framebuffer" , 0 , OpStackMain{ -1 } } );
	infos.add( E_OpType::OP_FB_TOGGLE , T_OpInfo{ "fb-toggle" , 2 } );
	infos.add( E_OpType::OP_USE_PIPELINE , T_OpInfo{ "use-pipeline" , 0 , OpStackMain{ -1 } } );
	infos.add( E_OpType::OP_USE_PROGRAM , T_OpInfo{ "use-program" , 0 , OpStackMain{ -1 } } );
	infos.add( E_OpType::OP_USE_TEXTURE , T_OpInfo{ "use-texture" , 1 , OpStackMain{ -2 } } );
	infos.add( E_OpType::OP_UNIFORMS , T_OpInfo{ "uniforms" , 2 , OpStackMain{ -2 } } );
	infos.add( E_OpType::OP_VIEWPORT , T_OpInfo{ "viewport" , 0 , OpStackMain{ -4 } } );
	//
	infos.add( E_OpType::OP_FULLSCREEN , T_OpInfo{ "fullscreen" } );
	infos.add( E_OpType::OP_CLEAR , T_OpInfo{ "clear" , 0 , OpStackMain{ -4 } } );
	//
	infos.add( E_OpType::OP_UI_PENTER , T_OpInfo{ "ui-prof-enter" , 1 } );
	infos.add( E_OpType::OP_UI_PEXIT , T_OpInfo{ "ui-prof-exit" } );
	infos.add( E_OpType::OP_UI_INPUT_DFT , T_OpInfo{ "ui-input-default" , 2 } );
	infos.add( E_OpType::OP_UI_INPUT_OVR , T_OpInfo{ "ui-overrides" , 1 } );
	infos.add( E_OpType::OP_UI_ODBG , T_OpInfo{ "ui-odbg" , 2 , OpStackMain{ -1 } } );

	return infos;
})( ) };

} // namespace

uint32_t ops::ArgumentsFor(
		const E_OpType op ) noexcept
{
	assert( OpInfoTable_.contains( op ) );
	return OpInfoTable_.get( op )->nArgs;
}

int32_t ops::DeltaMainStack(
		const E_OpType op ) noexcept
{
	assert( OpInfoTable_.contains( op ) );
	return OpInfoTable_.get( op )->sdMain;
}

int32_t ops::DeltaFPUStack(
		const E_OpType op ) noexcept
{
	assert( OpInfoTable_.contains( op ) );
	return OpInfoTable_.get( op )->sdFPU;
}


/*= STRING FORMATTING ==========================================================*/

T_StringBuilder& ops::operator<<(
		T_StringBuilder& sb ,
		const E_OpType et )
{
	assert( OpInfoTable_.contains( et ) );
	sb << OpInfoTable_.get( et )->name;
	return sb;
}

T_StringBuilder& ops::operator<<(
		T_StringBuilder& sb ,
		T_Op const& op )
{
	sb << op.op;
	const auto args{ OpInfoTable_.get( op.op )->nArgs };
	for ( auto i = 0u ; i < args ; i ++ ) {
		sb << ' ' << op.args[ i ];
	}
	return sb;
}