/**
 * =========================================================================
 * File        : InstancingModelRenderer.cpp
 * Project     : Pyrogenesis
 * Description : Implementation of InstancingModelRenderer
 *
 * @author  Nicolai Hähnle <nicolai@wildfiregames.com>
 * =========================================================================
 */

#include "precompiled.h"

#include "lib/ogl.h"
#include "lib/res/graphics/ogl_shader.h"
#include "maths/Vector3D.h"
#include "maths/Vector4D.h"

#include "ps/CLogger.h"

#include "graphics/Color.h"
#include "graphics/LightEnv.h"
#include "graphics/Model.h"
#include "graphics/ModelDef.h"

#include "renderer/InstancingModelRenderer.h"
#include "renderer/Renderer.h"
#include "renderer/RenderModifiers.h"
#include "renderer/RenderPathVertexShader.h"
#include "renderer/VertexArray.h"


#define LOG_CATEGORY "graphics"


///////////////////////////////////////////////////////////////////////////////////////////////
// InstancingModelRenderer implementation

struct IModelDef : public CModelDefRPrivate
{
	/// Static per-CModel vertex array
	VertexArray m_Array;

	/// Position, normals and UV are all static
	VertexArray::Attribute m_Position;
	VertexArray::Attribute m_Normal;
	VertexArray::Attribute m_UV;

	/// Indices are the same for all models, so share them
	u16* m_Indices;


	IModelDef(CModelDefPtr mdef);
	~IModelDef() { delete[] m_Indices; }
};


IModelDef::IModelDef(CModelDefPtr mdef)
	: m_Array(false)
{
	size_t numVertices = mdef->GetNumVertices();

	m_Position.type = GL_FLOAT;
	m_Position.elems = 3;
	m_Array.AddAttribute(&m_Position);

	m_Normal.type = GL_FLOAT;
	m_Normal.elems = 3;
	m_Array.AddAttribute(&m_Normal);

	m_UV.type = GL_FLOAT;
	m_UV.elems = 2;
	m_Array.AddAttribute(&m_UV);

	m_Array.SetNumVertices(numVertices);
	m_Array.Layout();

	VertexArrayIterator<CVector3D> Position = m_Position.GetIterator<CVector3D>();
	VertexArrayIterator<CVector3D> Normal = m_Normal.GetIterator<CVector3D>();
	VertexArrayIterator<float[2]> UVit = m_UV.GetIterator<float[2]>();

	ModelRenderer::CopyPositionAndNormals(mdef, Position, Normal);
	ModelRenderer::BuildUV(mdef, UVit);

	m_Array.Upload();
	m_Array.FreeBackingStore();

	m_Indices = new u16[mdef->GetNumFaces()*3];
	ModelRenderer::BuildIndices(mdef, m_Indices);
}


struct InstancingModelRendererInternals
{
	/// Currently used RenderModifier
	RenderModifierPtr modifier;

	/// Previously prepared modeldef
	IModelDef* imodeldef;

	/// If true, primary color will only contain the diffuse term
	bool colorIsDiffuseOnly;

	/// After BeginPass, this points to the instancing matrix interface
	VS_Instancing* instancingConfig;
};


// Construction and Destruction
InstancingModelRenderer::InstancingModelRenderer(bool colorIsDiffuseOnly)
{
	m = new InstancingModelRendererInternals;
	m->imodeldef = 0;
	m->colorIsDiffuseOnly = colorIsDiffuseOnly;
}

InstancingModelRenderer::~InstancingModelRenderer()
{
	delete m;
}


// Check hardware support
bool InstancingModelRenderer::IsAvailable()
{
	return g_Renderer.m_VertexShader != 0;
}


// Build modeldef data if necessary - we have no per-CModel data
void* InstancingModelRenderer::CreateModelData(CModel* model)
{
	CModelDefPtr mdef = model->GetModelDef();
	IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m);

	debug_assert(!model->GetBoneMatrices());

	if (!imodeldef)
	{
		imodeldef = new IModelDef(mdef);
		mdef->SetRenderData(m, imodeldef);
	}

	return NULL;
}


void InstancingModelRenderer::UpdateModelData(CModel* UNUSED(model), void* UNUSED(data), u32 UNUSED(updateflags))
{
	// We have no per-CModel data
}


