#include "base.h"
#include <iostream>
#include <string>
#include "Texture.h"

int Texture::m_TextureCounter{ 0 };

Texture::Texture( const std::string& imagePath )
	:m_Id{ }
	,m_Width{ 10.0f }
	,m_Height{ 10.0f }
	,m_CreationOk{ false }
{
	m_TextureCounter++;
	CreateFromImage( imagePath );
}

Texture::Texture( const std::string& text, TTF_Font *pFont, const Color4f& textColor )
	:m_Id{}
	,m_Width{ 10.0f }
	,m_Height{ 10.0f }
	,m_CreationOk{ false }
{
	CreateFromString( text, pFont, textColor );
}

Texture::Texture( const std::string& text, const std::string& fontPath, int ptSize, const Color4f& textColor )
	:m_Id{}
	,m_Width{ 10.0f }
	,m_Height{ 10.0f }
	,m_CreationOk{ false }
{
	CreateFromString( text, fontPath, ptSize, textColor );
}
Texture::Texture( Texture&& other ) noexcept
	:m_Id{ other.m_Id }
	,m_Width{ other.m_Width }
	,m_Height{ other.m_Height }
	,m_CreationOk{ other.m_CreationOk }
{
	other.m_Id = 0;
	other.m_CreationOk = false;
}

Texture& Texture::operator=( Texture&& other ) noexcept
{
	if (this != &other)// no self assignment
	{

		m_Id = other.m_Id;
		m_Width = other.m_Width;
		m_Height = other.m_Height;
		m_CreationOk = other.m_CreationOk;
		other.m_Id = 0;
		other.m_CreationOk = false;
	}
	return *this;
}

Texture::~Texture()
{
	glDeleteTextures( 1, &m_Id );
}

void Texture::CreateFromImage( const std::string& path )
{
	m_CreationOk = true;

	// Load image at specified path
	SDL_Surface* pLoadedSurface = IMG_Load(path.c_str());
	if (pLoadedSurface == nullptr)
	{
		std::cerr << "Texture::CreateFromImage, error when calling IMG_Load: " << SDL_GetError() << std::endl;
		m_CreationOk = false;
		return;
	}
	CreateFromSurface(pLoadedSurface);

	// Free loaded surface
	SDL_FreeSurface(pLoadedSurface);

}

void Texture::CreateFromString( const std::string& text, const std::string& fontPath, int ptSize, const Color4f& textColor )
{
	m_CreationOk = true;

	// Create font
	TTF_Font *pFont{};
	pFont = TTF_OpenFont( fontPath.c_str( ), ptSize );
	if(pFont == nullptr )
	{
		std::cerr << "Texture::CreateFromString, error when calling TTF_OpenFont: " << TTF_GetError( ) << std::endl;
		m_CreationOk = false;
		return;
	}

	// Create texture using this font and close font afterwards
	CreateFromString( text, pFont, textColor );
	TTF_CloseFont( pFont );
}

void Texture::CreateFromString( const std::string& text, TTF_Font *pFont, const Color4f& color )
{
	m_CreationOk = true;
	if ( pFont == nullptr )
	{
		std::cerr << "Texture::CreateFromString, invalid TTF_Font pointer\n" ;
		m_CreationOk = false;
		return;
	}

	// Render text surface
	SDL_Color textColor{};
	textColor.r = Uint8( color.r * 255 );
	textColor.g = Uint8( color.g * 255 );
	textColor.b = Uint8( color.b * 255 );
	textColor.a = Uint8( color.a * 255 );

	SDL_Surface* pLoadedSurface = TTF_RenderText_Blended( pFont, text.c_str( ), textColor );
	if ( pLoadedSurface == nullptr )
	{
		std::cerr << "Texture::CreateFromString, error when calling TTF_RenderText_Blended: " << TTF_GetError( ) << std::endl;
		m_CreationOk = false;
		return;
	}

	// Copy to video memory
	CreateFromSurface( pLoadedSurface );

	// Free loaded surface
	SDL_FreeSurface( pLoadedSurface );
}

