#include "meshmodel.hpp"
#include "meshcollider.hpp"
#include "surface.hpp"
#include <vector>
#include "animator.hpp"

extern gxGraphics* gx_graphics;

struct MeshModel::Rep : public Surface::Monitor {
	int                   ref_cnt;
	mutable Box           box, cullBox;
	mutable MeshCollider* collider;
	mutable int           box_valid, coll_valid, norms_valid;

	SurfaceList       surfaces;
	std::vector<Transform> bone_tforms;

	Rep() : ref_cnt(1), collider(0), box_valid(-1), coll_valid(-1), norms_valid(-1)
	{
		geom_changes = brush_changes = 0;
	}

	~Rep()
	{
		delete collider;
		for (int k = 0; k < surfaces.size(); ++k)
			delete surfaces[k];
	}

	Surface* createSurface(const Brush& b)
	{
		Surface* t = new Surface(this);
		surfaces.push_back(t);
		t->setBrush(b);
		return t;
	}

	Surface* findSurface(const Brush& b)
	{
		for (int k = 0; k < surfaces.size(); ++k) {
			Surface* s = surfaces[k];
			if (s->getBrush() < b || b < s->getBrush())
				continue;
			return s;
		}
		return 0;
	}

	void paint(const Brush& b)
	{
		for (int k = 0; k < surfaces.size(); ++k) {
			Surface* s = surfaces[k];
			s->setBrush(b);
		}
	}

	void add(Rep* t)
	{
		if (cullBox.empty() && !t->cullBox.empty()) {
			setCullBox(t->cullBox);
		}
		for (int k = 0; k < t->surfaces.size(); ++k) {
			Surface* src  = t->surfaces[k];
			Surface* dest = findSurface(src->getBrush());
			if (!dest)
				dest = createSurface(src->getBrush());
			int j;
			for (j = 0; j < src->numTriangles(); ++j) {
				Surface::Triangle t = src->getTriangle(j);
				t.verts[0] += dest->numVertices();
				t.verts[1] += dest->numVertices();
				t.verts[2] += dest->numVertices();
				dest->addTriangle(t);
			}
			for (j = 0; j < src->numVertices(); ++j) {
				dest->addVertex(src->getVertex(j));
			}
		}
	}

	void transform(const Transform& t)
	{
		Matrix co = t.m.cofactor();
		for (int k = 0; k < surfaces.size(); ++k) {
			Surface* s = surfaces[k];
			for (int j = 0; j < s->numVertices(); ++j) {
				const Vector& v = s->getVertex(j).coords;
				const Vector& n = s->getVertex(j).normal;
				s->setCoords(j, t * v);
				s->setNormal(j, co * n);
			}
		}
	}

	void flip()
	{
		for (int k = 0; k < surfaces.size(); ++k) {
			Surface* s = surfaces[k];
			int      j;
			for (j = 0; j < s->numVertices(); ++j) {
				s->setNormal(j, -s->getVertex(j).normal);
			}
			for (j = 0; j < s->numTriangles(); ++j) {
				Surface::Triangle t = s->getTriangle(j);
				std::swap(t.verts[1], t.verts[2]);
				s->setTriangle(j, t);
			}
		}
	}

	void setCullBox(const Box& t)
	{
		cullBox = t;
	}

	void updateNormals()
	{
		if (norms_valid != geom_changes) {
			for (int k = 0; k < surfaces.size(); ++k) {
				Surface* s = surfaces[k];
				s->updateNormals();
			}
			norms_valid = geom_changes;
		}
	}

	const Box& getBox() const
	{
		if (box_valid != geom_changes) {
			box.clear();
			for (int k = 0; k < surfaces.size(); ++k) {
				Surface* s = surfaces[k];
				for (int j = 0; j < s->numVertices(); ++j) {
					box.update(s->getVertex(j).coords);
				}
			}
			box_valid = geom_changes;
		}
		return box;
	}

	const Box& getCullBox() const
	{
		return cullBox.empty() ? getBox() : cullBox;
	}

	MeshCollider* getCollider() const
	{
		if (coll_valid != geom_changes) {
			delete collider;
			std::vector<MeshCollider::Vertex>   verts;
			std::vector<MeshCollider::Triangle> tris;
			for (int k = 0; k < surfaces.size(); ++k) {
				Surface* s = surfaces[k];
				int      j;
				for (j = 0; j < s->numTriangles(); ++j) {
					MeshCollider::Triangle q;
					q.verts[0] = s->getTriangle(j).verts[0] + verts.size();
					q.verts[1] = s->getTriangle(j).verts[1] + verts.size();
					q.verts[2] = s->getTriangle(j).verts[2] + verts.size();
					q.surface  = s;
					q.index    = j;
					tris.push_back(q);
				}
				for (j = 0; j < s->numVertices(); ++j) {
					MeshCollider::Vertex q;
					q.coords = s->getVertex(j).coords;
					verts.push_back(q);
				}
			}
			collider   = new MeshCollider(verts, tris);
			coll_valid = geom_changes;
		}
		return collider;
	}
};