void InstancingModelRenderer::DestroyModelData(CModel* UNUSED(model), void* UNUSED(data))
{
	// We have no per-CModel data, and per-CModelDef data is deleted by the CModelDef
}


// Setup one rendering pass.
void InstancingModelRenderer::BeginPass(uint streamflags, const CMatrix3D* texturematrix)
{
	debug_assert(streamflags == (streamflags & (STREAM_POS|STREAM_UV0|STREAM_COLOR|STREAM_TEXGENTOUV1)));

	RenderPathVertexShader* rpvs = g_Renderer.m_VertexShader;

	glEnableClientState(GL_VERTEX_ARRAY);

	if (streamflags & STREAM_UV0) glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	if (streamflags & STREAM_COLOR)
	{
		const CLightEnv& lightEnv = g_Renderer.GetLightEnv();
		VS_GlobalLight* lightConfig;

		if (streamflags & STREAM_TEXGENTOUV1)
		{
			ogl_program_use(rpvs->m_InstancingLightP);
			lightConfig = &rpvs->m_InstancingLightP_Light;
			m->instancingConfig = &rpvs->m_InstancingLightP_Instancing;

			rpvs->m_InstancingLightP_PosToUV1.SetMatrix(*texturematrix);
		}
		else
		{
			ogl_program_use(rpvs->m_InstancingLight);
			lightConfig = &rpvs->m_InstancingLight_Light;
			m->instancingConfig = &rpvs->m_InstancingLight_Instancing;
		}

		if (m->colorIsDiffuseOnly)
			lightConfig->SetAmbient(RGBColor(0,0,0));
		else
			lightConfig->SetAmbient(lightEnv.m_UnitsAmbientColor);
		lightConfig->SetSunDir(lightEnv.GetSunDir());
		lightConfig->SetSunColor(lightEnv.m_SunColor);

		glEnableClientState(GL_NORMAL_ARRAY);
	}
	else
	{
		if (streamflags & STREAM_TEXGENTOUV1)
		{
			ogl_program_use(rpvs->m_InstancingP);
			m->instancingConfig = &rpvs->m_InstancingP_Instancing;

			rpvs->m_InstancingP_PosToUV1.SetMatrix(*texturematrix);
		}
		else
		{
			ogl_program_use(rpvs->m_Instancing);
			m->instancingConfig = &rpvs->m_Instancing_Instancing;
		}
	}
}

// Cleanup rendering pass.
void InstancingModelRenderer::EndPass(uint streamflags)
{
	if (streamflags & STREAM_UV0) glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	if (streamflags & STREAM_COLOR) glDisableClientState(GL_NORMAL_ARRAY);

	pglUseProgramObjectARB(0);
	glDisableClientState(GL_VERTEX_ARRAY);
}


// Prepare UV coordinates for this modeldef
void InstancingModelRenderer::PrepareModelDef(uint streamflags, CModelDefPtr def)
{
	m->imodeldef = (IModelDef*)def->GetRenderData(m);

	debug_assert(m->imodeldef);

	u8* base = m->imodeldef->m_Array.Bind();
	GLsizei stride = (GLsizei)m->imodeldef->m_Array.GetStride();

	glVertexPointer(3, GL_FLOAT, stride, base + m->imodeldef->m_Position.offset);
	if (streamflags & STREAM_COLOR)
	{
		glNormalPointer(GL_FLOAT, stride, base + m->imodeldef->m_Normal.offset);
	}
	if (streamflags & STREAM_UV0)
	{
		glTexCoordPointer(2, GL_FLOAT, stride, base + m->imodeldef->m_UV.offset);
	}
}


// Render one model
void InstancingModelRenderer::RenderModel(uint streamflags, CModel* model, void* UNUSED(data))
{
	CModelDefPtr mdldef = model->GetModelDef();
	const CMatrix3D& mat = model->GetTransform();

	if (streamflags & STREAM_COLOR)
	{
		CColor sc = model->GetShadingColor();
		glColor3f(sc.r, sc.g, sc.b);
	}

	m->instancingConfig->SetMatrix(mat);

	// render the lot
	size_t numFaces = mdldef->GetNumFaces();
	pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdldef->GetNumVertices(),
			(GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldef->m_Indices);

	// bump stats
	g_Renderer.m_Stats.m_DrawCalls++;
	g_Renderer.m_Stats.m_ModelTris += numFaces;
}

