#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

#include "GlInclude.h"


namespace lib_gl
{


// ----------------------------------------------------------------------------------------------------------------
// GlslShader

class GlslShader
{
public:
	GlslShader(void)
		: m_ShaderID(0)
		, m_ShaderType(0)
	{}

	virtual ~GlslShader(void)
	{
		Release();
	}

	//! シェーダを文字列のソースコードか、ソースファイルからコンパイルする. 
	//! @param ShaderType - 生成するシェーダに応じて GL_VERTEX_SHADER , GL_FRAGMENT_SHADER のどちらかを指定
	//! @retval 成功、失敗を返す
	bool CreateFromSrc ( GLenum ShaderType , const char* ShaderSrc , int ShaderSrcLength );
	bool CreateFromFile( GLenum ShaderType , const std::string& FileName );

	// シェーダを解放する
	void Release(void);
	// 最も最後にコンパイルしたときのコンパイル情報を管理するバッファをクリア.
	void ClearLastCompileStatus(void);

	bool               IsInitialized        (void) const { return ( m_ShaderID != 0 ); }
	GLuint             GetShaderID          (void) const { return m_ShaderID;          }
	GLenum             GetShaderType        (void) const { return m_ShaderType;        }
	const std::string& GetLastCompileStatus (void) const { return m_LastCompileStatus; }

protected:
	void UpdateCompileStatus(void);

private:
	GlslShader(const GlslShader&) {}
	void operator=(const GlslShader&) {}

protected:
	GLuint m_ShaderID;
	GLenum m_ShaderType;

	std::string m_LastCompileStatus;
};


// ----------------------------------------------------------------------------------------------------------------
// GlslProgram

class GlslProgram
{
public:
	GlslProgram(void)
		: m_ProgramID(0) 
	{}

	virtual ~GlslProgram(void)
	{
		Release();
	}

	//! シェーダプログラムのオブジェクトを生成する.
	bool CreateProgram(void);

	//! シェーダからシェーダプログラムを生成する.
	bool Create( const GlslShader& VertShader , const GlslShader& FragShader );
	bool Create( const GlslShader& VertShader , const GlslShader& FragShader , const GlslShader& GeomShader );

	bool AttachShader( const GlslShader& shader );
	bool AttachShaders( const GlslShader& VertShader , const GlslShader& FragShader );
	bool AttachShaders( const GlslShader& VertShader , const GlslShader& FragShader , const GlslShader& GeomShader );

	bool Link(void);

	//! シェーダプログラムを解放する.
	void Release(void);
	//! 最も最後にシェーダを生成した結果の情報を管理するバッファをクリアする.
	void ClearLastLinkStatus(void);

	//! シェーダの開始と終了.
	void BeginShader(void);
	void EndShader(void);

	bool               IsInitialized     (void) const { return ( m_ProgramID != 0 ); }
	GLuint             GetID             (void) const { return m_ProgramID;          }
	const std::string& GetLastLinkStatus (void) const { return m_LastLinkStatus;     }

	GLint GetUniformLocation(const GLchar* name) const { return glGetUniformLocation( GetID() , name ); }
	GLint GetAttribLocation(const GLchar* name) const { return glGetAttribLocation( GetID() , name ); }

protected:
	void UpdateLinkStatus(void);


protected:
	GLuint m_ProgramID;