MeshModel::MeshModel() : rep(new Rep()), brush_changes(0) {}

MeshModel::MeshModel(const MeshModel& t) : Model(t), rep(t.rep), brush_changes(rep->brush_changes - 1)
{
	++rep->ref_cnt;
	surf_bones.resize(t.surf_bones.size());
	/*
	if( t.surf_bones.size() ){
		surf_bones.resize( t.surf_bones.size() );
		if( rep->bone_tforms.size() ){
			setRenderSpace( RENDER_SPACE_WORLD );
		}
	}
	*/
}

MeshModel::~MeshModel()
{
	if (!--rep->ref_cnt)
		delete rep;
}

void MeshModel::updateNormals()
{
	rep->updateNormals();
}

void MeshModel::setCullBox(const Box& box)
{
	rep->setCullBox(box);
}

void MeshModel::setRenderBrush(const Brush& b)
{
	--brush_changes;
	render_brush = b;
}

void MeshModel::createBones()
{
	setRenderSpace(RENDER_SPACE_WORLD);
	const std::vector<Object*>& bones = getAnimator()->getObjects();

	surf_bones.resize(bones.size());

	rep->bone_tforms.resize(bones.size());

	for (int k = 0; k < bones.size(); ++k) {
		rep->bone_tforms[k] = -bones[k]->GetWorldTransform();
	}
}

bool MeshModel::render(const RenderContext& rc)
{
	const Box& b = rep->getCullBox();
	if (b.empty())
		return false;

	static Frustum model_frustum;
	new (&model_frustum) Frustum(rc.getWorldFrustum(), -getRenderTform());
	if (!model_frustum.cull(b))
		return false;

	if (brush_changes != rep->brush_changes) {
		brushes.clear();
		for (int k = 0; k < rep->surfaces.size(); ++k) {
			Surface* s = rep->surfaces[k];
			brushes.push_back(Brush(s->getBrush(), render_brush));
		}
		brush_changes = rep->brush_changes;
	}

	if (!surf_bones.size()) {
		for (int k = 0; k < rep->surfaces.size(); ++k) {
			Surface* s = rep->surfaces[k];
			if (gxMesh* mesh = s->getMesh()) {
				enqueue(mesh, 0, s->numVertices(), 0, s->numTriangles(), brushes[k]);
			}
		}
		return false;
	}

	//OK, its boned!
	const std::vector<Object*>& bones = getAnimator()->getObjects();

	int k;
	for (k = 0; k < bones.size(); ++k) {
		Transform t                = bones[k]->getRenderTform() * rep->bone_tforms[k];
		surf_bones[k].coord_tform  = t;
		surf_bones[k].normal_tform = t.m.cofactor();
	}

	bool trans = false;
	for (k = 0; k < rep->surfaces.size(); ++k) {
		Surface* s = rep->surfaces[k];
		if (brushes[k].getBlend() == gxScene::BLEND_REPLACE) {
			if (gxMesh* mesh = s->getMesh(surf_bones)) {
				enqueue(mesh, 0, s->numVertices(), 0, s->numTriangles(), brushes[k]);
			}
		} else {
			trans = true;
		}
	}
	return trans;
}

void MeshModel::renderQueue(int type)
{
	if (type == QUEUE_TRANSPARENT && surf_bones.size()) {
		for (int k = 0; k < rep->surfaces.size(); ++k) {
			Surface* s = rep->surfaces[k];
			if (brushes[k].getBlend() != gxScene::BLEND_REPLACE) {
				if (gxMesh* mesh = s->getMesh(surf_bones)) {
					enqueue(mesh, 0, s->numVertices(), 0, s->numTriangles(), brushes[k]);
				}
			}
		}
	}
	Model::renderQueue(type);
}

Surface* MeshModel::createSurface(const Brush& b)
{
	return rep->createSurface(b);
	--brush_changes;
}

void MeshModel::flipTriangles()
{
	rep->flip();
}

void MeshModel::transform(const Transform& t)
{
	rep->transform(t);
}

void MeshModel::add(const MeshModel& t)
{
	rep->add(t.rep);
}

const MeshModel::SurfaceList& MeshModel::getSurfaces() const
{
	return rep->surfaces;
}

void MeshModel::paint(const Brush& b)
{
	rep->paint(b);
}

const Box& MeshModel::getBox() const
{
	return rep->getBox();
}

MeshCollider* MeshModel::getCollider() const
{
	return rep->getCollider();
}

Surface* MeshModel::findSurface(const Brush& b) const
{
	return rep->findSurface(b);
}

bool MeshModel::collide(const Line& line, float radius, Collision* curr_coll, const Transform& t)
{
	return getCollider()->collide(line, radius, curr_coll, t);
}

bool MeshModel::intersects(const MeshModel& m) const
{
	return getCollider()->intersects(*m.getCollider(), -m.GetWorldTransform() * GetWorldTransform());
}
