#include "externals.hh"
#include "control.hh"
#include "sync.hh"

using namespace cops;


/*= X_OpFailure ==============================================================*/

X_OpFailure::X_OpFailure(
		__rd__ std::string const& source ,
		__rd__ uint32_t line ,
		__rd__ std::string const& error ) noexcept
	: std::exception( ) , source_( source ) , line_( line ) ,
		error_( error )
{
	std::ostringstream oss;
	oss << "Program error (" << source << ", l. " << line << "): " << error;
	fullMessage_ = oss.str( );
}

char const* X_OpFailure::what( ) const noexcept
{
	return fullMessage_.c_str( );
}


/*= T_Op =====================================================================*/

T_Op::T_Op( __rd__ const E_Op op )
	: op_( op )
{ }

T_Op::~T_Op( ) { }


X_OpFailure T_Op::error(
		__rd__ std::string const& message ) const noexcept
{
	return X_OpFailure( source , line , message );
}


/*= OPLoadConstant ===========================================================*/

OPLoadConstant::OPLoadConstant(
		__rd__ const float constant )
	: T_Op( OP_LOAD_CONSTANT ) , constant( constant )
{ }

void OPLoadConstant::execute(
		__rw__ T_Context& ctx )
{
	ctx.opStack.push_back( constant );
}


/*= OPLoadVariable ===========================================================*/

OPLoadVariable::OPLoadVariable(
		__rd__ std::string const& variable )
	: T_Op( OP_LOAD_VARIABLE ) , variable( variable )
{ }

void OPLoadVariable::execute(
		__rw__ T_Context& ctx )
{
	const auto vPos( ctx.varPos.find( variable ) );
	if ( vPos == ctx.varPos.end( ) ) {
		throw error( "variable '" + variable + "' not found" );
	}
	ctx.opStack.push_back( ctx.varValues[ vPos->second ] );
}


/*= OPLoadInput ==============================================================*/

OPLoadInput::OPLoadInput(
		__rd__ std::string const& input )
	: T_Op( OP_LOAD_VARIABLE ) , input( input )
{ }

void OPLoadInput::execute(
		__rw__ T_Context& ctx )
{
	ctx.opStack.push_back( ctx.sync->valueOf( input , *( ctx.time ) ) );
}


/*= OPStoreVariable ==========================================================*/

OPStoreVariable::OPStoreVariable(
		__rd__ std::string const& variable )
	: T_Op( OP_LOAD_VARIABLE ) , variable( variable )
{ }

void OPStoreVariable::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.empty( ) ) {
		throw error( "stack is empty" );
	}

	const auto vPos( ctx.varPos.find( variable ) );
	uint32_t pos;
	if ( vPos == ctx.varPos.end( ) ) {
		pos = ctx.varValues.size( );
		ctx.varPos.emplace( variable , pos );
	} else {
		pos = vPos->second;
	}

	ctx.varValues[ pos ] = ctx.opStack.back( );
	ctx.opStack.pop_back( );
}


/*= Arithmetic operators =====================================================*/

void OPAdd::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.size( ) < 2 ) {
		throw error( "missing operands in stack" );
	}
	const float v( ctx.opStack.back( ) );
	ctx.opStack.pop_back( );
	ctx.opStack.back( ) += v;
}

void OPMul::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.size( ) < 2 ) {
		throw error( "missing operands in stack" );
	}
	const float v( ctx.opStack.back( ) );
	ctx.opStack.pop_back( );
	ctx.opStack.back( ) *= v;
}

void OPNeg::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.empty( ) ) {
		throw error( "missing operand in stack" );
	}
	ctx.opStack.back( ) = -ctx.opStack.back( );
}

void OPInv::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.empty( ) || ctx.opStack.back( ) == 0 ) {
		throw error( "missing operand in stack" );
	}
	ctx.opStack.back( ) = 1.0f / ctx.opStack.back( );
}


/*= Stack operations =========================================================*/

OPDup::OPDup( __rd__ const uint32_t stackIndex )
	: T_Op( OP_DUP ) , stackIndex( stackIndex )
{ }

void OPDup::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.size( ) <= stackIndex ) {
		std::string err( "stack does not have " );
		err += stackIndex + 1;
		err += " items";
		throw error( err );
	}
	ctx.opStack.push_back( *( ctx.opStack.end( ) - stackIndex - 1 ) );
}

/*----------------------------------------------------------------------------*/

OPXchg::OPXchg( __rd__ const uint32_t stackIndex )
	: T_Op( OP_XCHG ) , stackIndex( stackIndex )
{ }

void OPXchg::execute(
		__rw__ T_Context& ctx )
{
	if ( ctx.opStack.size( ) <= stackIndex ) {
		std::string err( "stack does not have " );
		err += stackIndex + 1;
		err += " items";
		throw error( err );
	}
	if ( stackIndex ) {
		std::swap( *( ctx.opStack.end( ) - 1 ) , *( ctx.opStack.end( ) - stackIndex - 1 ) );
	}
}


/*= OPSetUniform =============================================================*/

OPSetUniform::OPSetUniform(
		__rd__ const uint32_t program ,
		__rd__ const uint32_t uniform ,
		__rd__ const uint32_t count ,
		__rd__ const bool integer )
	: T_Op( OP_SET_UNIFORM ) , program( program ) , uniform( uniform ) ,
		count( count ) , integer( integer )
{ }

void OPSetUniform::execute(
		__rw__ T_Context& ctx )
{
	if ( count == 0 || ctx.opStack.size( ) < count ) {
		std::string err( "stack does not have " );
		err += count;
		err += " items";
		throw error( err );
	}

	void* funcs[] = {
		&glProgramUniform1fv , &glProgramUniform2fv , &glProgramUniform3fv , &glProgramUniform4fv ,
		&glProgramUniform1iv , &glProgramUniform2iv , &glProgramUniform3iv , &glProgramUniform4iv ,
	};
	void (*func)( int , int , int , void* ) = (void (*)( int , int , int , void* )) funcs[ count + ( integer ? 4 : 0 ) ];

	if ( integer ) {
		int values[ count ];
		auto i = count;
		while ( i != 0 ) {
			values[ count - i ] = int( ctx.opStack.back( ) );
			ctx.opStack.pop_back( );
			i --;
		}
		func( program , uniform , 1 , values );
	} else {
		float values[ count ];
		auto i = count;
		while ( i != 0 ) {
			values[ count - i ] = ctx.opStack.back( );
			ctx.opStack.pop_back( );
			i --;
		}
		func( program , uniform , 1 , values );
	}
}