#pragma once
#include "c-renderdefs.hh"
#include <ebcl/SRDData.hh>


struct T_SyncOverrideSection;

namespace opast {


/*= BASE CLASS FOR AST NODES ===================================================*/

class T_RootNode;

class A_Node
{
    public:
	enum E_Type {
		ROOT ,		// Root node
		ILIST ,		// Instruction list
		//
		DECL_INIT ,	// Initialisation block
		DECL_FRAME ,	// Frame block
		DECL_FN ,	// Function
		//
		OP_CALL ,	// Function call
		OP_CLEAR ,	// Clear buffer
		OP_COMPUTE ,	// Compute shader dispatch
		OP_COND ,	// Conditional instruction
		OP_FRAMEBUFFER ,// Define framebuffer
		OP_FULLSCREEN ,	// Draw a fullscreen quad
		OP_IMAGE ,	// Image unit binding
		OP_INPUT ,	// Input declaration
		OP_LOCALS ,	// Declare local variables
		OP_MAINOUT ,	// Select the output buffer
		OP_ODBG ,	// Output debugging
		OP_OVERRIDES ,	// Register input overrides
		OP_PIPELINE ,	// Shader pipeline declaration
		OP_PROFILE ,	// Profiling block
		OP_PROGRAM ,	// Shader program declaration
		OP_SAMPLER ,	// Define sampler
		OP_SET ,	// Set instruction
		OP_TEXTURE ,	// Define texture
		OP_UNIFORMS ,	// Set uniforms
		OP_VIEWPORT ,	// Set viewport
		// "Use-<obj type>" instructions
		OP_USE_FRAMEBUFFER ,
		OP_USE_PIPELINE ,
		OP_USE_PROGRAM ,
		OP_USE_TEXTURE ,
		// Unary operators
		EXPR_NEG , EXPR_INV , EXPR_NOT ,
		EXPR_SIN , EXPR_COS , EXPR_TAN ,
		EXPR_SQRT , EXPR_EXP , EXPR_LN ,
		// Binary operators
		EXPR_ADD , EXPR_SUB ,
		EXPR_MUL , EXPR_DIV ,
		EXPR_POW ,
		// Binary operators - comparisons
		EXPR_CMP_EQ , EXPR_CMP_NE ,
		EXPR_CMP_GT , EXPR_CMP_GE ,
		EXPR_CMP_LT , EXPR_CMP_LE ,
		//
		EXPR_ID ,	// Variable access
		EXPR_INPUT ,	// Input value access
		EXPR_CONST ,	// Numeric constant
		// Technical nodes
		TN_CONDITION ,	// Expression for a conditional instruction
		TN_CASE ,	// Valued case for a conditional instruction
		TN_DEFAULT ,	// Default case for a conditional instruction
		TN_FBATT ,	// Framebuffer attachment
		TN_ARG ,	// Call argument
	};

    private:
	const E_Type type_;
	A_Node* parent_;
	ebcl::T_SRDLocation location_;

    protected:
	T_AutoArray< T_OwnPtr< A_Node > , 8 > children_;

	explicit A_Node( const E_Type type ,
			A_Node* const parent ) noexcept;

    public:
	virtual ~A_Node( ) = 0;

	E_Type type( ) const noexcept
		{ return type_; }

	ebcl::T_SRDLocation& location( ) noexcept
		{ return location_; }
	ebcl::T_SRDLocation const& location( ) const noexcept
		{ return location_; }

	A_Node& parent( ) const noexcept
		{ assert( parent_ ); return *parent_; }

	T_RootNode& root( ) const noexcept;

	uint32_t size( ) const noexcept
		{ return children_.size( ); }

	T_OwnPtr< A_Node > const& child(
			const uint32_t index ) const noexcept
		{ return children_[ index ]; }
	T_OwnPtr< A_Node >& child(
			const uint32_t index ) noexcept
		{ return children_[ index ]; }

	bool replace( A_Node const& node ,
			T_OwnPtr< A_Node >& replacement ) noexcept;

	void setParent( A_Node& newParent ) noexcept
		{ parent_ = &newParent; }
};

// Browser function to be used with T_Visitor
extern A_Node* ASTVisitorBrowser(
		A_Node& node ,
		const uint32_t child ) noexcept;

// Data types
enum class E_DataType {
	UNKNOWN ,
	BUILTIN ,
	VARIABLE ,
	FRAMEBUFFER ,
	INPUT ,
	PIPELINE ,
	PROGRAM ,
	SAMPLER ,
	TEXTURE ,
};
M_LSHIFT_OP( T_StringBuilder , E_DataType );


/*= BASE STRUCTURES FOR INSTRUCTIONS =========================================*/

// Some instructions are restricted to either the initialisation code or the
// frame code.
enum class E_InstrRestriction
{
	INIT , FRAME
};
using T_InstrRestriction = T_Flags< E_InstrRestriction >;

// Base class for instruction nodes
class A_InstructionNode : public A_Node
{
    private:
	const T_InstrRestriction restriction_;

    protected:
	A_InstructionNode( const E_Type type ,
			A_Node& parent ,
			T_InstrRestriction restriction = {} ) noexcept
		: A_Node( type , &parent ) ,
			restriction_( restriction )
	{
		assert( !restriction_.isSet({
					E_InstrRestriction::INIT ,
					E_InstrRestriction::FRAME }) );
	}

    public:
	T_InstrRestriction restriction( ) const noexcept
		{ return restriction_; }
};

// Nodes that store lists of instructions
class T_InstrListNode : public A_Node
{
    public:
	T_InstrListNode( A_Node& parent ) noexcept
		: A_Node( ILIST , &parent ) { }

	template<
		typename IType ,
		typename... ArgTypes
	> IType& add( ArgTypes&&... args );

