#include "externals.hh"

#include "common.hh"
#include "c-project.hh"
#include "c-ops.hh"
#include "c-opcomp.hh"

#include <ebcl/Algorithms.hh>

using namespace ebcl;
using namespace ops;
using namespace opast;


#define M_LOGSTR_( S , L ) \
	logger( [](){ return T_StringBuilder{ S }; } , L )


namespace {

struct T_CompilerImpl_
{
	T_CompilerImpl_( F_OPLogger* const logger ,
			const bool noUIInstructions ) noexcept
		: logger( *logger ) , noUIInstructions( noUIInstructions )
	{}

	P_OpProgram compile( T_OpsParserOutput const& input ) noexcept;

    private:
	F_OPLogger& logger;
	const bool noUIInstructions;

	T_Visitor< A_Node > astVisitor{ ASTVisitorBrowser };
	T_Set< uint32_t > constants{ UseTag< IndexBacked< > >( ) };
	T_KeyValueTable< T_String , uint32_t > locations;

	T_OpsParserOutput* input;
	P_OpProgram output;

	uint32_t fiVariables , fiFramebuffers ,
		 fiPipelines , fiPrograms ,
		 fiSamplers , fiTextures;

	uint32_t sdMain , sdFPU;

	/*
	 * Conditionals look something like this:
	 *
	 * 01	{ compute expression and push it }
	 * 02	skip-cond value-for-case-1 2 { 5 - 2 - 1 }
	 * 03		{ case 1 code }
	 * 04		skip 6 { 11 - 4 - 1 }
	 * 05	skip-cond value-for-case-2 3 { 9 - 5 - 1 }
	 * 06		{ case 2 code }
	 * 07		{ case 2 code }
	 * 08		skip 2 { 11 - 8 - 1 }
	 * 09	{ default case code }
	 * 10	{ default case code }
	 * 11	pop 1
	 *
	 * So, in order to adjust the skips once we know about them,
	 * we need to store the location of the previous skip-cond,
	 * and the locations of all end-of-case skips.
	 */
	struct T_CondInfo_ {
		uint32_t prevCase;
		T_AutoArray< uint32_t , 16 > caseEnds;
	};
	T_AutoArray< T_CondInfo_ , 16 > condJumps;

	uint32_t fbCurrent; // Current framebuffer attachment

	void gatherConstants( ) noexcept;
	void countAssets( ) noexcept;

	bool compileNode( uint32_t funcIndex ,
			A_Node& node ,
			bool exit ) noexcept;
	void processFunction(
			bool exit ,
			uint32_t args ,
			uint32_t lvars ,
			T_SRDLocation const& location ) noexcept;

	bool processIdentifier(
			uint32_t funcIndex ,
			T_IdentifierExprNode const& node ) noexcept;
	bool processIdentifier(
			uint32_t funcIndex ,
			T_String const& id ,
			T_SRDLocation const& location ) noexcept;

