#pragma once
#include "odbg.hh"
#include <ebcl/SRDData.hh>
#include <ebcl/Sets.hh>


namespace opast {

using namespace ebcl;


/*= 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_COND ,	// Conditional instruction
		OP_FRAMEBUFFER ,// Define framebuffer
		OP_FULLSCREEN ,	// Draw a fullscreen quad
		OP_INPUT ,	// Input declaration
		OP_LOCALS ,	// Declare local variables
		OP_MAINOUT ,	// Select the output buffer
		OP_ODBG ,	// Output debugging
		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* const parent_;
	T_SRDLocation location_;

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

    public:
	virtual ~A_Node( ) = 0;

	E_Type type( ) const noexcept
		{ return type_; }

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

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

	T_RootNode& root( ) const noexcept;
};

// 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
{
    private:
	T_Array< T_OwnPtr< A_InstructionNode > > instructions_;

    public:
	T_InstrListNode( A_Node& parent ) noexcept
		: A_Node( ILIST , &parent ) { }

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

	uint32_t size( ) const noexcept
		{ return instructions_.size( ); }
	A_InstructionNode& node( const uint32_t index ) const noexcept
		{ return *instructions_[ index ]; }
};

template<
	typename IType ,
	typename... ArgTypes
> inline IType& T_InstrListNode::add(
		ArgTypes&&... args )
{
	instructions_.add( NewOwned< IType >( *this ,
			std::forward< ArgTypes >( args ) ... ) );
	return (IType&) *instructions_.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
{
    private:
	P_ExpressionNode expr_;

    public:
	T_ArgumentNode( A_Node& parent ,
			P_ExpressionNode expr ) noexcept
		: A_Node( TN_ARG , &parent ) ,
			expr_( std::move( expr ) )
	{
		if ( expr_ ) {
			location( ) = expr_->location( );
		}
	}

	A_ExpressionNode& expression( ) const noexcept
		{ return *expr_; }
	bool isIdentifier( ) const noexcept
		{ return expr_->type( ) == EXPR_ID; }
};
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_;

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

		T_Local_( T_String const& name ,
				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
		{ return instructions_; }
	T_InstrListNode const& instructions( ) const noexcept
		{ return instructions_; }

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

	T_Optional< T_SRDLocation > addLocalVariable(
			T_String const& name ,
			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; }

	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; }
};
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_ObjectTable< T_String , T_OwnPtr< A_FuncNode > > 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< T_SRDLocation > dupLocation;

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

		T_AddFunctionResult( A_FuncNode& function ,
				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
		{ return functions_.indexOf( name ); }
	A_FuncNode& function( const uint32_t index ) const noexcept
		{ return *functions_.values( )[ index ]; }
};

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

// 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< T_SRDLocation > addArgument(
			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_;
	T_SRDLocation idLocation_;
	T_AutoArray< P_ArgumentNode , 8 > arguments_;

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

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

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

	uint32_t arguments( ) const noexcept
		{ return arguments_.size( ); }
	T_ArgumentNode& argument( const uint32_t index ) const noexcept
		{ return *arguments_[ index ]; }
};

// Conditional instruction
class T_CondInstrNode : public A_InstructionNode
{
    public:
	class T_Expression : public A_Node
	{
	    private:
		P_ExpressionNode expression_;

	    public:
		T_Expression( T_CondInstrNode& parent ,
				P_ExpressionNode expr )
			: A_Node( TN_CONDITION , &parent ) ,
				expression_( std::move( expr ) )
		{ }

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

	class T_ValuedCase : public A_Node
	{
	    private:
		int64_t value_;
		P_InstrListNode instructions_;

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

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

	class T_DefaultCase : public A_Node
	{
	    private:
		P_InstrListNode instructions_;

	    public:
		T_DefaultCase( T_CondInstrNode& parent ,
				P_InstrListNode il )
			: A_Node( TN_DEFAULT , &parent ) ,
				instructions_( std::move( il ) )
		{ }

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

    private:
	T_OwnPtr< T_Expression > expression_;
	T_ObjectTable< int64_t , T_OwnPtr< T_ValuedCase > > cases_{
		[]( T_OwnPtr< T_ValuedCase > const& c ) -> int64_t {
			return c->value( );
		}
	};
	T_OwnPtr< T_DefaultCase > defaultCase_;

    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 *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 > 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 **cases_.get( value ); }
	T_ValuedCase& getCaseByIndex(
			const uint32_t index ) const noexcept
		{ return *cases_[ index ]; }

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

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

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

	T_Optional< T_SRDLocation > addVariable(
			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 ]; }

	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_;
	T_SRDLocation idLocation_;
	P_ExpressionNode expression_;

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

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

	void setExpression( P_ExpressionNode expression ) noexcept
		{ expression_ = std::move( expression ); }
	bool hasExpression( ) const noexcept
		{ return bool( expression_ ); }
	A_ExpressionNode& expression( ) const noexcept
		{ return *expression_; }
};


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

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

    protected:
	A_ResourceDefInstrNode(
			const E_Type type ,
			T_InstrListNode& parent ,
			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_; }
	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_;
		P_ArgumentNode lod_;

	    public:
		T_Attachment( T_FramebufferInstrNode& parent ,
				const bool depth ,
				T_SRDToken const& texId ,
				P_ExpressionNode lod = { } ) noexcept
			: A_Node( TN_FBATT , &parent ) ,
				depth_( depth ) ,
				id_( texId.stringValue( ) )
		{
			location() = texId.location( );
			if ( lod ) {
				lod_ = 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 lod_.get( ); }
	};

    private:
	T_AutoArray< T_OwnPtr< T_Attachment > , 16 > colorAttachments_;
	T_OwnPtr< T_Attachment > depthAttachment_;

	bool hasAttachment( T_String const& name ) const noexcept;

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

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

	bool addColorAttachment( 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 *colorAttachments_[ index ]; }

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

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

	T_Attachment* depthAttachment( ) const noexcept
		{ return depthAttachment_.get( ); }
};

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

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

	T_InputInstrNode( T_InstrListNode& parent ,
			T_SRDToken const& tName ,
			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_; }
	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< T_SRDLocation , 6 > pidLocations_;

    public:
	T_PipelineInstrNode(
			T_InstrListNode& parent ,
			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< T_SRDLocation > addProgram(
			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 ]; }
	T_SRDLocation const& pLocation( const uint32_t index ) const noexcept
		{ return pidLocations_[ index ]; }
};

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

    public:
	T_ProgramInstrNode(
			T_InstrListNode& parent ,
			T_SRDToken const& idToken ,
			T_SRDToken const& pathToken ) noexcept
		: A_ResourceDefInstrNode( OP_PROGRAM , parent ,
				idToken , E_DataType::PROGRAM ) ,
			path_( pathToken.stringValue( ) ) ,
			pathLocation_( pathToken.location( ) )
	{ }

	T_String const& path( ) const noexcept
		{ return path_; }
	T_SRDLocation const& pathLocation( ) const noexcept
		{ return pathLocation_; }
};

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

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

	struct T_Wrapping_
	{
		T_SRDLocation location;
		E_TexWrap mode;
	};

	struct T_LOD_
	{
		T_SRDLocation location;
		P_ArgumentNode min;
		P_ArgumentNode 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 ,
			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< T_SRDLocation > setSampling(
			E_TexSampling mode ,
			T_SRDLocation const& location ) noexcept;
	T_Optional< T_SRDLocation > setMipmapSampling(
			E_TexSampling mode ,
			T_SRDLocation const& location ) noexcept;
	T_Optional< T_SRDLocation > setNoMipmap(
			T_SRDLocation const& location ) noexcept;
	T_Optional< T_SRDLocation > setWrapping(
			E_TexWrap mode ,
			T_SRDLocation const& location ) noexcept;
	T_Optional< T_SRDLocation > setLOD(
			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 lod_ ? lod_->min.get( ) : nullptr; }
	T_ArgumentNode* maxLod( ) const noexcept
		{ return lod_ ? lod_->max.get( ) : nullptr; }
};

// Texture definition
class T_TextureInstrNode : public A_ResourceDefInstrNode
{
    private:
	E_TexType type_;
	P_ArgumentNode width_;
	P_ArgumentNode height_;
	P_ArgumentNode lods_;

    public:
	T_TextureInstrNode(
			T_InstrListNode& parent ,
			T_SRDToken const& id ,
			E_TexType type ) noexcept
		: A_ResourceDefInstrNode( OP_TEXTURE , parent ,
				id , E_DataType::TEXTURE ) ,
			type_( type )
	{ }

	E_TexType texType( ) const noexcept
		{ return type_; }

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

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

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

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

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

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


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

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

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

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

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

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

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

    public:
	T_ProfileInstrNode(
			T_InstrListNode& parent ,
			T_String const& text ) noexcept
		: A_InstructionNode( OP_PROFILE , parent , E_InstrRestriction::INIT ) ,
			text_( text ) ,
			instructions_( *this )
	{ }

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

	T_InstrListNode& instructions( ) noexcept
		{ return instructions_; }
	T_InstrListNode const& instructions( ) const noexcept
		{ return instructions_; }
};


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

// Clear instruction
class T_ClearInstrNode : public A_InstructionNode
{
    private:
	T_StaticArray< P_ArgumentNode , 4 > components_;

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

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

	uint32_t components( ) const noexcept
		{ return components_.size( ); }
	T_ArgumentNode& component( const uint32_t index ) const noexcept
		{ return *components_[ 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 )
	{ }
};

// 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_;
	T_SRDLocation progIdLocation_;
	uint32_t uloc_;
	T_SRDLocation ulocLocation_;
	T_StaticArray< P_ArgumentNode , 4 > values_;

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

	bool integers( ) const noexcept
		{ return integers_; }

	T_String const& progId( ) const noexcept
		{ return progId_; }
	T_SRDLocation const& progIdLocation( ) const noexcept
		{ return progIdLocation_; }

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

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

	uint32_t values( ) const noexcept
		{ return values_.size( ); }
	T_ArgumentNode& value( const uint32_t index ) const noexcept
		{ return *values_[ 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_;
	T_SRDLocation idLocation_;

    public:
	T_UseInstrNode( T_InstrListNode& parent ,
			const E_Type type ,
			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_; }
	T_SRDLocation const& idLocation( ) const noexcept
		{ return idLocation_; }
};

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

    public:
	T_UseTextureInstrNode( T_InstrListNode& parent ,
			T_SRDToken const& bank ,
			T_SRDToken const& identifier ,
			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_; }
	T_SRDLocation const& bankLocation( ) const noexcept
		{ return bankLocation_; }

	T_String const& samplerId( ) const noexcept
		{ return samplerId_; }
	T_SRDLocation const& samplerIdLocation( ) const noexcept
		{ return samplerIdLocation_; }
};

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

    private:
	P_ArgumentNode parameters_[ 4 ];

    public:
	T_ViewportInstrNode( T_InstrListNode& parent ) noexcept
		: A_InstructionNode( OP_VIEWPORT , parent ,
				E_InstrRestriction::INIT )
	{ }

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

	bool hasParameter( const E_Parameter p ) const noexcept
		{ return bool( parameters_[ int( p ) ] ); }
	T_ArgumentNode& parameter( const E_Parameter p ) const noexcept
		{ return *parameters_[ 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 ,
			T_SRDToken const& token ) noexcept
		: A_ExpressionNode( EXPR_CONST , parent ) ,
			wasFloat_( token.type( ) == 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 ,
			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_;
	T_SRDLocation idLocation_;

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

	T_String const& id( ) const noexcept
		{ return id_; }
	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_;
	P_ExpressionNode argument_;

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

	E_Operator op( ) const noexcept
		{ return op_; }

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

	bool hasArgument( ) const noexcept
		{ return bool( argument_ ); }

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

// 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_;
	P_ExpressionNode left_;
	P_ExpressionNode right_;

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

	E_Operator op( ) const noexcept
		{ return op_; }

	void setLeft( P_ExpressionNode left ) noexcept
		{ left_ = std::move( left ); }
	void setRight( P_ExpressionNode right ) noexcept
		{ right_ = std::move( right ); }

	bool hasLeft( ) const noexcept
		{ return bool( left_ ); }
	bool hasRight( ) const noexcept
		{ return bool( right_ ); }

	A_ExpressionNode const& left( ) const noexcept
		{ return *left_; }
	A_ExpressionNode& left( ) noexcept
		{ return *left_; }

	A_ExpressionNode const& right( ) const noexcept
		{ return *right_; }
	A_ExpressionNode& right( ) noexcept
		{ return *right_; }
};


/*= PARSER ===================================================================*/

// Parser output. Consists in a root node as well as other details (table of
// constants, data types, etc...)
struct T_ParserOutput
{
	T_RootNode root;
	T_KeyValueTable< T_String , E_DataType > types;
};

// The actual parser
class T_Parser : public A_PrivateImplementation
{
    private:
	T_Array< T_SRDError > errors_;
	T_OwnPtr< T_ParserOutput > output_;

    public:
	T_Parser( ) noexcept;

	bool parse( T_SRDList const& input ) noexcept;

	T_Array< T_SRDError > const& errors( ) const noexcept
		{ return errors_; }
	T_OwnPtr< T_ParserOutput > result( ) noexcept
		{ return std::move( output_ ); }
};


} // namespace opast