	A_InstructionNode& node( const uint32_t index ) const noexcept
		{ return dynamic_cast< A_InstructionNode& >( *children_[ index ] ); }

	void replaceMultiple(
			A_InstructionNode& instruction ,
			T_InstrListNode* replacement ) noexcept;
};

template<
	typename IType ,
	typename... ArgTypes
> inline IType& T_InstrListNode::add(
		ArgTypes&&... args )
{
	children_.add( NewOwned< IType >( *this ,
			std::forward< ArgTypes >( args ) ... ) );
	return (IType&) *children_.last( );
}

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

// Base class for expressions
class A_ExpressionNode : public A_Node
{
    protected:
	A_ExpressionNode( E_Type type ,
			A_Node& parent ) noexcept
		: A_Node( type , &parent )
	{ }
};
using P_ExpressionNode = T_OwnPtr< A_ExpressionNode >;

// Technical node used for the arguments of various instructions
class T_ArgumentNode : public A_Node
{
    public:
	T_ArgumentNode( A_Node& parent ,
			P_ExpressionNode expr ) noexcept
		: A_Node( TN_ARG , &parent )
	{
		if ( expr ) {
			location( ) = expr->location( );
			children_.add( std::move( expr ) );
		}
	}

	A_ExpressionNode& expression( ) const noexcept
		{ return (A_ExpressionNode&) *child( 0 ); }
	bool isIdentifier( ) const noexcept
		{ return expression( ).type( ) == EXPR_ID; }

	void expression( P_ExpressionNode expr ) noexcept
		{ assert( expr ); child( 0 ) = std::move( expr ); }
};
using P_ArgumentNode = T_OwnPtr< T_ArgumentNode >;


/*= FUNCTIONS AND ROOT NODES =================================================*/

// Function-like nodes
class A_FuncNode : public A_Node
{
    private:
	T_String name_;
	T_InstrListNode* instructions_{ nullptr };

    protected:
	struct T_Local_
	{
		T_String name;
		ebcl::T_SRDLocation location;
		bool argument;
		E_DataType type{ E_DataType::UNKNOWN };

		T_Local_( T_String const& name ,
				ebcl::T_SRDLocation const& location ,
				const bool argument ) noexcept
			: name{ name } , location{ location } ,
				argument{ argument }
		{ }
	};
	T_ObjectTable< T_String , T_Local_ > locals_{
		[]( T_Local_ const& l ) -> T_String {
			return l.name;
		}
	};

    protected:
	// For init or frame entry points.
	// isInit = true => init entry point
	// isInit = false => frame entry point
	explicit A_FuncNode( bool isInit ,
			T_RootNode* const parent ) noexcept;

	// For normal functions
	explicit A_FuncNode( T_String const& name ,
			T_RootNode* const parent ) noexcept;

    public:
	T_String const& name( ) const noexcept
		{ return name_; }

	T_InstrListNode& instructions( ) noexcept
	{
		if ( !instructions_ ) {
			auto p{ NewOwned< T_InstrListNode >( *this ) };
			instructions_ = p.get( );
			children_.add( std::move( p ) );
		}
		return *instructions_;
	}

	// ---------------------------------------------------------------------

	T_Optional< ebcl::T_SRDLocation > addLocalVariable(
			T_String const& name ,
			ebcl::T_SRDLocation const& location ) noexcept;

	uint32_t locals( ) const noexcept
		{ return locals_.size( ); }
	bool hasLocal( T_String const& id ) const noexcept
		{ return locals_.contains( id ); }

	bool isArgument( T_String const& id ) const noexcept
	{
		auto const* const ptr( locals_.get( id ) );
		return ptr && ptr->argument;
	}

	uint32_t getLocalIndex(
			T_String const& name ) const noexcept
		{ return locals_.indexOf( name ); }

	T_String const& getLocalName(
			const uint32_t index ) const noexcept
		{ return locals_[ index ].name; }
	ebcl::T_SRDLocation const& getLocalLocation(
			const uint32_t index ) const noexcept
		{ return locals_[ index ].location; }

	E_DataType getLocalType(
			T_String const& name ) const noexcept
		{ return locals_.get( name )->type; }
	E_DataType getLocalType(
			const uint32_t index ) const noexcept
		{ return locals_[ index ].type; }

	void setLocalType(
			const uint32_t index ,
			const E_DataType type ) noexcept
		{ locals_[ index ].type = type; }

	void removeLocal( T_String const& name ) noexcept
		{ locals_.remove( name ); }
};
using P_InstrListNode = T_OwnPtr< T_InstrListNode >;

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

// Root node, keeps track of the whole tree and related data (function table,
// assets...)
class T_RootNode : public A_Node
{
    private:
	T_KeyValueTable< T_String , uint32_t > functions_;

    public:
	T_RootNode( ) noexcept;

	// ---------------------------------------------------------------------

	// Return type for addFunction. We'll always return a reference to a
	// function node (which may or may not be the same as the initial one,
	// if there were duplicates), and we'll return the location of the
	// initial function in the case of a duplicate.
	struct T_AddFunctionResult
	{
		A_FuncNode& function;
		T_Optional< ebcl::T_SRDLocation > dupLocation;

		T_AddFunctionResult( A_FuncNode& function ) noexcept
			: function{ function } , dupLocation{}
		{}

		T_AddFunctionResult( A_FuncNode& function ,
				ebcl::T_SRDLocation const& dupLocation ) noexcept
			: function{ function } , dupLocation{ dupLocation }
		{}
	};

	// Attempts to add a function. If the function is already present in
	// the table, the result will be set up with the previous declaration's
	// location, and the specified function will not be modified (a duplicate
	// entry will be added to the table instead).
	T_AddFunctionResult addFunction(
			T_OwnPtr< A_FuncNode >& function ) noexcept;