	void addInstruction(
			E_OpType op ,
			T_SRDLocation const& location ) noexcept;
	void addInstruction(
			E_OpType op ,
			uint32_t arg0 ,
			T_SRDLocation const& location ) noexcept;
	void addInstruction(
			E_OpType op ,
			std::initializer_list< uint32_t > args ,
			T_SRDLocation const& location ) noexcept;
	void applyStackEffects(
			T_Op const& op ) noexcept;
};


P_OpProgram T_CompilerImpl_::compile(
		T_OpsParserOutput const& in ) noexcept
{
	input = const_cast< T_OpsParserOutput* >( &in );
	output = NewOwned< T_OpProgram >( );

	// Gather all constants used in expressions, count resources
	gatherConstants( );
	countAssets( );

	// Get function indices
	// FIXME ideally we should remap functions so that init is always 0
	// and frame is always 1
	output->init = input->root.functionIndex( "*init*" );
	output->frame = input->root.functionIndex( "*frame*" );
	logger( [this]( ) {
		T_StringBuilder sb;
		sb << "Function indices\n\tinit: " << output->init
			<< "\n\tframe: " << output->frame;
		return sb;
	} , 3 );

	// Compile each function
	uint32_t nInstr = 0;
	uint32_t cfi;
	for ( cfi = 0u ; cfi < input->root.nFunctions( ) ; cfi ++ ) {
		output->ops.next( );
		auto& func( input->root.function( cfi ) );
		logger( [&]( ) {
			T_StringBuilder sb;
			sb << "... Compiling function " << func.name( );
			return sb;
		} , 3 );

		sdMain = sdFPU = 0;
		astVisitor.visit( func ,
			[=]( A_Node& node , const bool exit ) -> bool {
				return compileNode( cfi , node , exit );
			} );

		logger( [&]( ) {
			T_StringBuilder sb , temp;
			sb << "...... Compiled function " << func.name( );
			for ( auto i = 0u ; i < output->ops.sizeOf( cfi ) ; i ++ ) {
				temp << "(" << output->ops.get( cfi , i ) << ")";
				while ( temp.length( ) < 30 ) {
					temp << ' ';
				}
				sb << "\n\t\t" << temp << "{ "
					<< output->ops.get( cfi , i ).location
					<< " }";
				temp.clear( );
			}
			return sb;
		} , 6 );
		logger( [&]( ) {
			T_StringBuilder sb;
			sb << "...... " << output->ops.sizeOf( cfi )
				<< " instructions";
			return sb;
		} , 3 );
		nInstr += output->ops.sizeOf( cfi );
	}
	logger( [=]( ) {
		T_StringBuilder sb;
		sb << "... Generated " << nInstr << " instructions";
		return sb;
	} , 2 );

	return std::move( output );
}

void T_CompilerImpl_::gatherConstants( ) noexcept
{
	M_LOGSTR_( "... Gathering constants" , 2 );
	constants.clear( );
	astVisitor.visit( input->root , [&]( A_Node& node , const bool exit ) {
		if ( exit ) {
			return true;
		}

		T_OpValue value;
		if ( node.type( ) == A_Node::EXPR_CONST ) {
			value.f = dynamic_cast< T_ConstantExprNode& >( node ).floatValue( );
		} else if ( node.type( ) == A_Node::OP_INPUT ) {
			value.f = dynamic_cast< T_InputInstrNode& >( node ).defValue( );
		} else {
			return true;
		}
		constants.add( value.u );
		return true;
	} );
	for ( auto i = 0u ; i < constants.size( ) ; i ++ ) {
		output->constants.add( constants[ i ] );
	}

	logger( [&]() {
		T_StringBuilder sb;
		char temp[ 9 ];
		sb << constants.size( ) << " constants\n\t";
		for ( auto i = 0u ; i < constants.size( ) ; i ++ ) {
			snprintf( temp , 9 , "%08x" , constants[ i ] );
			sb << "    " << temp;
			if ( i % 4 == 3 && i != constants.size( ) - 1 ) {
				sb << "\n\t";
			}
		}
		return sb;
	} , 3 );
}

void T_CompilerImpl_::countAssets( ) noexcept
{
	M_LOGSTR_( "... Counting resources" , 2 );
	locations.clear( );
	locations.add( T_String::Pooled( "time" ) , 0u );
	locations.add( T_String::Pooled( "width" ) , 1u );
	locations.add( T_String::Pooled( "height" ) , 2u );

	auto const nt{ input->types.size( ) };
	for ( auto i = 0u ; i < nt ; i ++ ) {
		const auto t{ input->types.values( )[ i ] };
		auto const& n{ input->types.keys( )[ i ] };
		switch ( t ) {
		    case E_DataType::FRAMEBUFFER:
			locations.add( n , output->nFramebuffers );
			output->nFramebuffers ++;
			break;
		    case E_DataType::PIPELINE:
			locations.add( n , output->nPipelines );
			output->nPipelines ++;
			break;
		    case E_DataType::PROGRAM:
			locations.add( n , output->nPrograms );
			output->nPrograms ++;
			break;
		    case E_DataType::SAMPLER:
			locations.add( n , output->nSamplers );
			output->nSamplers ++;
			break;
		    case E_DataType::TEXTURE:
			locations.add( n , output->nTextures );
			output->nTextures ++;
			break;
		    case E_DataType::VARIABLE:
			locations.add( n , output->nVariables + 3 );
			output->nVariables ++;
			break;

		    case E_DataType::INPUT:
			assert( !output->inputs.contains(
						input->types.keys( )[ i ] ) );
			output->inputs.add( input->types.keys( )[ i ] );
			break;

		    case E_DataType::BUILTIN:
		    case E_DataType::UNKNOWN:
			break;
		}
	}

	fiVariables = 3 + constants.size( );
	fiFramebuffers = fiVariables + output->nVariables;
	fiPipelines = fiFramebuffers + output->nFramebuffers;
	fiPrograms = fiPipelines + output->nPipelines;
	fiSamplers = fiPrograms + output->nPrograms;
	fiTextures = fiSamplers + output->nSamplers;

	for ( auto i = 0u ; i < nt ; i ++ ) {
		const auto li{ locations.indexOf( input->types.keys( )[ i ] ) };
		if ( li == T_HashIndex::INVALID_INDEX ) {
			continue;
		}

		const auto t{ input->types.values( )[ i ] };
		auto& pos{ locations[ li ] };
		switch ( t ) {
		    case E_DataType::FRAMEBUFFER: pos += fiFramebuffers; break;
		    case E_DataType::PIPELINE: pos += fiPipelines; break;
		    case E_DataType::PROGRAM: pos += fiPrograms; break;
		    case E_DataType::SAMPLER: pos += fiSamplers; break;
		    case E_DataType::TEXTURE: pos += fiTextures; break;

		    case E_DataType::VARIABLE:
		    case E_DataType::INPUT:
		    case E_DataType::BUILTIN:
		    case E_DataType::UNKNOWN:
			break;
		}
	}

	logger( [&]() {
		T_StringBuilder sb;
		sb << "... Resource counts:\n"
			<< "\t\tFramebuffers:\t" << output->nFramebuffers << '\n'
			<< "\t\tPipelines:   \t" << output->nPipelines << '\n'
			<< "\t\tPrograms:    \t" << output->nPrograms << '\n'
			<< "\t\tSamplers:    \t" << output->nSamplers << '\n'
			<< "\t\tTextures:    \t" << output->nTextures << '\n'
			<< "\t\tInputs:      \t" << output->inputs.size( ) << '\n'
		<< "\tTable ranges:\n"
			<< "\t\tBuilt-ins    \t0\t2\n"
			<< "\t\tConstants    \t3\t" << fiVariables - 1 << '\n'
			<< "\t\tVariables    \t" << fiVariables << '\t' << fiFramebuffers - 1 << '\n'
			<< "\t\tFramebuffers \t" << fiFramebuffers << '\t' << fiPipelines - 1 << '\n'
			<< "\t\tPipelines    \t" << fiPipelines << '\t' << fiPrograms - 1 << '\n'
			<< "\t\tPrograms     \t" << fiPrograms << '\t' << fiSamplers - 1 << '\n'
			<< "\t\tSamplers     \t" << fiSamplers << '\t' << fiTextures - 1 << '\n'
			<< "\t\tTextures     \t" << fiTextures << '\t' << fiTextures + output->nTextures - 1
		;
		return sb;
	} , 3 );
	logger( [&]() {
		T_StringBuilder sb;
		T_Array< uint32_t > indices( locations.size( ) );
		indices.ensureCapacity( locations.size( ) );
		for ( auto i = 0u ; i < locations.size( ) ; i ++ ) {
			indices.add( i );
		}
		indices.sort( [this]( uint32_t a , uint32_t b ) {
			return T_Comparator< uint32_t >::compare(
					locations.values( )[ a ] ,
					locations.values( )[ b ] );
		} );
		sb << "... Location map (constants not included)";
		for ( auto idx : indices ) {
			sb << "\n\t" << locations.values( )[ idx ]
				<< '\t' << locations.keys( )[ idx ];
		}
		return sb;
	} , 4 );
}

bool T_CompilerImpl_::compileNode(
		const uint32_t funcIndex ,
		A_Node& node ,
		const bool exit ) noexcept
{
	switch ( node.type( ) ) {

	    //- PROGRAM STRUCTURE -----------------------------------------------------------------

	    case A_Node::ROOT:
		fprintf( stderr , "Internal error: root node found during compilation\n" );
		std::abort( );
		break;

	    case A_Node::ILIST: break;
	    case A_Node::OP_LOCALS: break;

	    case A_Node::DECL_FN:
	    {
		T_FuncNode& fn( (T_FuncNode&) node );
		const auto args( fn.arguments( ) );
		processFunction( exit , args , fn.locals( ) - args , node.location( ) );
		break;
	    }

	    case A_Node::DECL_INIT:
	    {
		A_FuncNode& fn( (A_FuncNode&) node );
		processFunction( exit , 0 , fn.locals( ) , node.location( ) );
		if ( output->nFramebuffers ) {
			addInstruction( OP_OFFSET , fiFramebuffers , node.location( ) );
			addInstruction( OP_GEN_ASSETS , { 0 , output->nFramebuffers } , node.location( ) );
		}
		if ( output->nPipelines ) {
			addInstruction( OP_OFFSET , fiPipelines , node.location( ) );
			addInstruction( OP_GEN_ASSETS , { 1 , output->nPipelines } , node.location( ) );
		}
		if ( output->nSamplers ) {
			addInstruction( OP_OFFSET , fiSamplers , node.location( ) );
			addInstruction( OP_GEN_ASSETS , { 2 , output->nSamplers } , node.location( ) );
		}
		if ( output->nTextures ) {
			addInstruction( OP_OFFSET , fiTextures , node.location( ) );
			addInstruction( OP_GEN_ASSETS , { 3 , output->nTextures } , node.location( ) );
		}
		break;
	    }

	    case A_Node::DECL_FRAME:
	    {
		A_FuncNode& fn( (A_FuncNode&) node );
		processFunction( exit , 0 , fn.locals( ) , node.location( ) );
		break;
	    }


	    //- GENERAL / FLOW CONTROL INSTRUCTIONS -----------------------------------------------

	    case A_Node::OP_CALL:
		if ( exit ) {
			auto& call( (T_CallInstrNode&) node );
			const auto fi( input->root.functionIndex( call.id( ) ) );
			assert( fi >= 0 );
			auto& callee( input->root.function( fi ) );
			assert( callee.type( ) == A_Node::DECL_FN );
			auto& fcallee( (T_FuncNode&) callee );
			const auto args( fcallee.arguments( ) );
			assert( sdMain > args );
			addInstruction( OP_CALL , fi , node.location( ) );
			sdMain -= args;
		}
		break;

	    case A_Node::OP_SET:
		if ( exit ) {
			auto& id( ((T_SetInstrNode&)node).id( ) );
			auto& func{ input->root.function( funcIndex ) };
			if ( func.hasLocal( id ) ) {
				const auto pos( func.getLocalIndex( id ) + 1 );
				addInstruction( OP_FP_SSTORE , sdMain - pos - 1 ,
						node.location( ) );
			} else {
				addInstruction( OP_FP_STORE ,
						*locations.get( ((T_SetInstrNode&)node).id( ) ) ,
						node.location( ) );
			}
		}
		break;

	    case A_Node::OP_COND:
		if ( exit ) {
			// Update all skips
			const auto cpos( output->ops.sizeOf( funcIndex ) );
			auto const& skips( condJumps.last( ).caseEnds );
			for ( auto i = 0u ; i < skips.size( ) ; i ++ ) {
				const auto skip( skips[ i ] );
				assert( output->ops.get( funcIndex , skip ).op == OP_SKIP );
				output->ops.get( funcIndex , skip ).args[ 0 ] = ( cpos - skip - 1 );
			}

			addInstruction( OP_POP , 0 , node.location( ) );
			sdMain --;
			condJumps.removeLast( );
		} else {
			condJumps.addNew( );
		}
		break;
	    case A_Node::TN_CONDITION:
		if ( exit ) {
			addInstruction( OP_PUSH , node.location( ) );
			addInstruction( OP_FP_SSTORE_INT , 0 , node.location( ) );
		}
		break;
	    case A_Node::TN_CASE:
		if ( exit ) {
			// Store a potential skip location
			condJumps.last( ).caseEnds.add( output->ops.sizeOf( funcIndex ) );

			// Update the initial skip
			const auto cpos( output->ops.sizeOf( funcIndex ) );
			const auto ppos( condJumps.last( ).prevCase );
			const auto diff( cpos - ppos );
			output->ops.get( funcIndex , ppos ).args[ 0 ] = diff - 1;
		} else {
			// If there is a previous skip location, insert the skip instruction
			if ( !condJumps.last( ).caseEnds.empty( ) ) {
				addInstruction( OP_SKIP , 0 , node.location( ) );
				const auto ppos( condJumps.last( ).prevCase );
				assert( output->ops.get( funcIndex , ppos ).op == OP_COND_SKIP );
				output->ops.get( funcIndex , ppos ).args[ 0 ] ++;
			}

			// Add the conditional skip
			auto& c( (T_CondInstrNode::T_ValuedCase&) node );
			condJumps.last( ).prevCase = output->ops.sizeOf( funcIndex );
			addInstruction( OP_COND_SKIP , { 0 , uint32_t( c.value( ) ) } ,
					node.location( ) );
		}
		break;
	    case A_Node::TN_DEFAULT:
		// If there is a previous skip location, insert the skip instruction
		// and update the previous conditional skip
		if ( !( exit || condJumps.last( ).caseEnds.empty( ) ) ) {
			addInstruction( OP_SKIP , 0 , node.location( ) );
			const auto ppos( condJumps.last( ).prevCase );
			assert( output->ops.get( funcIndex , ppos ).op == OP_COND_SKIP );
			output->ops.get( funcIndex , ppos ).args[ 0 ] ++;
		}
		break;


	    //- ASSET DECLARATIONS ----------------------------------------------------------------

	    case A_Node::OP_PIPELINE:
		if ( exit ) {
			auto& pn( (T_PipelineInstrNode&) node );
			for ( auto i = 0u ; i < pn.size( ) ; i ++ ) {
				processIdentifier( funcIndex , pn.program( i ) ,
						pn.pLocation( i ) );
			}
			processIdentifier( funcIndex , pn.id( ) , pn.location( ) );
			addInstruction( OP_INIT_PIPELINE , pn.size( ) - 1 , pn.location( ) );
			sdMain -= pn.size( );
		}
		break;

	    case A_Node::OP_PROGRAM:
		if ( exit ) {
			auto& pn( (T_ProgramInstrNode&) node );
			const auto p{ pn.path( ).makeRelative( Common::Project( ).basePath( ) ) };
			if ( !output->progNames.contains( p ) ) {
				output->progNames.add( p );
			}
			assert( locations.contains( pn.id( ) ) );

			addInstruction( OP_INIT_PROGRAM , output->progNames.indexOf( p ) ,
					pn.location( ) );
			addInstruction( OP_STORE , *locations.get( pn.id( ) ) , pn.idLocation( ) );
		}
		break;

	    case A_Node::OP_SAMPLER:
		if ( exit ) {
			auto& sn( (T_SamplerInstrNode&) node );
			if ( !sn.minLod( ) ) {
				// XXX maybe use OP_RES_STACK
				addInstruction( OP_CONST , 0 , node.location( ) );
				addInstruction( OP_PUSH , node.location( ) );
				addInstruction( OP_PUSH , node.location( ) );
			}
			processIdentifier( funcIndex , sn.id( ) , sn.idLocation( ) );
			addInstruction( OP_INIT_SAMPLER , {
						( uint32_t( sn.sampling( ) ) << 2 )
						| ( ( sn.mipmap( ) ? 1 : 0 ) << 1 )
						| ( sn.mipmap( ) ? uint32_t( *sn.mipmap( ) ) : 0 ) ,
						uint32_t( sn.wrapping( ) )
					} , node.location( ) );
		}
		break;

	    case A_Node::OP_TEXTURE:
		if ( exit ) {
			auto& tn( (T_TextureInstrNode&) node );
			const auto hasLOD( tn.lods( ) );
			processIdentifier( funcIndex , tn.id( ) , tn.idLocation( ) );
			addInstruction( OP_INIT_TEXTURE , {
					uint32_t( tn.texType( ) ) , hasLOD ? 1u : 0u
				} , tn.location( ) );
			if ( hasLOD ) {
				sdMain --;
			}
		}
		break;

	    case A_Node::OP_FRAMEBUFFER:
		if ( !exit ) {
			auto& fbn( (T_FramebufferInstrNode&) node );
			processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) );
			addInstruction( OP_USE_FRAMEBUFFER , fbn.location( ) );
			fbCurrent = 0;
		}
		break;
	    case A_Node::TN_FBATT:
		if ( exit ) {
			auto& fan( (T_FramebufferInstrNode::T_Attachment&) node );
			processIdentifier( funcIndex , fan.id( ) , fan.location( ) );
			const uint32_t lod( fan.lod( ) ? 1 : 0 ) ,
			      id( fan.isDepth( ) ? 0 : ++fbCurrent );
			addInstruction( OP_FB_ATTACH , { id , lod } , fan.location( ) );
			if ( lod ) {
				sdMain --;
			}
			if ( id ) {
				addInstruction( OP_FB_TOGGLE , { id - 1 , 1 } , fan.location( ) );
			}
		}
	    	break;


