#include "externals.hh"
#include "utilities.hh"
#include "texture.hh"


/*==============================================================================*/

T_Texture::T_Texture(
		__rd__ const uint32_t width ,
		__rd__ const uint32_t height ,
		__rd__ const E_TexType type ,
		__rd__ const uint32_t levels )
	: levels_( levels ) , width_( width ) , height_( height )
{
	assert( levels > 0 );

	glGenTextures( 1 , &id_ );
	glBindTexture( GL_TEXTURE_2D , id_ );

	GLenum ifmt , fmt , dt;
	switch ( type ) {
		case E_TexType::RGBA8:
			ifmt = GL_RGBA8;
			fmt = GL_RGBA;
			dt = GL_UNSIGNED_BYTE;
			break;

		case E_TexType::RGBA16F:
			ifmt = GL_RGBA16F;
			fmt = GL_RGBA;
			dt = GL_FLOAT;
			break;

		case E_TexType::RGB8:
			ifmt = GL_RGB8;
			fmt = GL_RGB;
			dt = GL_UNSIGNED_BYTE;
			break;

		case E_TexType::RGB16F:
			ifmt = GL_RGB16F;
			fmt = GL_RGB;
			dt = GL_FLOAT;
			break;

		case E_TexType::R8:
			ifmt = GL_R8;
			fmt = GL_RED;
			dt = GL_UNSIGNED_BYTE;
			break;

		case E_TexType::R16F:
			ifmt = GL_R16F;
			fmt = GL_RED;
			dt = GL_FLOAT;
			break;
	}

	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_BASE_LEVEL , 0 );
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MAX_LEVEL , levels - 1 );
	glTexParameterf( GL_TEXTURE_2D , GL_TEXTURE_MIN_LOD , 0 );
	glTexParameterf( GL_TEXTURE_2D , GL_TEXTURE_MAX_LOD , levels - 1 );
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_NEAREST );

	uint32_t w = width , h = height;
	for ( auto i = 0u ; i < levels ; i ++ ) {
#ifdef INTRUSIVE_TRACES
		printf( "init %p txid %d lv %d sz %dx%d\n" , this , id_ ,
				i , w , h );
#endif
		glTexImage2D( GL_TEXTURE_2D , i , ifmt , w , h , 0 , fmt , dt , nullptr );
		w >>= 1;
		h >>= 1;
		assert( w && h );
	}

	GL_ASSERT( );
}

T_Texture::~T_Texture( )
{
	glDeleteTextures( 1 , &id_ );
}


T_Texture& T_Texture::samplingMode(
		__rd__ const E_TexSampling mode )
{
	glBindTexture( GL_TEXTURE_2D , id_ );

	GLenum min , max;
	switch ( mode ) {
		case E_TexSampling::NEAREST:
			min = levels_ > 1 ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST;
			max = GL_NEAREST;
			break;
		case E_TexSampling::LINEAR:
			min = levels_ > 1 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR;
			max = GL_LINEAR;
			break;

	}
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , min );
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , max );
	return *this;
}

T_Texture& T_Texture::wrap(
		__rd__ const E_TexWrap mode )
{
	GLenum gm;
	switch ( mode ) {
		case E_TexWrap::REPEAT:
			gm = GL_REPEAT;
			break;
		case E_TexWrap::CLAMP_EDGE:
			gm = GL_CLAMP_TO_EDGE;
			break;
		case E_TexWrap::CLAMP_BORDER:
			gm = GL_CLAMP_TO_BORDER;
			break;
	}
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , gm );
	glTexParameteri( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , gm );
	return *this;
}

/*==============================================================================*/

T_TextureSampler::T_TextureSampler( )
{
	glGenSamplers( 1 , &id_ );
	GL_ASSERT( );
}

T_TextureSampler::T_TextureSampler(
		__rw__ T_TextureSampler&& other ) noexcept
{
	std::swap( id_ , other.id_ );
	std::swap( sampling_ , other.sampling_ );
	std::swap( lodSampling_ , other.lodSampling_ );
	std::swap( hasLOD_ , other.hasLOD_ );
	setSamplingMode( );
}

T_TextureSampler& T_TextureSampler::operator =(
		__rw__ T_TextureSampler&& other ) noexcept
{
	std::swap( id_ , other.id_ );
	std::swap( sampling_ , other.sampling_ );
	std::swap( lodSampling_ , other.lodSampling_ );
	std::swap( hasLOD_ , other.hasLOD_ );
	return *this;
}

T_TextureSampler::~T_TextureSampler( )
{
	if ( id_ != 0 ) {
		glDeleteSamplers( 1 , &id_ );
	}
}

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

T_TextureSampler& T_TextureSampler::sampling(
		__rd__ const E_TexSampling mode )
{
	sampling_ = mode;
	setSamplingMode( );
	return *this;
}

T_TextureSampler& T_TextureSampler::noMipmap( )
{
	hasLOD_ = false;
	setSamplingMode( );
	return *this;
}

