/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include "core/geometry_template.h"
		#include "sort/sort.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::Core;


//------------------------------------------------------------------------------
void			GeometryTemplate::ClearMaterials()
{	materials.Clear();	}
void			GeometryTemplate::PushMaterial(const char *uri)
{	materials.Add(uri);	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
#define	__AssertPolygon	 if	(!polygon) __ERRRAW__(__LOG_E__ << "You must begin a polygon before pushing attributes.\n")
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void			GeometryTemplate::BeginPolygon()
{	polygon = new Polygon;	}
void			GeometryTemplate::PushVertex(const Vector4 &v)
{	__AssertPolygon
	polygon->vertex.Add(v);	}
void			GeometryTemplate::PushNormal(const Vector4 &n)
{	__AssertPolygon
	polygon->normal.Add(n);	}
void			GeometryTemplate::PushColor(const Color &c)
{	__AssertPolygon
	polygon->color.Add(c);	}
void			GeometryTemplate::PushUV(uint channel, const Vector2 &uv)
{	__AssertPolygon
	polygon->uv[channel].Add(uv);	}
void			GeometryTemplate::EndPolygon(ushort material)
{
	if	(polygon)
	{
		polygon->material = material;
		polygons.Add(polygon.Detach());
	}
	else
		__LOG_E__ << "You must begin a polygon before ending it.\n";
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Vector4			GeometryTemplate::GetVertex(const PolygonVertex &pv)
{	return polygons[pv.ipoly]->vertex[pv.ivertex];	}
Vector4			GeometryTemplate::GetNormal(const PolygonVertex &pv)
{	return polygons[pv.ipoly]->normal[pv.ivertex];	}
Vector2				GeometryTemplate::GetUV(uint channel, const PolygonVertex &pv)
{	return polygons[pv.ipoly]->uv[channel][pv.ivertex];	}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
Geometry		*GeometryTemplate::Instantiate(const char *name)
{
	if	(!materials.GetCount())
		__ERR__(__LOG_E__ << "Cannot instantiate a geometry with no material array.\n", NULL)

	AutoPtr <Geometry> geo(new Geometry);
	if	(geo.IsNull())
		__ERR__(__LOG_E__ << "Geometry allocation error.\n", NULL)
	geo->name = name;

	// Build vertex merge table.
	uint vtx_per_poly_count = 0;
	ListForeachPtr(Polygon *, p, polygons)
		vtx_per_poly_count += p->vertex.GetCount();

	typedef GS::Sort<float, uint> SortVPP;

	Array <PolygonVertex> raw_v(vtx_per_poly_count);
	Array <SortVPP::Entry> qs_e(vtx_per_poly_count);

	vtx_per_poly_count = 0;
	uint ipoly = 0;
	Vector4	&support = polygons[0]->vertex[0];

	ListForeachPtr(Polygon *, p, polygons)
	{
		for	(uint ivertex = 0; ivertex < p->vertex.GetCount(); ++ivertex)
		{
			// Track this vertex.
			raw_v[vtx_per_poly_count].ipoly = ipoly;
			raw_v[vtx_per_poly_count].ivertex = ivertex;

			// Quick-sort entry.
			qs_e[vtx_per_poly_count].o = vtx_per_poly_count;
			qs_e[vtx_per_poly_count].v = Vector4::Dist2(GetVertex(raw_v[vtx_per_poly_count]), support);

			++vtx_per_poly_count;
		}
		++ipoly;
	}

	// Compute polygon start offsets.
	Array <int> poly_offset(polygons.GetCount());

	int poly_binding_start_offset = 0;
	for	(uint n = 0; n < polygons.GetCount(); ++n)
	{
		poly_offset[n] = poly_binding_start_offset;
		poly_binding_start_offset += polygons[n]->vertex.GetCount();
	}

	// Count merged vertex.
	SortVPP::QuickSort(vtx_per_poly_count, qs_e);
	geo->binding.Allocate(vtx_per_poly_count);

	uint packed_vtx_count = 0;
	for	(uint n = 0; n < vtx_per_poly_count; )
	{
		PolygonVertex &pv = raw_v[qs_e[n].o];
		geo->binding[poly_offset[pv.ipoly] + pv.ivertex] = packed_vtx_count;

		uint m = n + 1;
		for	(; m < vtx_per_poly_count; ++m)
		{
			if	(Vector4::Dist2(GetVertex(raw_v[qs_e[n].o]), GetVertex(raw_v[qs_e[m].o])) > merge_threshold)
				break;

			PolygonVertex &pv = raw_v[qs_e[m].o];
			geo->binding[poly_offset[pv.ipoly] + pv.ivertex] = packed_vtx_count;
		}

		++packed_vtx_count;
		n = m;
	}

	// Pack vertices.
	geo->vtx.Allocate(packed_vtx_count);

	packed_vtx_count = 0;
	for	(uint n = 0; n < vtx_per_poly_count; )
	{
		uint m = n + 1;
		for	(; m < vtx_per_poly_count; ++m)
			if	(Vector4::Dist2(GetVertex(raw_v[qs_e[n].o]), GetVertex(raw_v[qs_e[m].o])) > merge_threshold)
				break;

		geo->vtx[packed_vtx_count++] = GetVertex(raw_v[qs_e[n].o]);
		n = m;
	}

	// Build polygons.
	geo->pol.Allocate(polygons.GetCount());

	uint pol_count = 0, pol_bind = 0;
	ListForeachPtr(Polygon *, p, polygons)
	{
		if	(p->vertex.GetCount() > 65535)
			__LOG_W__ << "Too many vertices in polygon " << pol_count << ".\n";

		geo->pol[pol_count].vtx_count = (ushort)p->vertex.GetCount();
		geo->pol[pol_count].binding = &geo->binding[pol_bind];
		pol_bind += p->vertex.GetCount();

		if	(p->material > materials.GetCount())
		{
			p->material = 0;
			__LOG_W__ << "Polygon " << pol_count << " is referencing a material outside of the material table.\n";
		}
		geo->pol[pol_count].material = p->material;
		++pol_count;
	}

	// Output remaining attributes.
	Polygon	*poly = polygons[0];

	bool has_normal = asbool(poly->normal.GetCount());
	bool has_color = asbool(poly->color.GetCount());

	bool has_uv[__UV_PER_GEOMETRY__];
	for	(uint n = 0; n < __UV_PER_GEOMETRY__; ++n)
		has_uv[n] = asbool(poly->uv[n].GetCount());

	//--------------------------------------------------------------------------
	#define	__CreateAttribute(__Destination, __Attrib)\
	{\
		if	(__Destination.Allocate(vtx_per_poly_count))\
		{\
			uint	cpol = 0, cvpl = 0;\
			ListForeachPtr(Polygon *, p, polygons)\
			{\
				if	(p->vertex.GetCount() != __Attrib.GetCount())\
					__LOG_W__ << "Incoherent normal count in polygon " << cpol << ".\n";\
				else\
					for	(uint n = 0; n < p->vertex.GetCount(); ++n)\
						__Destination[cvpl + n] = __Attrib[n];\
				\
				cvpl += p->vertex.GetCount();\
				++cpol;\
			}\
		}\
		else\
			__LOG_E__ << "Failed to allocate attribute array while creating geometry '" << name << "'.\n";\
	}
	//--------------------------------------------------------------------------

	if	(has_normal)
		__CreateAttribute(geo->vtx_normal, p->normal)
	if	(has_color)
		__CreateAttribute(geo->rgb, p->color)

	for	(uint i_uv = 0; i_uv < __UV_PER_GEOMETRY__; ++i_uv)
		if	(has_uv[i_uv])
			__CreateAttribute(geo->uv[i_uv], p->uv[i_uv])

	if	(!geo->material_table.Allocate(materials.GetCount()))
		__ERR__(__LOG_E__ << "Failed to allocate material array when creating geometry '" << name << "'.\n", NULL)

	uint slot_count = 0;
	ListForeach(String, n, materials)
		geo->material_table[slot_count++].name = n.Object();

	return geo.Detach();
}
void			GeometryTemplate::Clear()
{
	polygon = NULL;
	polygons.Clear();
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
GeometryTemplate::GeometryTemplate()
{
	merge_threshold = 0.0001f;
}
//------------------------------------------------------------------------------