void Texture::CreateFromSurface( SDL_Surface* pSurface )
{
	m_CreationOk = true;

	//Get image dimensions
	m_Width = float(pSurface->w);
	m_Height =float( pSurface->h);

	// Get pixel format information and translate to OpenGl format
	GLenum pixelFormat{ GL_RGB };
	switch ( pSurface->format->BytesPerPixel )
	{
	case 3:
		if ( pSurface->format->Rmask == 0x000000ff )
		{
			pixelFormat = GL_RGB;
		}
		else
		{
			pixelFormat = GL_BGR;
		}
		break;
	case 4:
		if ( pSurface->format->Rmask == 0x000000ff )
		{
			pixelFormat = GL_RGBA;
		}
		else
		{
			pixelFormat = GL_BGRA;
		}
		break;
	default:
		std::cerr << "Texture::CreateFromSurface, unknow pixel format, BytesPerPixel: " << pSurface->format->BytesPerPixel << "\nUse 32 bit or 24 bit images.\n";
		m_CreationOk = false;
		return;
	}

	//Generate an array of textures.  We only want one texture (one element array), so trick
	//it by treating "texture" as array of length one.
	glGenTextures(1, &m_Id);

	//Select (bind) the texture we just generated as the current 2D texture OpenGL is using/modifying.
	//All subsequent changes to OpenGL's texturing state for 2D textures will affect this texture.
	glBindTexture(GL_TEXTURE_2D, m_Id);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, pSurface->pitch / pSurface->format->BytesPerPixel);
	// check for errors. Can happen if a texture is created while a static pointer is being initialized, even before the call to the main function.
	GLenum e = glGetError();
	if (e != GL_NO_ERROR)
	{
		std::cerr << "Texture::CreateFromSurface, error binding textures, Error id = " << e << '\n';
		std::cerr << "Can happen if a texture is created before performing the initialization code (e.g. a static Texture object).\n";
		std::cerr << "There might be a white rectangle instead of the image.\n";
	}

	// Specify the texture's data.  
	// This function is a bit tricky, and it's hard to find helpful documentation. 
	// A summary:
	//    GL_TEXTURE_2D:    The currently bound 2D texture (i.e. the one we just made)
	//                0:    The mipmap level.  0, since we want to update the base level mipmap image (i.e., the image itself,
	//                         not cached smaller copies)
	//          GL_RGBA:    Specifies the number of color components in the texture.
	//                     This is how OpenGL will store the texture internally (kinda)--
	//                     It's essentially the texture's type.
	//       surface->w:    The width of the texture
	//       surface->h:    The height of the texture
	//                0:    The border.  Don't worry about this if you're just starting.
	//      pixelFormat:    The format that the *data* is in--NOT the texture! 
	// GL_UNSIGNED_BYTE:    The type the data is in.  In SDL, the data is stored as an array of bytes, with each channel
	//                         getting one byte.  This is fairly typical--it means that the image can store, for each channel,
	//                         any value that fits in one byte (so 0 through 255).  These values are to be interpreted as
	//                         *unsigned* values (since 0x00 should be dark and 0xFF should be bright).
	//  surface->pixels:    The actual data.  As above, SDL's array of bytes.
	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, pSurface->w, pSurface->h, 0, pixelFormat, GL_UNSIGNED_BYTE, pSurface->pixels );

	// Set the minification and magnification filters.  In this case, when the texture is minified (i.e., the texture's pixels (texels) are
	// *smaller* than the screen pixels you're seeing them on, linearly filter them (i.e. blend them together).  This blends four texels for
	// each sample--which is not very much.  Mipmapping can give better results.  Find a texturing tutorial that discusses these issues
	// further.  Conversely, when the texture is magnified (i.e., the texture's texels are *larger* than the screen pixels you're seeing
	// them on), linearly filter them.  Qualitatively, this causes "blown up" (overmagnified) textures to look blurry instead of blocky.
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
}