	// ---------------------------------------------------------------------

	bool hasFunction( T_String const& name ) noexcept
		{ return functions_.contains( name ); }
	bool hasInit( ) noexcept
		{ return hasFunction( "*init*" ); }
	bool hasFrame( ) noexcept
		{ return hasFunction( "*frame*" ); }

	uint32_t nFunctions( ) const noexcept
		{ return functions_.size( ); }

	int32_t functionIndex( T_String const& name ) const noexcept
	{
		auto const* const ip{ functions_.get( name ) };
		return ip ? *ip : -1;
	}

	A_FuncNode& function( const uint32_t index ) const noexcept
		{ return dynamic_cast< A_FuncNode& >( *child( index ) ); }

	void removeFunction( T_String const& name ) noexcept
	{
		auto const* const ip{ functions_.get( name ) };
		if ( !ip ) {
			return;
		}
		const auto i{ *ip };
		functions_.remove( name );
		children_.removeSwap( i );
		if ( i < children_.size( ) ) {
			functions_.update( function( i ).name( ) , i );
		}
	}
};

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

// Init & frame functions
class T_SpecialFuncNode : public A_FuncNode
{
    public:
	T_SpecialFuncNode(
			bool isInit ,
			T_RootNode& parent ) noexcept
		: A_FuncNode( isInit , &parent )
	{ }
};

// Normal functions
class T_FuncNode : public A_FuncNode
{
    public:
	T_FuncNode( T_String const& name ,
			T_RootNode& parent ) noexcept
		: A_FuncNode( name , &parent )
	{ }

	// Add an argument. If the argument is a duplicate, return the location
	// of the initial argument.
	T_Optional< ebcl::T_SRDLocation > addArgument(
			ebcl::T_SRDToken const& token ) noexcept;

	uint32_t arguments( ) const noexcept;
};


/*= GENERAL / FLOW CONTROL INSTRUCTIONS ========================================*/

// Function call
class T_CallInstrNode : public A_InstructionNode
{
    private:
	T_String id_;
	ebcl::T_SRDLocation idLocation_;

    public:
	T_CallInstrNode( T_InstrListNode& parent ,
			ebcl::T_SRDToken const& idToken ) noexcept
		: A_InstructionNode( OP_CALL , parent ) ,
			id_( idToken.stringValue( ) ) ,
			idLocation_( idToken.location( ) )
	{ }

	void addArgument( P_ExpressionNode expr ) noexcept
	{
		if ( expr ) {
			children_.add( NewOwned< T_ArgumentNode >(
					*this , std::move( expr ) ) );
		}
	}

	T_String const& id( ) const noexcept
		{ return id_; }
	ebcl::T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }

	T_ArgumentNode& argument( const uint32_t index ) const noexcept
		{ return (T_ArgumentNode&) *child( index ); }
};

// Conditional instruction
class T_CondInstrNode : public A_InstructionNode
{
    public:
	class T_Expression : public A_Node
	{
	    public:
		T_Expression( T_CondInstrNode& parent ,
				P_ExpressionNode expr )
			: A_Node( TN_CONDITION , &parent )
		{
			children_.add( std::move( expr ) );
		}

		A_ExpressionNode& expression( ) const noexcept
			{ return (A_ExpressionNode&) *child( 0 ); }

		void expression( P_ExpressionNode expr ) noexcept
			{ assert( expr ); child( 0 ) = std::move( expr ); }
	};

	class T_ValuedCase : public A_Node
	{
	    private:
		int64_t value_;

	    public:
		T_ValuedCase( T_CondInstrNode& parent ,
				const int64_t value ,
				P_InstrListNode il )
			: A_Node( TN_CASE , &parent ) ,
				value_( value )
		{
			children_.add( std::move( il ) );
		}

		int64_t value( ) const noexcept
			{ return value_; }
		T_InstrListNode& instructions( ) const noexcept
			{ return (T_InstrListNode&) *child( 0 ); }
	};

	class T_DefaultCase : public A_Node
	{
	    public:
		T_DefaultCase( T_CondInstrNode& parent ,
				P_InstrListNode il )
			: A_Node( TN_DEFAULT , &parent )
		{
			children_.add( std::move( il ) );
		}

		T_InstrListNode& instructions( ) const noexcept
			{ return (T_InstrListNode&) *child( 0 ); }
	};

    private:
	T_Optional< uint32_t > expression_;
	T_KeyValueTable< int64_t , uint32_t > cases_;
	T_Optional< uint32_t > defaultCase_;

	void handleRemoval( uint32_t idx ) noexcept;

    public:
	explicit T_CondInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_COND , parent )
	{ }

	void setExpression( P_ExpressionNode expression ) noexcept;
	bool hasExpression( ) const noexcept
		{ return bool( expression_ ); }
	T_Expression& expression( ) const noexcept
		{ return (T_Expression&) *child( *expression_ ); }

	void setCase( const int64_t value ,
			P_InstrListNode instrList ) noexcept;
	void rmCase( const int64_t value ) noexcept
		{ cases_.remove( value ); }

	uint32_t nCases( ) const noexcept
		{ return cases_.size( ); }
	T_Array< int64_t > const& cases( ) const noexcept
		{ return cases_.keys( ); }
	bool hasCase( const int64_t value ) const noexcept
		{ return cases_.contains( value ); }
	T_ValuedCase& getCase( const int64_t value ) const noexcept
		{ return (T_ValuedCase&) *child( *cases_.get( value ) ); }
	T_ValuedCase& getCaseByIndex(
			const uint32_t index ) const noexcept
		{ return (T_ValuedCase&) *child( cases_[ index ] ); }

	void setDefaultCase( P_InstrListNode defaultCase ) noexcept;
	bool hasDefaultCase( ) const noexcept
		{ return bool( defaultCase_ ); }
	T_DefaultCase& defaultCase( ) const noexcept
		{ return (T_DefaultCase&) *child( *defaultCase_ ); }
};

