#include "BillboardRenderer.h"
#include "Billboard.h"
#include "Color.h"
#include "OpenGLMatrix.h"
#include "OpenGLUtils.h"
#include "Camera.h"
#include "PngImage.h"

#include <Utility/Log.h>

#include <vector>

//////////////////////////////////////////////////////////////
namespace Dubious {
namespace Renderer {

namespace
{
struct BufferData
{
	BufferData( const Math::LocalVector& V, const Math::LocalPoint& P )
	{
		x = V.X();
		y = V.Y();
		z = V.Z();
		tx = P.X();
		ty = P.Y();
	}
	float x, y, z;
	float tx, ty;
private:
	BufferData();
};
typedef std::vector<BufferData> BufferDataVect;
typedef std::vector<unsigned short> IndicesVect;

//////////////////////////////////////////////////////////////
void FillBufferDataVect( BufferDataVect& Data )
{
	const float HALF_SIZE = 0.5;
	Data.push_back( BufferData( Math::LocalVector( -HALF_SIZE,  HALF_SIZE, 0 ), Math::LocalPoint( 0, 1 ) ) );
	Data.push_back( BufferData( Math::LocalVector(  HALF_SIZE, -HALF_SIZE, 0 ), Math::LocalPoint( 1, 0 ) ) );
	Data.push_back( BufferData( Math::LocalVector(  HALF_SIZE,  HALF_SIZE, 0 ), Math::LocalPoint( 1, 1 ) ) );

	Data.push_back( BufferData( Math::LocalVector( -HALF_SIZE,  HALF_SIZE, 0 ), Math::LocalPoint( 0, 1 ) ) );
	Data.push_back( BufferData( Math::LocalVector( -HALF_SIZE, -HALF_SIZE, 0 ), Math::LocalPoint( 0, 0 ) ) );
	Data.push_back( BufferData( Math::LocalVector(  HALF_SIZE, -HALF_SIZE, 0 ), Math::LocalPoint( 1, 0 ) ) );
}

GLuint BindTexture( PngImagePtr pImage )
{
	GLuint Texture;
	glGenTextures( 1, &Texture );

	// Bind Texture
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE, GL_SRC_COLOR);
	glBindTexture(GL_TEXTURE_2D, Texture);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pImage->Dimensions().X(), pImage->Dimensions().Y(),
				 0, GL_RGBA, GL_UNSIGNED_BYTE, pImage->Data());

	return Texture;
}

}

BillboardRenderer::BillboardRenderer()
	: m_VertexID( -1 )
	, m_IndexID( -1 )
{
}

BillboardRenderer::~BillboardRenderer()
{
	glDeleteBuffers(1, &m_VertexID);
	glDeleteBuffers(1, &m_IndexID);
	for (auto iter = m_ImageTextures.begin(), end = m_ImageTextures.end(); iter != end; ++iter) {
		glDeleteTextures( 1, &iter->second );
	}
}

void BillboardRenderer::PreloadImage( PngImagePtr pImage )
{
	auto iter = m_ImageTextures.find( pImage );
	if (iter == m_ImageTextures.end()) {
		m_ImageTextures.insert( std::make_pair( pImage, BindTexture( pImage ) ) );
	}
}

void BillboardRenderer::RenderBillboard( CameraPtr pCamera, BillboardPtr pBillboard )
{
	if (pBillboard->Layers().empty()) {
		return;
	}
	if (m_VertexID == -1) {
		BindToVBO();
	}
	glBindBuffer(GL_ARRAY_BUFFER, m_VertexID);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, sizeof(BufferData), reinterpret_cast<void*>(0));

	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glColor4f( 1, 1, 1, 1 );
	glTexCoordPointer(2, GL_FLOAT, sizeof(BufferData), reinterpret_cast<void*>(12));

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexID);

	// The matrix manipulation is a bit tricky here.  We want
	// to first move to the billboard's position, then we want
	// to translate to the camera's rotation (so the billboard
	// is facing the camera), then we want to apply the
	// billboard's rotation
	OpenGLMatrix GLMatrix;
	float m[16];
	// set position
	Math::CoordinateSpace Coord;
	Coord.SetPosition( pBillboard->Layers()[0].CoordinateSpace.Position() );
	Coord.GLMatrix( m );
	GLMatrix.MultMatrix( m );
	// apply camera rotation
	pCamera->CoordinateSpace().GLMatrix( m );
	GLMatrix.MultMatrix( m );

	for (size_t i=0; i<pBillboard->Layers().size(); ++i) {
		auto iter = m_ImageTextures.find( pBillboard->Layers()[i].pImage );
		if (iter == m_ImageTextures.end()) {
			iter = m_ImageTextures.insert( std::make_pair( pBillboard->Layers()[i].pImage, BindTexture( pBillboard->Layers()[i].pImage ) ) ).first;
		}
		OpenGLMatrix LayerMatrix;

		// apply billboard rotation
		Coord = pBillboard->Layers()[i].CoordinateSpace;
		Coord.SetPosition( Math::Vector( 0, 0, 0 ) );
		Coord.GLMatrix( m );
		LayerMatrix.MultMatrix( m );

		glScalef( pBillboard->Size(), pBillboard->Size(), 0 );
		glBindTexture(GL_TEXTURE_2D, iter->second );
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(0));
	}
}

//////////////////////////////////////////////////////////////
void BillboardRenderer::BindToVBO()
{
	BufferDataVect Data;
	FillBufferDataVect( Data );

	IndicesVect Indices;
	for (unsigned short i=0; i<Data.size(); ++i) {
		Indices.push_back( i );
	}

	glGenBuffers(1, &m_VertexID);
	glBindBuffer(GL_ARRAY_BUFFER, m_VertexID);
	glBufferData(GL_ARRAY_BUFFER, sizeof(BufferData)*Data.size(), &(Data[0]), GL_STATIC_DRAW);

	glGenBuffers(1, &m_IndexID);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_IndexID);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned short)*Indices.size(), &(Indices[0]), GL_STATIC_DRAW);
}

//////////////////////////////////////////////////////////////
void BillboardRenderer::ResetGL()
{
	m_VertexID = -1;
	m_ImageTextures.clear();
}

}
}