T_TextureSampler& T_TextureSampler::mipmap(
		__rd__ const E_TexSampling mode )
{
	hasLOD_ = true;
	lodSampling_ = mode;
	setSamplingMode( );
	return *this;
}

T_TextureSampler& T_TextureSampler::wrap(
		__rd__ const E_TexWrap mode )
{
	GLenum gm;
	switch ( mode ) {
		case E_TexWrap::REPEAT:
			gm = GL_REPEAT;
			break;
		case E_TexWrap::CLAMP_EDGE:
			gm = GL_CLAMP_TO_EDGE;
			break;
		case E_TexWrap::CLAMP_BORDER:
			gm = GL_CLAMP_TO_BORDER;
			break;
	}
	glSamplerParameteri( id_ , GL_TEXTURE_WRAP_S , gm );
	glSamplerParameteri( id_ , GL_TEXTURE_WRAP_T , gm );
	GL_ASSERT( );
	return *this;
}

T_TextureSampler& T_TextureSampler::lod(
		__rd__ const float min ,
		__rd__ const float max )
{
	glSamplerParameterf( id_ , GL_TEXTURE_MIN_LOD , min );
	glSamplerParameterf( id_ , GL_TEXTURE_MAX_LOD , max );
	GL_ASSERT( );
	return *this;
}

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

void T_TextureSampler::setSamplingMode( ) const
{
	GLenum min , max;
	if ( hasLOD_ ) {
		switch ( sampling_ ) {

			case E_TexSampling::NEAREST:
				min = lodSampling_ == E_TexSampling::LINEAR
					? GL_NEAREST_MIPMAP_LINEAR
					: GL_NEAREST_MIPMAP_NEAREST;
				max = GL_NEAREST;
				break;

			case E_TexSampling::LINEAR:
				min = lodSampling_ == E_TexSampling::LINEAR
					? GL_LINEAR_MIPMAP_LINEAR
					: GL_LINEAR_MIPMAP_NEAREST;
				max = GL_LINEAR;
				break;

		}
	} else {
		min = ( sampling_ == E_TexSampling::LINEAR )
			? GL_LINEAR
			: GL_NEAREST;
		max = min;
	}

	glSamplerParameteri( id_ , GL_TEXTURE_MIN_FILTER , min );
	glSamplerParameteri( id_ , GL_TEXTURE_MAG_FILTER , max );
	GL_ASSERT( );
}


/*============================================================================*/

constexpr uint32_t T_TextureManager::MaxUnits;


T_TextureManager::T_TextureManager( )
{
	std::shared_ptr< T_TextureSampler > tsam;

	tsam = std::make_shared< T_TextureSampler >( );
	tsam->wrap( E_TexWrap::CLAMP_EDGE ).sampling( E_TexSampling::NEAREST );
	samplers_[ "nearest-edge" ] = tsam;

	tsam = std::make_shared< T_TextureSampler >( );
	tsam->wrap( E_TexWrap::CLAMP_EDGE ).sampling( E_TexSampling::LINEAR );
	samplers_[ "linear-edge" ] = tsam;

	tsam = std::make_shared< T_TextureSampler >( );
	tsam->wrap( E_TexWrap::CLAMP_BORDER ).sampling( E_TexSampling::NEAREST );
	samplers_[ "nearest-border" ] = tsam;

	tsam = std::make_shared< T_TextureSampler >( );
	tsam->wrap( E_TexWrap::CLAMP_BORDER ).sampling( E_TexSampling::LINEAR );
	samplers_[ "linear-border" ] = tsam;
}

T_TextureSampler const* T_TextureManager::sampler(
		__rd__ std::string const& name ) const
{
	auto pos( samplers_.find( name ) );
	if ( pos == samplers_.end( ) ) {
		return nullptr;
	}
	return (*pos).second.get( );
}

void T_TextureManager::bind(
		__rd__ const uint32_t unit ,
		__rd__ T_Texture const& texture )
{
	assert( unit < MaxUnits );
	auto& u( bindings_[ unit ] );
	if ( u.texture == texture.id( ) && !u.sampler ) {
		return;
	}
	u.texture = texture.id( );
	u.sampler = 0;
	glBindTextureUnit( unit , texture.id( ) );
	glBindSampler( unit , 0 );
}

void T_TextureManager::bind(
		__rd__ const uint32_t unit ,
		__rd__ T_Texture const& texture ,
		__rd__ T_TextureSampler const& sampler )
{
	assert( unit < MaxUnits );
	auto& u( bindings_[ unit ] );
	if ( u.texture == texture.id( ) && u.sampler == sampler.id( ) ) {
		return;
	}
	u.texture = texture.id( );
	u.sampler = sampler.id( );
	glBindTextureUnit( unit , texture.id( ) );
	glBindSampler( unit , sampler.id( ) );
}

void T_TextureManager::reset( )
{
	for ( auto i = 0u ; i < MaxUnits ; i ++ ) {
		auto& u( bindings_[ i ] );
		u.texture = 0;
		u.sampler = 0;
		glBindTextureUnit( i , 0 );
		glBindSampler( i , 0 );
	}
}