	    //- STATE -----------------------------------------------------------------------------

	    case A_Node::OP_MAINOUT:
		if ( exit ) {
			addInstruction( OP_CONST , 0 , node.location( ) );
			addInstruction( OP_PUSH , node.location( ) );
			addInstruction( OP_USE_FRAMEBUFFER , node.location( ) );
		}
		break;

	    case A_Node::OP_IMAGE:
		if ( exit ) {
			auto& imn{ (T_ImageInstrNode&) node };
			processIdentifier( funcIndex , imn.id( ) , imn.idLocation( ) );
			addInstruction( OP_IMAGE , {
					uint32_t( imn.accessMode( ) ) ,
					imn.hasParameter( T_ImageInstrNode::P_LAYER ) ? 1u : 0u
				} , node.location( ) );
		}
		break;

	    case A_Node::OP_USE_FRAMEBUFFER:
		if ( exit ) {
			auto& fbn( (T_UseInstrNode&) node );
			processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) );
			addInstruction( OP_USE_FRAMEBUFFER , fbn.location( ) );
		}
		break;

	    case A_Node::OP_USE_PIPELINE:
		if ( exit ) {
			auto& fbn( (T_UseInstrNode&) node );
			processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) );
			addInstruction( OP_USE_PIPELINE , fbn.location( ) );
		}
		break;

	    case A_Node::OP_USE_PROGRAM:
		if ( exit ) {
			auto& fbn( (T_UseInstrNode&) node );
			processIdentifier( funcIndex , fbn.id( ) , fbn.idLocation( ) );
			addInstruction( OP_USE_PROGRAM , fbn.location( ) );
		}
		break;

	    case A_Node::OP_USE_TEXTURE:
		if ( exit ) {
			auto& tn( (T_UseTextureInstrNode&) node );
			processIdentifier( funcIndex , tn.samplerId( ) , tn.samplerIdLocation( ) );
			processIdentifier( funcIndex , tn.id( ) , tn.idLocation( ) );
			addInstruction( OP_USE_TEXTURE , tn.bank( ) , tn.location( ) );
		}
		break;

	    case A_Node::OP_UNIFORMS:
		if ( exit ) {
			auto& un( (T_UniformsInstrNode&) node );
			addInstruction( OP_CONST , un.uloc( ) , un.ulocLocation( ) );
			addInstruction( OP_PUSH , un.ulocLocation( ) );
			processIdentifier( funcIndex , un.progId( ) , un.progIdLocation( ) );
			addInstruction( OP_UNIFORMS , { un.size( ) - 1 , un.integers( ) ? 1u : 0u } ,
					un.location( ) );
			sdMain -= un.size( );
		}
		break;

	    case A_Node::OP_VIEWPORT:
		if ( exit ) {
			addInstruction( OP_VIEWPORT , node.location( ) );
		}
		break;


	    //- RENDERING -------------------------------------------------------------------------

	    case A_Node::OP_FULLSCREEN:
		if ( exit ) {
			addInstruction( OP_FULLSCREEN , node.location( ) );
		}
		break;

	    case A_Node::OP_CLEAR:
		if ( exit ) {
			addInstruction( OP_CLEAR , node.location( ) );
		}
		break;

	    case A_Node::OP_COMPUTE:
		if ( exit ) {
			addInstruction( OP_COMPUTE , node.location( ) );
		}
		break;


	    //- DEBUGGING / UI CONTROLS -----------------------------------------------------------

	    case A_Node::OP_PROFILE:
		if ( noUIInstructions ) {
			break;
		}
		if ( exit ) {
			addInstruction( OP_UI_PEXIT , node.location( ) );
		} else {
			auto& pn( (T_ProfileInstrNode&) node );
			if ( ! output->uiStrings.contains( pn.text( ) ) ) {
				output->uiStrings.add( pn.text( ) );
			}
			addInstruction( OP_UI_PENTER , output->uiStrings.indexOf( pn.text( ) ) ,
					pn.location( ) );
		}
		break;

	    case A_Node::OP_INPUT:
		if ( noUIInstructions ) {
			break;
		}
		if ( exit ) {
			auto& in( (T_InputInstrNode&) node );
			T_OpValue value;
			value.f = in.defValue( );
			addInstruction( OP_UI_INPUT_DFT , {
					uint32_t( output->inputs.indexOf( in.id( ) ) ) ,
					uint32_t( constants.indexOf( value.u ) + 3 + output->nVariables ) } ,
				node.location( ) );
		}
		break;

	    case A_Node::OP_ODBG:
		if ( noUIInstructions ) {
			break;
		}
		if ( exit ) {
			auto& n( (T_OutputDebugInstrNode&) node );
			if ( ! output->uiStrings.contains( n.description( ) ) ) {
				output->uiStrings.add( n.description( ) );
			}
			processIdentifier( funcIndex , n.texture( ) , n.textureLocation( ) );
			addInstruction( OP_UI_ODBG , {
						uint32_t( n.mode( ) ) ,
						uint32_t( output->uiStrings.indexOf( n.description( ) ) ) } ,
					node.location( ) );
		}
		break;

	    case A_Node::OP_OVERRIDES:
		if ( noUIInstructions ) {
			break;
		}
		if ( exit ) {
			auto& n{ (T_OverridesInstrNode&) node };
			const auto idx{ output->overrides.add( n.extractRoot( ) ) };
			addInstruction( OP_UI_INPUT_OVR , idx , node.location( ) );
		}
		break;


	    //- EXPRESSIONS - ARGUMENTS -----------------------------------------------------------

	    case A_Node::TN_ARG:
	    {
		auto& n( (T_ArgumentNode&)node );
		if ( n.isIdentifier( ) && !exit ) {
			const bool main{ processIdentifier( funcIndex ,
					(T_IdentifierExprNode&) n.expression( ) ) };
			if ( !main ) {
				addInstruction( OP_PUSH , node.location( ) );
				addInstruction( OP_FP_SSTORE , 0 , node.location( ) );
			}
			return false;
		} else if ( exit && !n.isIdentifier( ) ) {
			addInstruction( OP_PUSH , node.location( ) );
			addInstruction( OP_FP_SSTORE , 0 , node.location( ) );
		}
		break;
	    }


	    //- EXPRESSIONS - OPERATORS -----------------------------------------------------------

	    case A_Node::EXPR_CMP_EQ: case A_Node::EXPR_CMP_NE:
	    case A_Node::EXPR_CMP_GT: case A_Node::EXPR_CMP_GE:
	    case A_Node::EXPR_CMP_LT: case A_Node::EXPR_CMP_LE:
		if ( exit ) {
			const uint32_t op( dynamic_cast< T_BinaryOperatorNode& >( node ).op( )
					- T_BinaryOperatorNode::CMP_EQ );
			addInstruction( OP_FP_CMP , op , node.location( ) );
		}
		break;

	    case A_Node::EXPR_ADD: if ( exit ) { addInstruction( OP_FP_ADD , node.location( ) ); } break;
	    case A_Node::EXPR_SUB: if ( exit ) { addInstruction( OP_FP_SUB , node.location( ) ); } break;
	    case A_Node::EXPR_MUL: if ( exit ) { addInstruction( OP_FP_MUL , node.location( ) ); } break;
	    case A_Node::EXPR_DIV: if ( exit ) { addInstruction( OP_FP_DIV , node.location( ) ); } break;
	    case A_Node::EXPR_POW: if ( exit ) { addInstruction( OP_FP_POW , node.location( ) ); } break;

	    case A_Node::EXPR_NEG: if ( exit ) { addInstruction( OP_FP_NEG , node.location( ) ); } break;
	    case A_Node::EXPR_INV: if ( exit ) { addInstruction( OP_FP_INV , node.location( ) ); } break;
	    case A_Node::EXPR_NOT: if ( exit ) { addInstruction( OP_FP_NOT , node.location( ) ); } break;
	    case A_Node::EXPR_SIN: if ( exit ) { addInstruction( OP_FP_SIN , node.location( ) ); } break;
	    case A_Node::EXPR_COS: if ( exit ) { addInstruction( OP_FP_COS , node.location( ) ); } break;
	    case A_Node::EXPR_TAN: if ( exit ) { addInstruction( OP_FP_TAN , node.location( ) ); } break;
	    case A_Node::EXPR_SQRT: if ( exit ) { addInstruction( OP_FP_SQRT , node.location( ) ); } break;
	    case A_Node::EXPR_EXP: if ( exit ) { addInstruction( OP_FP_EXP , node.location( ) ); } break;
	    case A_Node::EXPR_LN: if ( exit ) { addInstruction( OP_FP_LN , node.location( ) ); } break;


	    //- EXPRESSIONS - TERMINAL NODES ------------------------------------------------------

	    case A_Node::EXPR_CONST:
		if ( !exit ) {
			T_OpValue value;
			value.f = dynamic_cast< T_ConstantExprNode& >( node ).floatValue( );
			addInstruction( OP_FP_LOAD ,
					constants.indexOf( value.u ) + 3 + output->nVariables ,
					node.location( ) );
		}
		break;

	    case A_Node::EXPR_ID:
		if ( !exit ) {
			processIdentifier( funcIndex ,
					dynamic_cast< T_IdentifierExprNode& >( node ) );
		}
		break;

	    case A_Node::EXPR_INPUT:
		if ( !exit ) {
			auto& in( (T_InputExprNode&) node );
			assert( output->inputs.contains( in.id( ) ) );
			addInstruction( OP_GET_INPUT ,
					output->inputs.indexOf( in.id( ) ) ,
					in.location( ) );
		}
		break;
	}

	return true;
}

