////////////////////////////////////////////////////////////////////////////////
// Renderable object implementation
// Copyright Bjoern Ganster
// Developed since 2000
////////////////////////////////////////////////////////////////////////////////

#include "RenderableObject.h"
#include "MemoryBlock.h"
#include "Mesh.h"

#ifndef WIN32
#include <inttypes.h>
#endif

#include <iostream>

#define FILE_NUMBER FILE_NO_RENDERABLEOBJECT_CPP

////////////////////////////////////////////////////////////////////////////////
// Constructor

RenderableObject::RenderableObject (const char* className, BGClassID classID, RenderableObject* parent)
: VirtualClass (className, classID),
  mParent(parent),
  mFlags(0)
{
	SET_SCOPE;
	if (parent != NULL) {
		//addParent (parent); // done as part of instruction below
		parent->addChild (this);
	}

#ifdef UseMemMan
	memman.store (this, sizeof (*this), _name);
#endif

	mM = Matrix();
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Destructor

RenderableObject::~RenderableObject () 
{
	// Delete childs
	// Do not use iterators to objects that will be removed
	SET_SCOPE;
	while (mChilds.size() != 0) 
	{
		RenderableObject* other = (*mChilds.rbegin());
		safeDelete(other);
		mChilds.pop_back();
	}

#ifdef UseMemMan
	memman.release (this);
#endif

#ifdef MATERIAL_SUPPORT
	for (size_t i = 0; i < parameters.size(); i++)
		delete parameters[i];
	parameters.clear();
#endif

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////

RenderableObject* RenderableObject::getParent()
{
	return mParent;
}

////////////////////////////////////////////////////////////////////////////////
// Return number of contained points

size_t RenderableObject::getRecursivePointCount () const
{
	SET_SCOPE;
	size_t pc = 0;
	for (size_t i = 0; i < mChilds.size(); ++i) {
		SET_OP;
		pc += mChilds[i]->getRecursivePointCount();
	}
	RETURN pc;
}

////////////////////////////////////////////////////////////////////////////////
// Add contained object

bool RenderableObject::addChild (RenderableObject* obj)
{
	if (obj == NULL|| obj == this) {
		return false;
	}

	SET_SCOPE;
	mChilds.push_back (obj);
	RETURN true;
}

////////////////////////////////////////////////////////////////////////////////
// Remove contained object

bool RenderableObject::removeObject (RenderableObject* obj, bool DeleteObject)
{
	bool found = false;
	obj->removeParent (this);

	/*   vector <RenderableObject*>::iterator i = ContainedObjects.begin();
	while (i != ContainedObjects.end())
	{
	if ((*i) == obj) {
	i = ContainedObjects.erase (i);
	//(*i) = (*ContainedObjects.rend());
	//ContainedObjects.pop_back();
	found = true;
	} else
	i++;
	}*/

	SET_SCOPE;
	unsigned int i = 0;
	while (i < mChilds.size()) {
		if (mChilds[i] == obj) {
			//if (i < ContainedObjects.size()-1)
			//   ContainedObjects[i] = ContainedObjects[ContainedObjects.size()-1];
			//ContainedObjects.pop_back();
			mChilds[i] = NULL;
		} else
			i++;
	}

	if (DeleteObject) {
		delete obj;
	}
	RETURN found;
}

bool RenderableObject::removeObject (size_t i, bool deleteObject)
{
	SET_SCOPE;
	if (i < getObjectCount()) {
		RenderableObject* obj = getObject(i);
		if (deleteObject) {
			delete obj;
		}
		auto iter = mChilds.begin();
		iter += i;
		mChilds.erase(iter);
		RETURN true;
	} else {
		RETURN false;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Remove parent objects

void RenderableObject::removeParent (RenderableObject* obj)
{
}

////////////////////////////////////////////////////////////////////////////////
// Get object's name

const BGCPString& RenderableObject::getName() const 
{
	return mName;
}

void RenderableObject::setName(const BGCPString& newName)
{
	mName = newName;
}

////////////////////////////////////////////////////////////////////////////////
// Mesh size calculation

void RenderableObject::getSize(Point3D& minCoords, 
   Point3D& maxCoords, size_t& vertices, size_t& polygons) const
{
	try {
		SET_SCOPE;
		MatrixStack m;
		vertices = 0;
		polygons = 0;
		getSizeRecursive (minCoords, maxCoords, vertices, polygons, getMatrix());
		RETURN;
	} catch (...) {
		COUT << "Cannot determine size of illegal object" << std::endl;
	}
}

////////////////////////////////////////////////////////////////////////////////
// Recursive Part of mesh size calculation

void RenderableObject::getSizeRecursive(Point3D& minCoords, 
   Point3D& maxCoords, size_t& vertices, size_t& polygons, const Matrix& m) const
{
	SET_SCOPE;
	Matrix m2 = m * getMatrix();
	size_t count = getObjectCount();
	for (size_t i = 0; i < count; i++) {
		SET_SCOPE;
		const RenderableObject* obj = getObject(i);
		if (obj != NULL) {
			obj->getSizeRecursive (minCoords, maxCoords, vertices, polygons, m2);
		}
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Render

// Use hardware matrix stack
void RenderableObject::render ()
{
	SET_SCOPE;
	//glPushMatrix();
	//mM.glMultMatrix();

	SET_OP;
	renderSelf();
	SET_OP;
	renderChilds();
	SET_OP;

	//glPopMatrix();
	RETURN;
}

void RenderableObject::renderWireframe()
{
	SET_SCOPE;
	glPushMatrix();
	mM.glMultMatrix();

	for (size_t i = 0; i < mChilds.size(); ++i) {
		RenderableObject* child = mChilds[i];
		child->renderWireframe();
	}

	glPopMatrix();
	RETURN;
}

// Mix software and hardware matrix stack
// This method can coexist with code that is unaware of software matrix stacks
// Unfortunately, it doesn't work right now ...
/*void RenderableObject::render ()
{
    Matrix oldM;
    oldM.glGetMatrix(GL_MODELVIEW);
    Matrix newM = oldM * M;
    newM.glLoadMatrix();

    if (visible) {
        renderSelf();
        renderChilds();
    }

    oldM.glLoadMatrix();
}*/

////////////////////////////////////////////////////////////////////////////////
// Render childs

void RenderableObject::renderChilds ()
{
	SET_SCOPE;

	for (size_t i = 0; i < mChilds.size(); ++i) {
		RenderableObject* ro = mChilds[i];
		if (ro != NULL) {
			ro->render();
		}
	}

	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Mark front-facing polygons (requires edge neighbourhood information)

void RenderableObject::markFrontFacingPolygons(const Point3D& ls)
{
	SET_SCOPE;
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			ro->markFrontFacingPolygons(ls);
		}
	}
	RETURN;
}

////////////////////////////////////////////////////////////////////////////////
// Render shadow volumes
void RenderableObject::renderShadowVolumes(const Point3D& ls) const
{
	SET_SCOPE;
	for (size_t i = 0; i < getObjectCount(); ++i) {
		const RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			ro->renderShadowVolumes(ls);
		}
	}
	RETURN;
}

// Load from file
// Differs from Mesh::loadFromObj in that the groups inside a .obj file are put into several meshes
// Also, tries to avoid copying data to external strings for efficiency
// Data that doesn't make sense is simply ignored

bool RenderableObject::loadFromObj (const PathChar* fileName)
{
	SET_SCOPE;
	MemoryBlock file;
	if (!file.loadFromFile(fileName)) {
		RETURN false;
	}

	if (file.getSize() < 25) {
		RETURN false;
	}

	char c1 = file.get<char>(0);
	char c2 = file.get<char>(1);
	bool startsWithComment = (c1 == '#' && c2 == ' ');
	bool startsWithGroup   = (c1 == 'g' && c2 == ' ');
	if (!startsWithComment && !startsWithGroup) {
		RETURN false;
	}

	Mesh* mesh = nullptr;
	size_t offset = 0;
	size_t eol, nextLine;
	char* line = file.getPtr<char>(0);
	if (!file.findEOL<char>(offset, eol, nextLine)) {
		RETURN false;
	}
	int lineLen = eol-offset;

	// Index 0 in obj refers to the origin
	std::vector<double> xyzCoords, uvCoords;
	xyzCoords.push_back(0.0);
	xyzCoords.push_back(0.0);
	xyzCoords.push_back(0.0);
	uvCoords.push_back(0.0);
	uvCoords.push_back(0.0);

	while (line != nullptr) {
		if (eol-offset > 2) {
			switch (line[0]) {
			case 'g':
			case 'o':
					SET_OP;
					if (mesh != nullptr) {
						mesh->calculateNormals();
					}
					{
						mesh = new Mesh("Mesh", MeshID, this);
						BGCPString name(line+2, lineLen-2, CP_ASCII);
						mesh->setName(name);
					}
					break;
			case 'v':
				if (line[1] == ' ') {
					// Read three coordinates from the file
					SET_OP;
					size_t p1, p2;
					if (bgstrscan(line, ' ', 3, lineLen, p1)
					&&  bgstrscan(line, ' ', p1+1, lineLen, p2))
					{
						double x = bgstrToD(line+2);
						double y = bgstrToD(line+p1+1);
						double z = bgstrToD(line+p2+1);

						xyzCoords.push_back(x);
						xyzCoords.push_back(y);
						xyzCoords.push_back(z);
					}
				} else if (line[1] == 't') {
					SET_OP;
					size_t sep;
					if (bgstrscan(line, ' ', 4, lineLen, sep)) {
						double u = bgstrToD(line+3);
						double v = bgstrToD(line+sep+1);
						uvCoords.push_back(u);
						uvCoords.push_back(v);
					}
				}
				break;
			case 'f':
				if (line[1] == ' ' && mesh != nullptr) {
					// Count words
					SET_OP;
					// Cinema 4D writes .obj files with terminating spaces - ignore these by ignoring the last character
					auto words = count(line, ' ', lineLen-1);
					if (words >= 3 && mesh != nullptr) {
						// Set polygon points
						MeshPolygon* newPoly = mesh->addPolygon(words);
						size_t pnum = 0;
						int pos = 2;

						while (pos < lineLen) {
							size_t slashPos;
							bgstrscan(line, '/', pos+1, lineLen, slashPos);
							size_t newV = (size_t)bgstrToI(line+pos); 

							// Check if vertex number is legal
							if (3*newV+2 <= xyzCoords.size() && newV != 0) {
								size_t t = (size_t)bgstrToI(line+slashPos+1);
								auto meshVNum = mesh->addPoint(xyzCoords[3*newV], xyzCoords[3*newV+1], xyzCoords[3*newV+2]);
								newPoly->setVertexIndex(pnum, meshVNum);
								if (2*t+1 < uvCoords.size()) {
									size_t meshTNum = mesh->addTexCoords(uvCoords[2*t], uvCoords[2*t+1]);
									newPoly->setTexCoordIndex(pnum, meshTNum);
								}
								pnum++;
							}

							size_t nextPos;
							bgstrscan(line, ' ', pos+1, lineLen, nextPos);
							if ((int)nextPos >= pos) {
								pos = nextPos+1;
							} else {
								pos = lineLen; // stop
							}
						}
					}
				}
				break;
			}
		}

		SET_OP;
		line = nullptr;
		if (offset < file.getSize()) {
			offset = nextLine;
			if (file.findEOL<char>(offset, eol, nextLine)) {
				line = file.getPtr<char>(offset);
				lineLen = eol-offset;
			}
		}
	}

	if (mesh != nullptr) {
		SET_OP;
		mesh->calculateNormals();
		RETURN true;
	} else {
		RETURN false;
	}
}

// Save to file
bool RenderableObject::saveToObj (const PathChar* FileName, bool saveUVs) 
{
	SET_SCOPE;
	BGCPString lines;
	size_t vnum = 0;
	size_t tnum = 0;
	saveToObjRec(lines, saveUVs, this, vnum, tnum);
	RETURN lines.saveToFile(FileName, CP_ASCII);
}

// Save to file (recursive part)
void RenderableObject::saveToObjRec(BGCPString& lines, bool saveUVs, RenderableObject* child, size_t& vnum, size_t& tnum) 
{
	if (child == nullptr) {
		return;
	}

	SET_SCOPE;
	Mesh* mesh = dynamic_cast<Mesh*>(child);
	if (mesh != nullptr) {
		if (!lines.empty()) {
			lines += BGCPString::EOL;
		}
		if (!child->mName.empty()) {
			lines += BGCPString("g ") + child->getName() + BGCPString::EOL;
		}

		// Add vertices
		SET_OP;
		for (size_t i = 0; i < mesh->getVertexCount(); ++i) {
			Point3D p;
			mesh->getVertex(i, p);
			lines += BGCPString("v ") + BGCPString::fromDouble(p.x) + 
				BGCPString(" ") + BGCPString::fromDouble(p.y) +
				BGCPString(" ") + BGCPString::fromDouble(p.z) + BGCPString::EOL;
		}

		// Add texture coordinates
		if (saveUVs) {
			SET_OP;
			for (size_t i = 0; i < mesh->getTexCoordCount(); ++i) {
				double u, v;
				mesh->getTexCoords(i, u, v);
				lines += BGCPString("vt ") + BGCPString::fromDouble(u) + BGCPString(" ") + BGCPString::fromDouble(v) + BGCPString::EOL;
			}
		}

		// Add polygons
		SET_OP;
		for (size_t i = 0; i < mesh->getPolygonCount(); ++i) {
			MeshPolygon* poly = mesh->getPolygon(i);
			BGCPString line = "f";
			for (size_t j = 0; j < poly->getVertexCount(); ++j) {
				line += BGCPString(" ") + BGCPString::fromInt(poly->getVertexIndex(j)+vnum+1);
				if (saveUVs) {
					line += BGCPString("/") + BGCPString::fromInt(poly->getTexCoordIndex(j)+tnum+1);
				}
			}
			lines += line +	BGCPString::EOL;
		}
		vnum += mesh->getVertexCount();
		tnum += mesh->getTexCoordCount();

		if (mesh->mShadowProxy != nullptr) {
			saveToObjRec(lines, saveUVs, mesh->mShadowProxy, vnum, tnum);
		}
	}

	// Add childs
	SET_OP;
	for (size_t i = 0; i < child->getObjectCount(); ++i) {
		saveToObjRec(lines, saveUVs, child->getObject(i), vnum, tnum);
	}

	RETURN;
}

// Transform all vertices in the scene and cache them
void RenderableObject::cacheTransformedVertices(const Matrix& m)
{
	// Check childs
	SET_SCOPE;
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* child = getObject(i);
		if (child != nullptr) {
			Matrix m2 = m * child->getMatrix();
			child->cacheTransformedVertices(m2);
		}
	}
	RETURN;
}

// Set/get matrix
const Matrix& RenderableObject::getMatrix() const
{
	return mM;
}

Matrix& RenderableObject::getMatrix()
{
	return mM;
}

void RenderableObject::setMatrix(const Matrix& _M)
{
	mM = _M;
}

void setMatrixIfNN (RenderableObject* ro, const Matrix& M)
{
	if (ro != NULL) {
		ro->setMatrix(M);
	}
}

// Get min mesh coords, taking into account transformations
bool RenderableObject::getMinCoords(Point3D& aValue)
{
	SET_SCOPE;
	Point3D candidate;
	bool atLeastOneVertexFound = getMinCoordsHere(aValue);

	for (size_t i = 0; i < mChilds.size(); ++i) {
		RenderableObject* child = mChilds[i];
		if (atLeastOneVertexFound) {
			if (child->getMinCoords(candidate)) {
				aValue = MinCoords(aValue, candidate);
			}
		} else {
			atLeastOneVertexFound = child->getMinCoords(aValue);
		}
	}

	RETURN atLeastOneVertexFound;
}

// Get max mesh coords, taking into account transformations
bool RenderableObject::getMaxCoords(Point3D& aValue)
{
	SET_SCOPE;
	Point3D candidate;
	bool atLeastOneVertexFound = getMaxCoordsHere(aValue);

	for (size_t i = 0; i < mChilds.size(); ++i) {
		RenderableObject* child = mChilds[i];
		if (atLeastOneVertexFound) {
			if (child->getMaxCoords(candidate)) {
				aValue = MaxCoords(aValue, candidate);
			}
		} else {
			atLeastOneVertexFound = child->getMaxCoords(aValue);
		}
	}

	RETURN atLeastOneVertexFound;
}

// Get min/max coords for this node
bool RenderableObject::getMinCoordsHere(Point3D&)
{
	return false;
}

bool RenderableObject::getMaxCoordsHere(Point3D&)
{
	return false;
}

// Get min/max mesh coords to decide about grid sizes
// Broken!
/*Point3D RenderableObject::getMinCoords(bool transform) const
{
	Point3D result(0.0, 0.0, 0.0, 1.0);
	if (childs.size() > 0) {
		result = childs[0]->getMinCoords();
		for (size_t i = 1; i < childs.size(); ++i) {
			result = MinCoords(result, childs[i]->getMinCoords(transform));
		}
	}
	return result;
}
Point3D RenderableObject::getMaxCoords(bool transform) const
{
	Point3D result(0.0, 0.0, 0.0, 1.0);
	if (childs.size() > 0) {
		result = childs[0]->getMaxCoords();
		for (size_t i = 1; i < childs.size(); ++i) {
			result = MaxCoords(result, childs[i]->getMaxCoords(transform));
		}
	}
	return result;
}*/

// Get min/max coords as a bounding box
BoundingBox RenderableObject::getBoundingBox() const
{
	BoundingBox bb(mMinCoords, mMaxCoords);
	return bb;
}

// Transform all vertices using m
void RenderableObject::applyMatrix(const Matrix& m)
{
	SET_SCOPE;
	for (size_t i = 0; i < mChilds.size(); ++i) {
		mChilds[i]->applyMatrix(m);
	}
	RETURN;
}

// Make sure all childs use the same texture
void RenderableObject::applyTexture(Texture* texture)
{
	SET_SCOPE;
	// Add geometry nodes for the limbs (RenderableObject adds itself to its parent)
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			ro->applyTexture(texture);
		}
	}
	RETURN;
}

// Delete all children
void RenderableObject::clear()
{
	SET_SCOPE;
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			delete ro;
		}
	}

	mChilds.clear();
	RETURN;
}