// Local variable declarations
class T_LocalsInstrNode : public A_InstructionNode
{
    private:
	T_AutoArray< T_String , 8 > vars_;
	T_AutoArray< ebcl::T_SRDLocation , 8 > varLocs_;

    public:
	T_LocalsInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_LOCALS , parent )
	{ }

	T_Optional< ebcl::T_SRDLocation > addVariable(
			ebcl::T_SRDToken const& token ) noexcept;

	uint32_t variables( ) const noexcept
		{ return vars_.size( ); }

	T_String const& varName(
			const uint32_t index ) const noexcept
	{ return vars_[ index ]; }

	ebcl::T_SRDLocation const& varLocation(
			const uint32_t index ) const noexcept
	{ return varLocs_[ index ]; }
};

// Setting a global variable
class T_SetInstrNode : public A_InstructionNode
{
    private:
	T_String id_;
	ebcl::T_SRDLocation idLocation_;

    public:
	T_SetInstrNode( T_InstrListNode& parent ,
			ebcl::T_SRDToken const& idToken ) noexcept
		: A_InstructionNode( OP_SET , parent ) ,
			id_( idToken.stringValue( ) ) ,
			idLocation_( idToken.location( ) )
	{ }

	T_String const& id( ) const noexcept
		{ return id_; }
	ebcl::T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }

	void id( T_String const& nid ) noexcept
		{ id_ = nid; }

	void setExpression( P_ExpressionNode expression ) noexcept
	{
		if ( children_.size( ) ) {
			child( 0 ) = std::move( expression );
		} else {
			children_.add( std::move( expression ) );
		}
	}

	bool hasExpression( ) const noexcept
		{ return children_.size( ) && child( 0 ); }
	A_ExpressionNode& expression( ) const noexcept
		{ return (A_ExpressionNode&) *child( 0 ); }
};


/*= RESOURCE DEFINITION INSTRUCTIONS =========================================*/

// Base class
class A_ResourceDefInstrNode : public A_InstructionNode
{
    private:
	T_String id_;
	ebcl::T_SRDLocation idLocation_;
	E_DataType dataType_;

    protected:
	A_ResourceDefInstrNode(
			const E_Type type ,
			T_InstrListNode& parent ,
			ebcl::T_SRDToken const& identifier ,
			const E_DataType dataType ) noexcept
		: A_InstructionNode( type , parent , E_InstrRestriction::FRAME ) ,
			id_( identifier.stringValue( ) ) ,
			idLocation_( identifier.location( ) ) ,
			dataType_( dataType )
	{ }

    public:
	T_String const& id( ) const noexcept
		{ return id_; }
	ebcl::T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }

	E_DataType dataType( ) const noexcept
		{ return dataType_; }
};

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

// Framebuffer definition instruction
class T_FramebufferInstrNode : public A_ResourceDefInstrNode
{
    public:
	class T_Attachment : public A_Node
	{
	    private:
		bool depth_;
		T_String id_;

	    public:
		T_Attachment( T_FramebufferInstrNode& parent ,
				const bool depth ,
				ebcl::T_SRDToken const& texId ,
				P_ExpressionNode lod = { } ) noexcept
			: A_Node( TN_FBATT , &parent ) ,
				depth_( depth ) ,
				id_( texId.stringValue( ) )
		{
			location() = texId.location( );
			if ( lod ) {
				children_.add( NewOwned< T_ArgumentNode >(
						*this , std::move( lod ) ) );
			}
		}

		bool isDepth( ) const noexcept
			{ return depth_; }

		T_String const& id( ) const noexcept
			{ return id_; }

		T_ArgumentNode* lod( ) const noexcept
			{ return (T_ArgumentNode*) ( children_.size( )
					? child( 0 ).get( ) : nullptr ); }

		void id( T_String const& nid ,
				ebcl::T_SRDLocation const& loc ) noexcept
		{
			id_ = nid;
			location( ) = loc;
		}
	};

    private:
	T_AutoArray< uint32_t , 16 > colorAttachments_;
	T_Optional< uint32_t > depthAttachment_;

	bool hasAttachment( T_String const& name ) const noexcept;

    public:
	T_FramebufferInstrNode( T_InstrListNode& parent ,
			ebcl::T_SRDToken const& identifier ) noexcept
		: A_ResourceDefInstrNode( OP_FRAMEBUFFER , parent ,
				identifier , E_DataType::FRAMEBUFFER )
	{ }

	// ---------------------------------------------------------------------

	bool addColorAttachment( ebcl::T_SRDToken const& id ,
			P_ExpressionNode lod = {} ) noexcept;

	uint32_t colorAttachments( ) const noexcept
		{ return colorAttachments_.size( ); }

	T_Attachment& colorAttachment(
			const uint32_t index ) const noexcept
		{ return (T_Attachment&) *child( colorAttachments_[ index ] ); }

	// ---------------------------------------------------------------------

	bool setDepthAttachment( ebcl::T_SRDToken const& token ,
			P_ExpressionNode lod = {} ) noexcept;

	T_Attachment* depthAttachment( ) const noexcept
		{ return depthAttachment_
			? ((T_Attachment*) child( *depthAttachment_ ).get( ) )
			: nullptr; }
};

// Input declaration
class T_InputInstrNode : public A_ResourceDefInstrNode
{
    private:
	float defValue_;
	ebcl::T_SRDLocation dvLocation_;

    public:
	T_InputInstrNode( T_InstrListNode& parent ,
			ebcl::T_SRDToken const& tName ) noexcept
		: A_ResourceDefInstrNode( OP_INPUT , parent ,
				tName , E_DataType::INPUT )
	{}

