#include "Model.h"

#include <Utility/AC3DFileReader.h>

namespace Dubious
{
namespace Renderer
{

//////////////////////////////////////////////////////////////
Model::Model()
	: m_Color( 1.0f, 1.0f, 1.0f, 1.0f )
{
}

//////////////////////////////////////////////////////////////
Model::Model( const Utility::AC3DFile& File, bool IncludeEdges )
	: m_Color( 1.0f, 1.0f, 1.0f, 1.0f )
{
	Construct( *File.Model(), File.Materials(), IncludeEdges );
}

//////////////////////////////////////////////////////////////
Model::~Model()
{
}

namespace 
{
Model::Surface BuildSurface( const LocalVectorVector& Points, int p0, int p1, int p2 )
{
	Model::Surface NewSurface;
	NewSurface.p0 = p0;
	NewSurface.p1 = p1;
	NewSurface.p2 = p2;
	const Math::LocalVector& A = Points[NewSurface.p0];
	const Math::LocalVector& B = Points[NewSurface.p1];
	const Math::LocalVector& C = Points[NewSurface.p2];
	NewSurface.Normal = (B-A).Cross( C-A ).Normalized();
	return NewSurface;
}

}

//////////////////////////////////////////////////////////////
void Model::Construct( const Utility::AC3DModel& AC3DModel, const Utility::AC3DMaterialVector& Materials, bool IncludeEdges )
{
	m_Offset = Math::LocalVector( AC3DModel.Offset().X, AC3DModel.Offset().Y, AC3DModel.Offset().Z );
	for (size_t i=0; i<AC3DModel.Points().size(); ++i) {
		const Utility::AC3DModel::Point& P = AC3DModel.Points()[i];
		m_Points.push_back( Math::LocalVector( P.X, P.Y, P.Z ) );
	}
	for (size_t i=0; i<AC3DModel.Surfaces().size(); ++i) {
		const Utility::AC3DModel::Surface& S = AC3DModel.Surfaces()[i];
		m_Surfaces.push_back( BuildSurface( m_Points, S.p0, S.p1, S.p2 ) );
	}
	for (size_t i=0; i<AC3DModel.Kids().size(); ++i) {
		ModelPtr pKid( new Model() );
		pKid->Construct( *AC3DModel.Kids()[i], Materials, IncludeEdges );
		m_Kids.push_back( pKid );
	}
	if (!AC3DModel.Surfaces().empty()) {
		const Utility::AC3DMaterial& Material = Materials[AC3DModel.Surfaces()[0].MaterialIndex];
		m_Color = Color( Material.MaterialColor().Red, Material.MaterialColor().Green, Material.MaterialColor().Blue, 1.0f );
	}
//	ComputePointNormals();
	if (IncludeEdges) {
		BuildEdges();
	}
}

//////////////////////////////////////////////////////////////
Model::Model( const Math::CubeVector& Size, bool IncludeEdges )
{
	// The code for this comes from looking at an AC3D file
	// for a cube.
	m_Points.push_back( Math::LocalVector( -Size.X(), -Size.Y(), -Size.Z() ) );
	m_Points.push_back( Math::LocalVector(  Size.X(), -Size.Y(), -Size.Z() ) );
	m_Points.push_back( Math::LocalVector(  Size.X(), -Size.Y(),  Size.Z() ) );
	m_Points.push_back( Math::LocalVector( -Size.X(), -Size.Y(),  Size.Z() ) );
	m_Points.push_back( Math::LocalVector( -Size.X(),  Size.Y(),  Size.Z() ) );
	m_Points.push_back( Math::LocalVector(  Size.X(),  Size.Y(),  Size.Z() ) );
	m_Points.push_back( Math::LocalVector(  Size.X(),  Size.Y(), -Size.Z() ) );
	m_Points.push_back( Math::LocalVector( -Size.X(),  Size.Y(), -Size.Z() ) );
/*
	m_Surfaces.push_back( BuildSurface( m_Points, 2, 1, 6 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 6, 5, 2 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 0, 3, 4 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 4, 7, 0 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 0, 7, 6 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 6, 1, 0 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 4, 3, 2 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 2, 5, 4 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 6, 7, 4 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 4, 5, 6 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 0, 1, 2 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 2, 3, 0 ) );
*/

	m_Surfaces.push_back( BuildSurface( m_Points, 6, 2, 1 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 2, 6, 5 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 4, 0, 3 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 0, 4, 7 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 6, 0, 7 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 0, 6, 1 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 2, 4, 3 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 4, 2, 5 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 4, 6, 7 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 6, 4, 5 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 2, 0, 1 ) );
	m_Surfaces.push_back( BuildSurface( m_Points, 0, 2, 3 ) );


	m_Color = Color( 1.0, 1.0, 1.0, 1.0 );
//	ComputePointNormals();
	if (IncludeEdges) {
		BuildEdges();
	}
}

//////////////////////////////////////////////////////////////
void Model::ComputePointNormals()
{
	m_PointNormals.resize( m_Points.size() );
	for( size_t i=0; i<m_Points.size(); ++i ){
		Math::LocalVector AvgNormal;
		for( size_t j=0; j<m_Surfaces.size(); ++j ){
			if( (m_Surfaces[j].p0 == i) || (m_Surfaces[j].p1 == i) || (m_Surfaces[j].p2 == i) ){
				// this surface includes the vertex in question.
				// find its normal
				AvgNormal += m_Surfaces[j].Normal;
			}
		}
		m_PointNormals[i] = AvgNormal.Normalized();
	}
}

//////////////////////////////////////////////////////////////
void Model::BuildEdges()
{
	size_t j = 0;
	for( size_t i=0; i<m_Surfaces.size(); ++i ){
		unsigned short i0 = m_Surfaces[i].p0;
		unsigned short i1 = m_Surfaces[i].p1;
		for( j=i+1; j<m_Surfaces.size(); ++j ){
			if( ((m_Surfaces[j].p0==i0) && ((m_Surfaces[j].p1==i1) || (m_Surfaces[j].p2==i1))) ||
			    ((m_Surfaces[j].p1==i0) && ((m_Surfaces[j].p0==i1) || (m_Surfaces[j].p2==i1))) ||
			    ((m_Surfaces[j].p2==i0) && ((m_Surfaces[j].p0==i1) || (m_Surfaces[j].p1==i1))) ){
				AddEdge( i0, i1, i, j );
				break;
			}
		}
		i0 = m_Surfaces[i].p1;
		i1 = m_Surfaces[i].p2;
		for( j=i+1; j<m_Surfaces.size(); ++j ){
			if( ((m_Surfaces[j].p0==i0) && ((m_Surfaces[j].p1==i1) || (m_Surfaces[j].p2==i1))) ||
			    ((m_Surfaces[j].p1==i0) && ((m_Surfaces[j].p0==i1) || (m_Surfaces[j].p2==i1))) ||
			    ((m_Surfaces[j].p2==i0) && ((m_Surfaces[j].p0==i1) || (m_Surfaces[j].p1==i1))) ){
				AddEdge( i0, i1, i, j );
				break;
			}
		}
		i0 = m_Surfaces[i].p2;
		i1 = m_Surfaces[i].p0;
		for( j=i+1; j<m_Surfaces.size(); ++j ){
			if( ((m_Surfaces[j].p0==i0) && ((m_Surfaces[j].p1==i1) || (m_Surfaces[j].p2==i1))) ||
			    ((m_Surfaces[j].p1==i0) && ((m_Surfaces[j].p0==i1) || (m_Surfaces[j].p2==i1))) ||
			    ((m_Surfaces[j].p2==i0) && ((m_Surfaces[j].p0==i1) || (m_Surfaces[j].p1==i1))) ){
				AddEdge( i0, i1, i, j );
				break;
			}
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////
void Model::AddEdge( unsigned short p0, unsigned short p1, unsigned short s0, unsigned short s1 )
{
	Edge	NewEdge;
	NewEdge.p0 = p0;
	NewEdge.p1 = p1;
	NewEdge.s0 = s0;
	NewEdge.s1 = s1;
	m_Edges.push_back( NewEdge );
}

}
}