void Texture::Draw( const Point2f& dstBottomLeft, const Rectf& srcRect ) const
{
	const float epsilon{ 0.001f };
	if ( !m_CreationOk )
	{
		if (!(srcRect.width > epsilon && srcRect.height > epsilon)) // No srcRect specified
		{
			DrawFilledRect(Rectf{ dstBottomLeft.x, dstBottomLeft.y, m_Width, m_Height });
		}
		else
		{
			DrawFilledRect(Rectf{ dstBottomLeft.x, dstBottomLeft.y, srcRect.width, srcRect.height });
		}
	}
	else
	{
		Rectf dstRect{ dstBottomLeft.x, dstBottomLeft.y, srcRect.width, srcRect.height };
		if (!(srcRect.width > epsilon && srcRect.height > epsilon)) // No srcRect specified
		{
			dstRect.width = m_Width;
			dstRect.height = m_Height;
		}
		Draw( dstRect, srcRect );
	}
}

void Texture::Draw( const Rectf& dstRect, const Rectf& srcRect ) const
{
	const float epsilon{ 0.001f };
	if ( !m_CreationOk )
	{
		DrawFilledRect( dstRect );
		return;
	}

	// Determine texture coordinates using srcRect and default destination width and height
	float textLeft{};
	float textRight{};
	float textTop{};
	float textBottom{};

	float defaultDestWidth{};
	float defaultDestHeight{};
	if ( !( srcRect.width > epsilon && srcRect.height > epsilon) ) // No srcRect specified
	{
		// Use complete texture
		textLeft = 0.0f;
		textRight = 1.0f;
		textTop = 0.0f;
		textBottom = 1.0f;

		defaultDestHeight = m_Height;
		defaultDestWidth = m_Width;
	}
	else // srcRect specified
	{
		// Convert to the range [0.0, 1.0]
		textLeft = srcRect.left / m_Width;
		textRight = ( srcRect.left + srcRect.width ) / m_Width;
		textTop = ( srcRect.bottom - srcRect.height ) / m_Height;
		textBottom = srcRect.bottom / m_Height;

		defaultDestHeight = srcRect.height;
		defaultDestWidth = srcRect.width;
	}

	// Determine vertex coordinates
	float vertexLeft{ dstRect.left };
	float vertexBottom{ dstRect.bottom };
	float vertexRight{};
	float vertexTop{};
	if ( !( dstRect.width > 0.001f && dstRect.height > 0.001f ) ) // If no size specified use default size
	{
		vertexRight = vertexLeft + defaultDestWidth;
		vertexTop = vertexBottom + defaultDestHeight;
	}
	else
	{
		vertexRight = vertexLeft + dstRect.width;
		vertexTop = vertexBottom + dstRect.height;

	}

	// Tell opengl which texture we will use
	glBindTexture( GL_TEXTURE_2D, m_Id );
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );

	// Draw
	glEnable( GL_TEXTURE_2D );
	{
		glBegin( GL_QUADS );
		{
			glTexCoord2f( textLeft, textBottom );
			glVertex2f( vertexLeft, vertexBottom );

			glTexCoord2f( textLeft, textTop );
			glVertex2f( vertexLeft, vertexTop );

			glTexCoord2f( textRight, textTop );
			glVertex2f( vertexRight, vertexTop );

			glTexCoord2f( textRight, textBottom );
			glVertex2f( vertexRight, vertexBottom );
		}
		glEnd( );
	}
	glDisable( GL_TEXTURE_2D );
}

float Texture::GetWidth() const
{
	return m_Width;
}

float Texture::GetHeight() const
{
	return m_Height;
}

bool Texture::IsCreationOk( ) const
{
	return m_CreationOk;
}

void Texture::DrawFilledRect(const Rectf& rect) const
{
	glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
	glBegin(GL_POLYGON);
	{
		glVertex2f(rect.left, rect.bottom);
		glVertex2f(rect.left + rect.width, rect.bottom);
		glVertex2f(rect.left + rect.width, rect.bottom + rect.height);
		glVertex2f(rect.left , rect.bottom + rect.height);
	}
	glEnd();

}