	T_InputInstrNode( T_InstrListNode& parent ,
			ebcl::T_SRDToken const& tName ,
			ebcl::T_SRDToken const& tDefault ) noexcept
		: A_ResourceDefInstrNode( OP_INPUT , parent ,
				tName , E_DataType::INPUT ) ,
			defValue_( tDefault.floatValue( ) ) , dvLocation_( tDefault.location( ) )
	{}

	float defValue( ) const noexcept
		{ return defValue_; }
	ebcl::T_SRDLocation const& defValueLocation( ) const noexcept
		{ return dvLocation_; }
};

// Pipeline declaration instruction
class T_PipelineInstrNode : public A_ResourceDefInstrNode
{
    private:
	T_StaticArray< T_String , 6 > pids_;
	T_StaticArray< ebcl::T_SRDLocation , 6 > pidLocations_;

    public:
	T_PipelineInstrNode(
			T_InstrListNode& parent ,
			ebcl::T_SRDToken const& idToken ) noexcept
		: A_ResourceDefInstrNode( OP_PIPELINE , parent ,
				idToken , E_DataType::PIPELINE )
	{ }

	// Add a program identifier. The token is assumed to be a valid word,
	// and the list of programs is assumed not to be full. If the identifer
	// is already in the pipeline's list, the location of the first
	// occurrence will be returned.
	T_Optional< ebcl::T_SRDLocation > addProgram(
			ebcl::T_SRDToken const& pidToken ) noexcept;

	uint32_t size( ) const noexcept
		{ return pids_.size( ); }

	T_String const& program( const uint32_t index ) const noexcept
		{ return pids_[ index ]; }
	ebcl::T_SRDLocation const& pLocation( const uint32_t index ) const noexcept
		{ return pidLocations_[ index ]; }

	void program( const uint32_t index ,
			T_String const& id ,
			ebcl::T_SRDLocation const& location ) noexcept
	{
		pids_[ index ] = id;
		pidLocations_[ index ] = location;
	}
};

// Program loader instruction
class T_ProgramInstrNode : public A_ResourceDefInstrNode
{
    private:
	T_FSPath path_;
	ebcl::T_SRDLocation pathLocation_;

    public:
	T_ProgramInstrNode(
			T_InstrListNode& parent ,
			ebcl::T_SRDToken const& idToken ,
			ebcl::T_FSPath path ,
			ebcl::T_SRDLocation const& pathLoc ) noexcept
		: A_ResourceDefInstrNode( OP_PROGRAM , parent ,
				idToken , E_DataType::PROGRAM ) ,
			path_( std::move( path ) ) ,
			pathLocation_( pathLoc )
	{ }

	T_FSPath const& path( ) const noexcept
		{ return path_; }
	ebcl::T_SRDLocation const& pathLocation( ) const noexcept
		{ return pathLocation_; }
};

// Sampler definition
class T_SamplerInstrNode : public A_ResourceDefInstrNode
{
    private:
	struct T_Sampling_
	{
		ebcl::T_SRDLocation location;
		E_TexSampling mode;
	};

	struct T_Mipmaps_
	{
		ebcl::T_SRDLocation location;
		T_Optional< E_TexSampling > mode;
	};

	struct T_Wrapping_
	{
		ebcl::T_SRDLocation location;
		E_TexWrap mode;
	};

	struct T_LOD_
	{
		ebcl::T_SRDLocation location;
		T_Optional< uint32_t > min;
		T_Optional< uint32_t > max;
	};

	T_Optional< T_Sampling_ > sampling_;
	T_Optional< T_Mipmaps_ > mipmaps_;
	T_Optional< T_Wrapping_ > wrapping_;
	T_Optional< T_LOD_ > lod_;

    public:
	T_SamplerInstrNode(
			T_InstrListNode& parent ,
			ebcl::T_SRDToken const& id ) noexcept
		: A_ResourceDefInstrNode( OP_SAMPLER , parent ,
				id , E_DataType::SAMPLER )
	{ }

	// Attempt to set sampling mode, mipmap mode, wrapping mode or
	// LOD minimal/maximal, returning the location of the previous
	// definition if there was one.
	T_Optional< ebcl::T_SRDLocation > setSampling(
			E_TexSampling mode ,
			ebcl::T_SRDLocation const& location ) noexcept;
	T_Optional< ebcl::T_SRDLocation > setMipmapSampling(
			E_TexSampling mode ,
			ebcl::T_SRDLocation const& location ) noexcept;
	T_Optional< ebcl::T_SRDLocation > setNoMipmap(
			ebcl::T_SRDLocation const& location ) noexcept;
	T_Optional< ebcl::T_SRDLocation > setWrapping(
			E_TexWrap mode ,
			ebcl::T_SRDLocation const& location ) noexcept;
	T_Optional< ebcl::T_SRDLocation > setLOD(
			ebcl::T_SRDLocation const& location ,
			P_ExpressionNode min ,
			P_ExpressionNode max ) noexcept;

	// Get either the defined values or the defaults
	E_TexSampling sampling( ) const noexcept
		{ return sampling_ ? sampling_->mode : E_TexSampling::NEAREST; }
	T_Optional< E_TexSampling > mipmap( ) const noexcept
		{ return mipmaps_ ? mipmaps_->mode : T_Optional< E_TexSampling >{}; }
	E_TexWrap wrapping( ) const noexcept
		{ return wrapping_ ? wrapping_->mode : E_TexWrap::REPEAT; }

	T_ArgumentNode* minLod( ) const noexcept
		{ return ( T_ArgumentNode*) ( ( lod_ && lod_->min )
			? child( *( lod_->min ) ).get( )
			: nullptr ); }
	T_ArgumentNode* maxLod( ) const noexcept
		{ return ( T_ArgumentNode*) ( ( lod_ && lod_->max )
			? child( *( lod_->max ) ).get( )
			: nullptr ); }
};