	std::string m_LastLinkStatus;
};



// implements

// ----------------------------------------------------------------------------------------------------------------
// GlslShader

inline
bool GlslShader::CreateFromSrc( GLenum ShaderType , const char* ShaderSrc , int ShaderSrcLength )
{
	Release();
	ClearLastCompileStatus();

	m_ShaderID = glCreateShader(ShaderType);
	if( m_ShaderID == 0 )
		return false;

	GLint compile_status;
	glShaderSource( m_ShaderID , 1 , &ShaderSrc , &ShaderSrcLength );
	glCompileShader( m_ShaderID );
	glGetShaderiv( m_ShaderID , GL_COMPILE_STATUS , &compile_status );

	UpdateCompileStatus();

	if( compile_status == GL_FALSE )
	{
		Release();
		return false;
	}

	return true;
}

inline
bool GlslShader::CreateFromFile( GLenum ShaderType , const std::string& FileName )
{
	Release();

	std::vector<char> buf;
	{
		std::ifstream ifs( FileName.c_str() , std::ios::binary );
		if( !ifs.is_open() )
			return false;

		std::fstream::pos_type pos_beg , pos_end;
		ifs.seekg( 0 , std::ios::end );
		pos_end = ifs.tellg();
		ifs.seekg( 0 , std::ios::beg );
		pos_beg = ifs.tellg();

		size_t filesize = pos_end - pos_beg;
		buf.resize( filesize );
		ifs.read( &buf[0] , filesize );
	}

	return CreateFromSrc( ShaderType , &buf[0] , static_cast<int>( buf.size() ) );
}

inline
void GlslShader::UpdateCompileStatus(void)
{
	m_LastCompileStatus.clear();

	GLsizei bufSize;
	glGetShaderiv( m_ShaderID , GL_INFO_LOG_LENGTH , &bufSize );
	if (bufSize > 1)
	{
		m_LastCompileStatus.resize( bufSize );
		GLsizei length;
		glGetShaderInfoLog( m_ShaderID , bufSize , &length , &m_LastCompileStatus.at(0) );
	}
}


inline
void GlslShader::Release(void)
{
	if( m_ShaderID != 0 )
	{
		glDeleteShader( m_ShaderID );
		m_ShaderID = 0;
	}
}

inline
void GlslShader::ClearLastCompileStatus(void)
{
	m_LastCompileStatus.clear();
}



// ----------------------------------------------------------------------------------------------------------------
// GlslProgram

inline
bool GlslProgram::CreateProgram(void)
{
	Release();
	ClearLastLinkStatus();

	m_ProgramID = glCreateProgram();
	if( m_ProgramID == 0 )
		return false;

	return true;
}

inline
bool GlslProgram::Create( const GlslShader& VertShader , const GlslShader& FragShader )
{
	if( !CreateProgram() )
		return false;

	if( !AttachShaders( VertShader , FragShader ) )
		return false;

	return Link();
}

inline
bool GlslProgram::Create( const GlslShader& VertShader , const GlslShader& FragShader , const GlslShader& GeomShader )
{
	if( !CreateProgram() )
		return false;

	if( !AttachShaders( VertShader , FragShader , GeomShader ) )
		return false;

	return Link();
}

inline
bool GlslProgram::AttachShader( const GlslShader& shader )
{
	if( !shader.IsInitialized() )
		return false;

	glAttachShader( m_ProgramID , shader.GetShaderID() );

	return true;
}

inline
bool GlslProgram::AttachShaders( const GlslShader& VertShader , const GlslShader& FragShader )
{
	if( !AttachShader( VertShader ) )
		return false;

	if( !AttachShader( FragShader ) )
		return false;

	return true;
}

inline
bool GlslProgram::AttachShaders( const GlslShader& VertShader , const GlslShader& FragShader , const GlslShader& GeomShader )
{
	if( !AttachShader( VertShader ) )
		return false;

	if( !AttachShader( FragShader ) )
		return false;

	if( !AttachShader( GeomShader ) )
		return false;

	return true;
}

inline
bool GlslProgram::Link(void)
{
	ClearLastLinkStatus();
	if( m_ProgramID == 0 )
		return false;

	glLinkProgram( m_ProgramID );
	GLint link_status;
	glGetProgramiv( m_ProgramID , GL_LINK_STATUS , &link_status );

	UpdateLinkStatus();

	if( link_status == GL_FALSE )
	{
		Release();
		return false;
	}

	return true;
}

inline
void GlslProgram::UpdateLinkStatus(void)
{
	m_LastLinkStatus.clear();

	GLsizei bufSize;
	glGetProgramiv( m_ProgramID , GL_INFO_LOG_LENGTH , &bufSize );

	if (bufSize > 1)
	{
		m_LastLinkStatus.resize( bufSize );
		GLsizei length;
		glGetProgramInfoLog( m_ProgramID , bufSize , &length , &m_LastLinkStatus.at(0) );
	}
}


inline
void GlslProgram::Release(void)
{
	if( m_ProgramID != 0 )
	{
		glDeleteProgram( m_ProgramID );
		m_ProgramID = 0;
	}
}

inline
void GlslProgram::ClearLastLinkStatus(void)
{
	m_LastLinkStatus.clear();
}

inline
void GlslProgram::BeginShader(void)
{
	if( m_ProgramID != 0 )
		glUseProgram( m_ProgramID );
}

inline
void GlslProgram::EndShader(void)
{
	glUseProgram(0);
}


}