void T_CompilerImpl_::processFunction(
		const bool exit ,
		const uint32_t args ,
		const uint32_t lvars ,
		T_SRDLocation const& location ) noexcept
{
	if ( exit ) {
		assert( sdMain == args + lvars + 1 );
		assert( sdFPU == 0 );
		if ( lvars ) {
			addInstruction( OP_POP , lvars - 1 , location );
			sdMain -= lvars;
		}
		sdMain -= args;
		addInstruction( OP_RET , args , location );
		assert( sdMain == 0 );
	} else {
		if ( lvars ) {
			addInstruction( OP_RES_STACK , lvars - 1 , location );
			sdMain += lvars;
		}
		sdMain += 1 + args;
	}
}

// Returns true if the identifier caused a push to the main stack, false
// if it pushed to the FPU stack.
bool T_CompilerImpl_::processIdentifier(
		const uint32_t funcIndex ,
		T_IdentifierExprNode const& node ) noexcept
{
	return processIdentifier( funcIndex , node.id( ) , node.location( ) );
}

bool T_CompilerImpl_::processIdentifier(
		uint32_t funcIndex ,
		T_String const& id ,
		T_SRDLocation const& location ) noexcept
{
	auto& func{ input->root.function( funcIndex ) };
	if ( func.hasLocal( id ) ) {
		const E_DataType dt{ func.getLocalType( id ) };
		assert( dt != E_DataType::UNKNOWN );

		uint32_t stackPos;
		if ( func.isArgument( id ) ) {
			auto const& fn( (T_FuncNode&) func );
			const auto nArgs( fn.arguments( ) );
			stackPos = nArgs - 1 - func.getLocalIndex( id );
		} else {
			stackPos = func.getLocalIndex( id ) + 1;
		}
		assert( stackPos < sdMain );

		const auto p( sdMain - ( stackPos + 1 ) );
		if ( dt == E_DataType::VARIABLE ) {
			addInstruction( OP_FP_SLOAD , p , location );
			return false;
		}

		addInstruction( OP_SLOAD , p , location );
		addInstruction( OP_PUSH , location );
		return true;
	}

	assert( input->types.contains( id ) );
	const E_DataType dt{ *( input->types.get( id ) ) };
	assert( dt != E_DataType::UNKNOWN );
	assert( locations.contains( id ) );
	if ( dt == E_DataType::VARIABLE || dt == E_DataType::BUILTIN ) {
		addInstruction( OP_FP_LOAD , *locations.get( id ) ,
				location );
		return false;
	}

	addInstruction( OP_LOAD , *locations.get( id ) ,
			location );
	addInstruction( OP_PUSH , location );
	return true;
}