// Texture definition
class T_TextureInstrNode : public A_ResourceDefInstrNode
{
    private:
	E_TexType type_;

    public:
	T_TextureInstrNode(
			T_InstrListNode& parent ,
			ebcl::T_SRDToken const& id ,
			E_TexType type ) noexcept
		: A_ResourceDefInstrNode( OP_TEXTURE , parent ,
				id , E_DataType::TEXTURE ) ,
			type_( type )
	{
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
	}

	E_TexType texType( ) const noexcept
		{ return type_; }

	void setWidth( P_ExpressionNode width ) noexcept
	{
		if ( width ) {
			child( 0 ) = NewOwned< T_ArgumentNode >(
					*this , std::move( width ) );
		}
	}

	void setHeight( P_ExpressionNode height ) noexcept
	{
		if ( height ) {
			child( 1 ) = NewOwned< T_ArgumentNode >(
					*this , std::move( height ) );
		}
	}

	void setLODs( P_ExpressionNode lods ) noexcept
	{
		if ( lods ) {
			child( 2 ) = NewOwned< T_ArgumentNode >(
					*this , std::move( lods ) );
		}
	}

	bool hasWidth( ) const noexcept
		{ return bool( child( 0 ) ); }
	T_ArgumentNode& width( ) const noexcept
		{ return (T_ArgumentNode&) *child( 0 ); }

	bool hasHeight( ) const noexcept
		{ return bool( child( 1 ) ); }
	T_ArgumentNode& height( ) const noexcept
		{ return (T_ArgumentNode&) *child( 1 ); }

	T_ArgumentNode* lods( ) const noexcept
		{ return (T_ArgumentNode*) child( 2 ).get( ); }
};


/*= TOOL CONTROL INSTRUCTIONS ==================================================*/

// Output debugging
class T_OutputDebugInstrNode : public A_InstructionNode
{
    private:
	T_String idTexture_;
	ebcl::T_SRDLocation locTexture_;
	E_ODbgMode mode_;
	ebcl::T_SRDLocation locMode_;
	T_String description_;
	ebcl::T_SRDLocation locDescription_;

    public:
	T_OutputDebugInstrNode(
			T_InstrListNode& parent ,
			ebcl::T_SRDToken const& texture ,
			const E_ODbgMode mode ,
			ebcl::T_SRDLocation const& modeLocation ,
			ebcl::T_SRDToken const& description ) noexcept;

	T_String const& texture( ) const noexcept
		{ return idTexture_; }
	ebcl::T_SRDLocation const& textureLocation( ) const noexcept
		{ return locTexture_; }

	E_ODbgMode mode( ) const noexcept
		{ return mode_; }
	ebcl::T_SRDLocation const& modeLocation( ) const noexcept
		{ return locMode_; }

	T_String const& description( ) const noexcept
		{ return description_; }
	ebcl::T_SRDLocation const& descriptionLocation( ) const noexcept
		{ return locDescription_; }
};

// User interface overrides for inputs
class T_OverridesInstrNode : public A_InstructionNode
{
    private:
	T_OwnPtr< T_SyncOverrideSection > overrides_;

    public:
	T_OverridesInstrNode(
			T_InstrListNode& parent ,
			T_OwnPtr< T_SyncOverrideSection > overrides ) noexcept
		: A_InstructionNode( OP_OVERRIDES , parent , E_InstrRestriction::FRAME ) ,
			overrides_( std::move( overrides ) )
	{ }

	T_SyncOverrideSection& root( ) const noexcept
		{ return *overrides_; }

	T_OwnPtr< T_SyncOverrideSection > extractRoot( ) noexcept
		{ return std::move( overrides_ ); }
};

// Profiling instruction
class T_ProfileInstrNode : public A_InstructionNode
{
    private:
	T_String text_;

    public:
	T_ProfileInstrNode(
			T_InstrListNode& parent ,
			T_String const& text ) noexcept
		: A_InstructionNode( OP_PROFILE , parent , E_InstrRestriction::INIT ) ,
			text_( text )
	{
		children_.add( NewOwned< T_InstrListNode >( *this ) );
	}

	T_String const& text( ) const
		{ return text_; }

	T_InstrListNode& instructions( ) const noexcept
		{ return (T_InstrListNode&) *child( 0 ); }
};


/*= RENDERING INSTRUCTIONS =====================================================*/

// Clear instruction
class T_ClearInstrNode : public A_InstructionNode
{
    public:
	T_ClearInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_CLEAR , parent ,
				E_InstrRestriction::INIT )
	{ }

	void addComponent( P_ExpressionNode expr ) noexcept
	{
		if ( expr ) {
			children_.add( NewOwned< T_ArgumentNode >(
					*this , std::move( expr ) ) );
		}
	}

	T_ArgumentNode& component( const uint32_t index ) const noexcept
		{ return (T_ArgumentNode&) *child( index ); }
};

// Fullscreen quad instruction
class T_FullscreenInstrNode : public A_InstructionNode
{
    public:
	T_FullscreenInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_FULLSCREEN , parent , E_InstrRestriction::INIT )
	{ }
};

// Dispatch a compute job
class T_ComputeInstrNode : public A_InstructionNode
{
    public:
	T_ComputeInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_COMPUTE , parent )
	{ }

	void addComponent( P_ExpressionNode expr ) noexcept
	{
		if ( expr ) {
			children_.add( NewOwned< T_ArgumentNode >(
					*this , std::move( expr ) ) );
		}
	}

	T_ArgumentNode& component( const uint32_t index ) const noexcept
		{ return (T_ArgumentNode&) *child( index ); }
};