// Get number of contained objects
size_t RenderableObject::getObjectCount() const
{
	return mChilds.size();
}

// Get contained object
RenderableObject* RenderableObject::getObject(size_t index)
{
	SET_SCOPE;
	if (index < mChilds.size()) {
		RETURN mChilds[index];
	} else {
		RETURN nullptr;
	}
}

const RenderableObject* RenderableObject::getObject(size_t index) const
{
	SET_SCOPE;
	if (index < mChilds.size()) {
		RETURN mChilds[index];
	} else {
		RETURN nullptr;
	}
}

// Set wireframe color for this object and its childs
void RenderableObject::setRecursiveWireframeColor(const Color& c)
{
	SET_SCOPE;
	for (size_t i = 0; i < mChilds.size(); ++i) {
		mChilds[i]->setRecursiveWireframeColor(c);
	}
	RETURN;
}

// Get bounding box, using transformed vertices
BoundingBox RenderableObject::getTransformedBB()
{
	Point3D min, max;
	if (getMinCoords(min) && getMaxCoords(max)) {
		return BoundingBox(min, max);
	} else {
		return BoundingBox();
	}
}

// Find child by name
const RenderableObject* RenderableObject::findChildByName(const BGCPString& name) const
{
	const RenderableObject* result = nullptr;
	size_t i = 0;
	while (result == nullptr && i < getObjectCount()) {
		const RenderableObject* child = getObject(i);
		if (child->getName() == name) {
			result = child;
		} else {
			++i;
		}
	}
	return result;
}

