#include "OutlinedObjectRenderer.h"
#include "VisibleObject.h"
#include "OpenGLMatrix.h"
#include "OpenGLUtils.h"
#include "Model.h"

#include <Utility/Log.h>
#include <stdexcept>

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

//////////////////////////////////////////////////////////////
OutlinedObjectRenderer::OutlinedObjectRenderer()
{
}

//////////////////////////////////////////////////////////////
OutlinedObjectRenderer::~OutlinedObjectRenderer()
{
	for (auto iter = m_ObjectDataMap.begin(), end = m_ObjectDataMap.end(); iter!=end; ++iter) {
		glDeleteBuffers(1, &iter->second.first.VertexID);
		glDeleteBuffers(1, &iter->second.first.IndexID);
	}
}

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

//////////////////////////////////////////////////////////////
void FillEdgeBufferDataVect( VisibleObjectPtr pObject, EdgeBufferDataVect& Data )
{
	const VisibleObject::EdgeVector& Edges = pObject->VisibleEdges();

	for( size_t i=0; i<Edges.size(); ++i ){
		Data.push_back( Edges[i].first );
		Data.push_back( Edges[i].second );
	}
}

}

//////////////////////////////////////////////////////////////
ObjectRenderer::VBOData OutlinedObjectRenderer::BindOutlineToVBO( VisibleObjectPtr pObject )
{
	EdgeBufferDataVect BufferData;
	FillEdgeBufferDataVect( pObject, BufferData );

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

	VBOData VBOBuffer;

	glGenBuffers(1, &VBOBuffer.VertexID);
	glBindBuffer(GL_ARRAY_BUFFER, VBOBuffer.VertexID);
	// NOTE: I used to have a mysterious crash on startup that was happening
	// during this next call.  It was due to incorrect buffer data.  Hopefully
	// it's fixed now, but if it re-appears it might be something else.
	glBufferData(GL_ARRAY_BUFFER, sizeof(EdgeBufferData)*BufferData.size(), &(BufferData[0]), GL_STATIC_DRAW);

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

	VBOBuffer.IndexCount = Indices.size();

	return VBOBuffer;
}

//////////////////////////////////////////////////////////////
void OutlinedObjectRenderer::RenderObject( VisibleObjectPtr pObject )
{
	auto iter = m_ObjectDataMap.find( pObject );
	if( iter == m_ObjectDataMap.end() ){
		VBODataPair NewPair;
		NewPair.first = BindToVBO( pObject );
		NewPair.second = BindOutlineToVBO( pObject );
		m_ObjectDataMap[pObject] = NewPair;
		iter = m_ObjectDataMap.find( pObject );
		if (iter == m_ObjectDataMap.end()) {
			throw std::runtime_error( "OutlinedObjectRenderer couldn't find the object it just created" );
		}
	}

	glDepthFunc (GL_LEQUAL);

	OpenGLMatrix GLMatrix;
	float m[16];
	pObject->CoordinateSpace().GLMatrix( m );
	GLMatrix.MultMatrix( m );

	OpenGLUtils::Material( GL_AMBIENT, pObject->BaseColor() );
	OpenGLUtils::Material( GL_DIFFUSE, pObject->BaseColor() );

	glBindBuffer(GL_ARRAY_BUFFER, iter->second.first.VertexID);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, VBOData::SIZEOF_BUFFER_DATA, reinterpret_cast<void*>(0));

	glEnableClientState(GL_NORMAL_ARRAY);
	glNormalPointer(GL_FLOAT, VBOData::SIZEOF_BUFFER_DATA, reinterpret_cast<void*>(12));

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iter->second.first.IndexID);
	glDrawElements(GL_TRIANGLES, iter->second.first.IndexCount, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(0));

	// draw the outline
	glLineWidth( 4 );
	glDepthFunc (GL_LEQUAL);
	OpenGLUtils::Material( GL_AMBIENT, Color::BLACK );
	OpenGLUtils::Material( GL_DIFFUSE, Color::BLACK );

	glBindBuffer(GL_ARRAY_BUFFER, iter->second.second.VertexID);
	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, 12, reinterpret_cast<void*>(0));

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iter->second.second.IndexID);
	glDrawElements(GL_LINES, iter->second.second.IndexCount, GL_UNSIGNED_SHORT, reinterpret_cast<void*>(0));
}

//////////////////////////////////////////////////////////////
void OutlinedObjectRenderer::ResetGL()
{
	m_ObjectDataMap.clear();
}

}
}