/*= RENDERING STATE INSTRUCTIONS ===============================================*/

// Main output selection
class T_MainOutputInstrNode : public A_InstructionNode
{
    public:
	T_MainOutputInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_MAINOUT , parent , E_InstrRestriction::INIT )
	{ }
};

// Uniform setting instruction
class T_UniformsInstrNode : public A_InstructionNode
{
    private:
	bool integers_;
	T_String progId_;
	ebcl::T_SRDLocation progIdLocation_;
	uint32_t uloc_;
	ebcl::T_SRDLocation ulocLocation_;

    public:
	T_UniformsInstrNode( T_InstrListNode& parent ,
			const bool integers ,
			ebcl::T_SRDToken const& prog ,
			ebcl::T_SRDToken const& loc ) noexcept
		: A_InstructionNode{ OP_UNIFORMS , parent } , integers_{ integers } ,
			progId_{ prog.stringValue( ) } , progIdLocation_{ prog.location( ) } ,
			uloc_{ (uint32_t)loc.longValue( ) } , ulocLocation_{ loc.location( ) }
	{ }

	bool integers( ) const noexcept
		{ return integers_; }

	T_String const& progId( ) const noexcept
		{ return progId_; }
	ebcl::T_SRDLocation const& progIdLocation( ) const noexcept
		{ return progIdLocation_; }
	void progId( T_String const& id ,
			ebcl::T_SRDLocation const& location ) noexcept
		{ progId_ = id; progIdLocation_ = location; }

	uint32_t uloc( ) const noexcept
		{ return uloc_; }
	ebcl::T_SRDLocation const& ulocLocation( ) const noexcept
		{ return ulocLocation_; }

	void addValue( P_ExpressionNode value ) noexcept
	{
		if ( value ) {
			children_.add( NewOwned< T_ArgumentNode >(
					*this , std::move( value ) ) );
		}
	}

	T_ArgumentNode& value( const uint32_t index ) const noexcept
		{ return (T_ArgumentNode&) *child( index ); }
};

// Use-* instructions (framebuffers, programs, pipelines)
// Also serves as the base for use-texture
class T_UseInstrNode : public A_InstructionNode
{
    public:
	enum E_Type {
		FRAMEBUFFER , PIPELINE , PROGRAM , TEXTURE
	};

    private:
	T_String id_;
	ebcl::T_SRDLocation idLocation_;

    public:
	T_UseInstrNode( T_InstrListNode& parent ,
			const E_Type type ,
			ebcl::T_SRDToken const& identifier ) noexcept
		: A_InstructionNode( ([type]() {
				switch ( type ) {
					case FRAMEBUFFER: return OP_USE_FRAMEBUFFER;
					case PIPELINE: return OP_USE_PIPELINE;
					case PROGRAM: return OP_USE_PROGRAM;
					case TEXTURE: return OP_USE_TEXTURE;
				}
				std::abort( );
			} )( ) , parent ) ,
			id_( identifier.stringValue( ) ) ,
			idLocation_( identifier.location( ) )
	{ }

	T_String const& id( ) const noexcept
		{ return id_; }
	ebcl::T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }
	void id( T_String const& nid ,
			ebcl::T_SRDLocation const& location ) noexcept
		{ id_ = nid; idLocation_ = location; }
};

// Texture/sampler use instructions
class T_UseTextureInstrNode : public T_UseInstrNode
{
    private:
	uint32_t bank_;
	ebcl::T_SRDLocation bankLocation_;
	T_String samplerId_;
	ebcl::T_SRDLocation samplerIdLocation_;

    public:
	T_UseTextureInstrNode( T_InstrListNode& parent ,
			ebcl::T_SRDToken const& bank ,
			ebcl::T_SRDToken const& identifier ,
			ebcl::T_SRDToken const& sampler ) noexcept
		: T_UseInstrNode( parent , TEXTURE , identifier ) ,
			bank_( bank.longValue( ) ) ,
			bankLocation_( bank.location( ) ) ,
			samplerId_( sampler.stringValue( ) ) ,
			samplerIdLocation_( sampler.location( ) )
	{ }

	uint32_t bank( ) const noexcept
		{ return bank_; }
	ebcl::T_SRDLocation const& bankLocation( ) const noexcept
		{ return bankLocation_; }

	T_String const& samplerId( ) const noexcept
		{ return samplerId_; }
	ebcl::T_SRDLocation const& samplerIdLocation( ) const noexcept
		{ return samplerIdLocation_; }
	void samplerId( T_String const& id ,
			ebcl::T_SRDLocation const& location ) noexcept
		{ samplerId_ = id; samplerIdLocation_ = location; }
};

// Viewport instruction
class T_ViewportInstrNode : public A_InstructionNode
{
    public:
	enum E_Parameter {
		PX , PY , PWIDTH , PHEIGHT
	};

    public:
	T_ViewportInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_VIEWPORT , parent ,
				E_InstrRestriction::INIT )
	{
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
	}

	void setParameter( const E_Parameter p , P_ExpressionNode value ) noexcept
	{
		if ( value ) {
			children_[ int( p ) ] = NewOwned< T_ArgumentNode >(
					*this , std::move( value ) );
		}
	}

	bool hasParameter( const E_Parameter p ) const noexcept
		{ return bool( children_[ int( p ) ] ); }
	T_ArgumentNode& parameter( const E_Parameter p ) const noexcept
		{ return (T_ArgumentNode&) *children_[ int( p ) ]; }
};

// Image unit binding
class T_ImageInstrNode : public A_InstructionNode
{
    public:
	enum class E_AccessMode
	{
		READ ,
		WRITE
	};
	using T_AccessMode = T_Flags< E_AccessMode >;

	enum E_Parameter {
		P_UNIT , P_LEVEL , P_LAYER
	};