// Find child by name
RenderableObject* RenderableObject::findChildByName(const BGCPString& name)
{
	RenderableObject* result = nullptr;
	size_t i = 0;
	while (result == nullptr && i < getObjectCount()) {
		RenderableObject* child = getObject(i);
		if (child->getName() == name) {
			result = child;
		} else {
			++i;
		}
	}
	return result;
}

// Flags
//const DWORD Flag_visible = 1;
//const DWORD Flag_waterTight = 2;
void RenderableObject::setFlag(DWORD flag)
{
	mFlags |= flag;
}

void RenderableObject::setFlagRecursive(DWORD flag)
{
	mFlags |= flag;
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			ro->setFlagRecursive(flag);
		}
	}
}

void RenderableObject::clearFlag(DWORD flag)
{
	mFlags = mFlags & (~flag);
}

void RenderableObject::clearFlagRecursive(DWORD flag)
{
	mFlags = mFlags & (~flag);
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			ro->clearFlagRecursive(flag);
		}
	}
}

DWORD RenderableObject::getFlags() const
{
	return mFlags;
}

// Set color recursively
void RenderableObject::setColorRec(const Color& aColor)
{
	color = aColor;
	for (size_t i = 0; i < getObjectCount(); ++i) {
		RenderableObject* ro = getObject(i);
		if (ro != nullptr) {
			ro->setColorRec(aColor);
		}
	}
}
