#pragma once
#include "c-shaders.hh"
#include "c-filewatcher.hh"


struct T_ShaderManager;

struct T_ShaderProgram
{
	friend struct T_ShaderManager;

	T_ShaderProgram( );
	COPY( T_ShaderProgram );
	MOVE( T_ShaderProgram );
	~T_ShaderProgram( );

	bool valid( ) const noexcept;
	void enable( ) const;
	GLuint id( ) const;
	T_Optional< E_ShaderType > type( ) const;
	T_FSPath name( ) const;

    private:
	explicit T_ShaderProgram( uint32_t id ) noexcept;
	uint32_t id_;
};

struct T_ShaderPipeline
{
	friend struct T_ShaderManager;

	T_ShaderPipeline( );
	COPY( T_ShaderPipeline );
	MOVE( T_ShaderPipeline );
	~T_ShaderPipeline( );

	bool valid( ) const noexcept;
	void enable( ) const;
	GLuint id( ) const;

	GLuint program( const E_ShaderType program ) const;

    private:
	explicit T_ShaderPipeline( T_String id ) noexcept;
	T_String id_;
};


struct T_ShaderManager
{
	friend struct T_ShaderPipeline;
	friend struct T_ShaderProgram;

	struct T_ShaderCode
	{
		E_ShaderType type;
		T_AutoArray< uint32_t , 8 > starts;	// Position of chunk in source file
		T_AutoArray< uint32_t , 8 > counts;	// Chunk lengths
		T_AutoArray< T_FSPath , 8 > sources;	// Chunk source files
		T_StringBuilder code;
		T_Array< T_ShaderError > errors;
		T_KeyValueTable< T_FSPath , bool > files;
	};

	T_ShaderManager( ) noexcept;

	// Build / get a program based on its name
	T_ShaderProgram program( T_FSPath const& name );
	// Build a program from its source code
	T_ShaderProgram program( T_String const& name ,
			E_ShaderType type ,
			char const* source );

	T_ShaderPipeline pipeline(
			std::initializer_list< T_String > shaders );
	T_ShaderPipeline pipeline(
			T_String const* shaderNames ,
			uint32_t count );

	void update( );

	void makeUI( );
	bool& uiEnabled( )
		{ return uiEnabled_; }

	T_ShaderPipeline currentPipeline( ) const noexcept
		{ return T_ShaderPipeline{ cPipeline_ }; }

    private:
	struct T_Pipeline_
	{
		T_String idString;
		uint32_t references = 0;
		GLuint id = 0;
		T_Array< T_String > programs{ 6 };

		T_Pipeline_( T_String const& id ,
				std::initializer_list< T_String > shaders )
			: idString{ id } , programs{ shaders }
		{}

		T_Pipeline_( T_String const& id ,
				T_String const* const shaderNames ,
				const uint32_t count ) noexcept
			: idString{ id }
		{
			for ( auto i = 0u ; i < count ; i ++ ) {
				programs.add( shaderNames[ i ] );
			}
		}
	};

	struct T_Program_
	{
		T_FSPath name;
		T_Array< T_String > plReferences;
		uint32_t saReferences{ 0 };
		T_ShaderCode code;
		GLuint id;
		T_Optional< T_WatchedFiles > watch;
	};

	bool uiEnabled_ = false;

	T_ObjectTable< T_FSPath , T_Pipeline_ > pipelines_;

	T_Array< T_Program_ > programs_;
	T_KeyValueTable< T_FSPath , uint32_t > programIndex_;

	T_KeyValueTable< T_FSPath , P_ShaderInput > inputs_;

	T_KeyValueTable< T_FSPath , T_Array< T_FSPath > > missing_;
	T_Array< T_FSPath > updates_;

	T_String cPipeline_{ };

	// Load/use existing program for use with pipelines
	void loadProgram(
			T_String const& pipeline ,
			T_FSPath const& name );
	bool useExistingProgram(
			T_String const& pipeline ,
			T_FSPath const& name );

	// Load/use existing program for standalone use
	void loadProgram( T_FSPath const& name );
	void loadBuiltinProgram(
			T_String const& name ,
			E_ShaderType type ,
			char const* source );
	bool useExistingProgram( T_FSPath const& name );

	// Program management
	T_Program_& initProgramRecord(				// Init management data
			T_FSPath const& record ,
			E_ShaderType type = E_ShaderType::__COUNT__ ,
			char const* source = nullptr );
	uint32_t newProgramRecord( );				// Allocate entry in index

	T_ShaderInput const* getInput(
			T_FSPath const& name );

	void dereferencePipeline( T_String const& id );
	void dereferenceProgram(
			const uint32_t index ,
			T_String const& pipeline );
	void dereferenceProgram(
			const uint32_t index );

	void initPipeline( T_Pipeline_& pipeline ) const;
	void initProgram( T_Program_& program );
	void initBuiltinProgram( T_Program_& program ,
			E_ShaderType type ,
			char const* source );
	void buildProgram( T_Program_& program );

	void parseGLSLError(
			T_ShaderCode& code ,
			char const* errorLine );

	void programUpdated( T_FSPath const& name );
	void resetProgram( T_Program_& program );
};