    private:
	T_AccessMode accessMode_ = { };
	T_String id_;
	ebcl::T_SRDLocation idLocation_;

    public:
	T_ImageInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_IMAGE , parent )
	{
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
		children_.add( P_ArgumentNode{} );
	}

	//----------------------------------------------------------------------

	T_String const& id( ) const noexcept
		{ return id_; }
	ebcl::T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }
	void id( T_String const& nid ,
			ebcl::T_SRDLocation const& location ) noexcept
		{ id_ = nid; idLocation_ = location; }

	//----------------------------------------------------------------------

	T_AccessMode accessMode( ) const noexcept
		{ return accessMode_; }
	void accessMode( T_AccessMode mode ) noexcept
		{ assert( mode ); accessMode_ = mode; }

	//----------------------------------------------------------------------

	void setParameter( const E_Parameter p ,
			P_ExpressionNode value ) noexcept
	{
		if ( value ) {
			children_[ int( p ) ] = NewOwned< T_ArgumentNode >(
					*this , std::move( value ) );
		}
	}

	bool hasParameter( const E_Parameter p ) const noexcept
		{ return bool( children_[ int( p ) ] ); }
	T_ArgumentNode& parameter( const E_Parameter p ) const noexcept
		{ return (T_ArgumentNode&) *children_[ int( p ) ]; }
};


/*= EXPRESSIONS ==============================================================*/

// A constant value
class T_ConstantExprNode : public A_ExpressionNode
{
    private:
	bool wasFloat_;
	double vFloat_;
	int64_t vInt_;

    public:
	T_ConstantExprNode( A_Node& parent ,
			ebcl::T_SRDToken const& token ) noexcept
		: A_ExpressionNode( EXPR_CONST , parent ) ,
			wasFloat_( token.type( ) == ebcl::E_SRDTokenType::FLOAT ) ,
			vFloat_( token.floatValue( ) ) ,
			vInt_( token.longValue( ) )
	{ location( ) = token.location( ); }

	T_ConstantExprNode( A_Node& parent ,
			int64_t value ) noexcept
		: A_ExpressionNode( EXPR_CONST , parent ) , wasFloat_( false ) ,
			vFloat_( value ) , vInt_( value )
	{ }

	T_ConstantExprNode( A_Node& parent ,
			double value ) noexcept
		 : A_ExpressionNode( EXPR_CONST , parent ) , wasFloat_( true ) ,
			vFloat_( value ) , vInt_( (int64_t) value )
	{ }

	bool wasFloat( ) const noexcept
		{ return wasFloat_; }
	double floatValue( ) const noexcept
		{ return vFloat_; }
	int64_t intValue( ) const noexcept
		{ return vInt_; }
};

// An identifier used in an expression
class T_IdentifierExprNode : public A_ExpressionNode
{
    private:
	T_String id_;

    public:
	T_IdentifierExprNode( A_Node& parent ,
			ebcl::T_SRDToken const& token ) noexcept
		: T_IdentifierExprNode( parent , token.stringValue( ) )
	{ location( ) = token.location( ); }

	T_IdentifierExprNode( A_Node& parent ,
			T_String const& id ) noexcept
		 : A_ExpressionNode( EXPR_ID , parent ) , id_( id )
	{ }

	T_String const& id( ) const noexcept
		{ return id_; }
};

// Access to an input value
class T_InputExprNode : public A_ExpressionNode
{
    private:
	T_String id_;
	ebcl::T_SRDLocation idLocation_;

    public:
	T_InputExprNode( A_Node& parent ,
			ebcl::T_SRDToken const& token ) noexcept
		 : A_ExpressionNode( EXPR_INPUT , parent ) ,
			id_( token.stringValue( ) ) ,
			idLocation_( token.location( ) )
	{ }

	T_String const& id( ) const noexcept
		{ return id_; }
	ebcl::T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }
};

// A unary operator
class T_UnaryOperatorNode : public A_ExpressionNode
{
    public:
	enum E_Operator {
		NEG , INV , NOT ,
		SIN , COS , TAN ,
		SQRT , EXP , LN
	};

    private:
	E_Operator op_;

    public:
	T_UnaryOperatorNode(
			A_Node& parent ,
			const E_Operator op ) noexcept;

	E_Operator op( ) const noexcept
		{ return op_; }

	void setArgument( P_ExpressionNode argument ) noexcept
		{ child( 0 ) = std::move( argument ); }

	bool hasArgument( ) const noexcept
		{ return bool( child( 0 ) ); }

	A_ExpressionNode& argument( ) const noexcept
		{ return (A_ExpressionNode&) *child( 0 ); }
};

// A binary operator
class T_BinaryOperatorNode : public A_ExpressionNode
{
    public:
	enum E_Operator {
		ADD , SUB ,
		MUL , DIV ,
		POW ,

		CMP_EQ , CMP_NE ,
		CMP_GT , CMP_GE ,
		CMP_LT , CMP_LE ,
	};

    private:
	E_Operator op_;

    public:
	T_BinaryOperatorNode(
			A_Node& parent ,
			const E_Operator op ) noexcept;

	E_Operator op( ) const noexcept
		{ return op_; }

	void setLeft( P_ExpressionNode left ) noexcept
		{ child( 0 ) = std::move( left ); }
	void setRight( P_ExpressionNode right ) noexcept
		{ child( 1 ) = std::move( right ); }

	bool hasLeft( ) const noexcept
		{ return bool( child( 0 ) ); }
	bool hasRight( ) const noexcept
		{ return bool( child( 1 ) ); }

	A_ExpressionNode& left( ) const noexcept
		{ return (A_ExpressionNode&) *child( 0 ); }
	A_ExpressionNode& right( ) const noexcept
		{ return (A_ExpressionNode&) *child( 1 ); }
};


} // namespace opast