void T_CompilerImpl_::addInstruction(
		const E_OpType op ,
		T_SRDLocation const& location ) noexcept
{
	assert( ArgumentsFor( op ) == 0 );
	applyStackEffects( output->ops.addNew( op , location ) );
}

void T_CompilerImpl_::addInstruction(
		const E_OpType op ,
		const uint32_t arg0 ,
		T_SRDLocation const& location ) noexcept
{
	assert( ArgumentsFor( op ) == 1 );
	applyStackEffects( output->ops.addNew( op , location , arg0 ) );
}

void T_CompilerImpl_::addInstruction(
		const E_OpType op ,
		std::initializer_list< uint32_t > args ,
		T_SRDLocation const& location ) noexcept
{
	assert( ArgumentsFor( op ) == args.size( ) );
	applyStackEffects( output->ops.addNew( op , location , args ) );
}

void T_CompilerImpl_::applyStackEffects(
		T_Op const& op ) noexcept
{
	const auto m( DeltaMainStack( op.op ) );
	const auto f( DeltaFPUStack( op.op ) );

	logger( [&]() {
		T_StringBuilder sb;
		sb << "applying stack effects for (" << op << ") - sdMain " << sdMain
			<< " (" << m << ") sdFPU " << sdFPU << " (" << f << ')';
		return sb;
	} , 5 );

	if ( m ) {
		assert( m > 0 || sdMain >= uint32_t( -m ) );
		sdMain += m;
	}

	if ( f ) {
		assert( ( f > 0 && sdFPU + f < 8 ) || ( f < 0 && sdFPU >= uint32_t( -m ) ) );
		sdFPU += m;
	}
}

}


/*= T_OpsCompiler ==============================================================*/

T_OpsCompiler::T_OpsCompiler(
		const bool noUIInstructions ) noexcept
	: A_PrivateImplementation( new T_CompilerImpl_( &logger_ , noUIInstructions ) )
{ }

P_OpProgram T_OpsCompiler::compile(
		T_OpsParserOutput const& input ) noexcept
{
	return p< T_CompilerImpl_ >( ).compile( input );
}