/***************************************************************************
 *   Copyright (C) 1998-2013 by authors (see AUTHORS.txt)                  *
 *                                                                         *
 *   This file is part of LuxRays.                                         *
 *                                                                         *
 *   LuxRays is free software; you can redistribute it and/or modify       *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   LuxRays is distributed in the hope that it will be useful,            *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 *                                                                         *
 *   LuxRays website: http://www.luxrender.net                             *
 ***************************************************************************/

#ifndef _LUXRAYS_EXTTRIANGLEMESH_H
#define	_LUXRAYS_EXTTRIANGLEMESH_H

#include <cassert>
#include <cstdlib>

#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>

#include "luxrays/luxrays.h"
#include "luxrays/core/spectrum.h"
#include "luxrays/core/geometry/uv.h"
#include "luxrays/core/geometry/triangle.h"
#include "luxrays/core/trianglemesh.h"
#include "luxrays/utils/properties.h"

namespace luxrays {

class ExtMeshCache;

class ExtMesh : public Mesh {
public:
	ExtMesh() { }
	virtual ~ExtMesh() { }

	std::string GetName() const { return "extmesh-" + boost::lexical_cast<std::string>(this); }

	virtual bool HasNormals() const = 0;
	virtual bool HasUVs() const = 0;
	virtual bool HasColors() const = 0;
	virtual bool HasAlphas() const = 0;

	virtual Normal GetGeometryNormal(const unsigned int triIndex) const = 0;
	virtual Normal GetShadeNormal(const unsigned int triIndex, const unsigned int vertIndex) const = 0;
	virtual Normal GetShadeNormal(const unsigned int vertIndex) const = 0;
	virtual UV GetUV(const unsigned int vertIndex) const = 0;
	virtual Spectrum GetColor(const unsigned int vertIndex) const = 0;
	virtual float GetAlpha(const unsigned int vertIndex) const = 0;

	virtual bool GetTriBaryCoords(const unsigned int index, const Point &hitPoint, float *b1, float *b2) const = 0;

	virtual Normal InterpolateTriNormal(const unsigned int index, const float b1, const float b2) const = 0;
	virtual UV InterpolateTriUV(const unsigned int index, const float b1, const float b2) const = 0;
	virtual Spectrum InterpolateTriColor(const unsigned int index, const float b1, const float b2) const = 0;
	virtual float InterpolateTriAlpha(const unsigned int index, const float b1, const float b2) const = 0;

	virtual void Sample(const unsigned int index, const float u0, const float u1, Point *p, float *b0, float *b1, float *b2) const = 0;

	virtual void Delete() = 0;
	virtual Properties ToProperties(const std::string &matName, const luxrays::ExtMeshCache &extMeshCache) const = 0;
	virtual void WritePly(const std::string &fileName) const = 0;
};

class ExtTriangleMesh : public ExtMesh {
public:
	ExtTriangleMesh(ExtTriangleMesh *mesh);
	ExtTriangleMesh(const unsigned int meshVertCount, const unsigned int meshTriCount,
			Point *meshVertices, Triangle *meshTris, Normal *meshNormals = NULL, UV *meshUV = NULL,
			Spectrum *meshCols = NULL, float *meshAlpha = NULL);
	~ExtTriangleMesh() { };
	void Delete() {
		delete[] vertices;
		delete[] tris;
		delete[] normals;
		delete[] triNormals;
		delete[] uvs;
		delete[] cols;
		delete[] alphas;
	}

	Normal *ComputeNormals();

	MeshType GetType() const { return TYPE_EXT_TRIANGLE; }
	unsigned int GetTotalVertexCount() const { return vertCount; }
	unsigned int GetTotalTriangleCount() const { return triCount; }
	BBox GetBBox() const;

	bool HasNormals() const { return normals != NULL; }
	bool HasUVs() const { return uvs != NULL; }
	bool HasColors() const { return cols != NULL; }
	bool HasAlphas() const { return alphas != NULL; }

	Point GetVertex(const unsigned int vertIndex) const { return vertices[vertIndex]; }
	float GetTriangleArea(const unsigned int triIndex) const { return tris[triIndex].Area(vertices); }
	Normal GetGeometryNormal(const unsigned int triIndex) const {
		return triNormals[triIndex];
	}
	Normal GetShadeNormal(const unsigned int triIndex, const unsigned int vertIndex) const { return normals[tris[triIndex].v[vertIndex]]; }
	Normal GetShadeNormal(const unsigned int vertIndex) const { return normals[vertIndex]; }
	UV GetUV(const unsigned int vertIndex) const { return uvs[vertIndex]; }
	Spectrum GetColor(const unsigned int vertIndex) const { return cols[vertIndex]; }
	float GetAlpha(const unsigned int vertIndex) const { return alphas[vertIndex]; }

	bool GetTriBaryCoords(const unsigned int index, const Point &hitPoint, float *b1, float *b2) const {
		const Triangle &tri = tris[index];
		return tri.GetBaryCoords(vertices, hitPoint, b1, b2);
	}
	
	Normal InterpolateTriNormal(const unsigned int index, const float b1, const float b2) const {
		if (!normals)
			return GetGeometryNormal(index);
		const Triangle &tri = tris[index];
		const float b0 = 1.f - b1 - b2;
		return Normalize(b0 * normals[tri.v[0]] + b1 * normals[tri.v[1]] + b2 * normals[tri.v[2]]);
	}

	UV InterpolateTriUV(const unsigned int index, const float b1, const float b2) const {
		if (uvs) {
			const Triangle &tri = tris[index];
			const float b0 = 1.f - b1 - b2;
			return b0 * uvs[tri.v[0]] + b1 * uvs[tri.v[1]] + b2 * uvs[tri.v[2]];
		} else
			return UV(0.f, 0.f);
	}

	Spectrum InterpolateTriColor(const unsigned int index, const float b1, const float b2) const {
		if (cols) {
			const Triangle &tri = tris[index];
			const float b0 = 1.f - b1 - b2;
			return b0 * cols[tri.v[0]] + b1 * cols[tri.v[1]] + b2 * cols[tri.v[2]];
		} else
			return Spectrum(1.f);
	}

	float InterpolateTriAlpha(const unsigned int index, const float b1, const float b2) const {
		if (alphas) {
			const Triangle &tri = tris[index];
			const float b0 = 1.f - b1 - b2;
			return b0 * alphas[tri.v[0]] + b1 * alphas[tri.v[1]] + b2 * alphas[tri.v[2]];
		} else
			return 1.f;
	}

	void Sample(const unsigned int index, const float u0, const float u1, Point *p, float *b0, float *b1, float *b2) const  {
		const Triangle &tri = tris[index];
		tri.Sample(vertices, u0, u1, p, b0, b1, b2);
	}

	Point *GetVertices() const { return vertices; }
	Triangle *GetTriangles() const { return tris; }

	virtual void ApplyTransform(const Transform &trans);

	virtual Properties ToProperties(const std::string &matName, const luxrays::ExtMeshCache &extMeshCache) const;
	virtual void WritePly(const std::string &fileName) const;

	static ExtTriangleMesh *LoadExtTriangleMesh(const std::string &fileName, const bool usePlyNormals = false);
	static ExtTriangleMesh *CreateExtTriangleMesh(
		const long plyNbVerts, const long plyNbTris,
		Point *p, Triangle *vi, Normal *n, UV *uv, Spectrum *cols, float *alphas,
		const bool usePlyNormals);

private:
	unsigned int vertCount;
	unsigned int triCount;
	Point *vertices;
	Triangle *tris;
	Normal *normals; // Vertices normals
	Normal *triNormals; // Triangle normals
	UV *uvs; // Vertex uvs
	Spectrum *cols; // Vertex color
	float *alphas; // Vertex alpha
};

class ExtInstanceTriangleMesh : public ExtMesh {
public:
	ExtInstanceTriangleMesh(ExtTriangleMesh *m, const Transform &t) {
		assert (m != NULL);

		trans = t;
		mesh = m;
	}
	~ExtInstanceTriangleMesh() { };
	void Delete() {	}

	virtual MeshType GetType() const { return TYPE_EXT_TRIANGLE_INSTANCE; }

	Point GetVertex(const unsigned index) const {
		return trans * mesh->GetVertex(index);
	}
	float GetTriangleArea(const unsigned int triIndex) const {
		const Triangle &tri = mesh->GetTriangles()[triIndex];

		return Triangle::Area(GetVertex(tri.v[0]), GetVertex(tri.v[1]), GetVertex(tri.v[2]));
	}
	unsigned int GetTotalVertexCount() const { return mesh->GetTotalVertexCount(); }
	unsigned int GetTotalTriangleCount() const { return mesh->GetTotalTriangleCount(); }

	BBox GetBBox() const {
		return trans * mesh->GetBBox();
	}

	bool HasNormals() const { return mesh->HasNormals(); }
	bool HasUVs() const { return mesh->HasUVs(); }
	bool HasColors() const { return mesh->HasColors(); }
	bool HasAlphas() const { return mesh->HasAlphas(); }

	Normal GetGeometryNormal(const unsigned int triIndex) const {
		return Normalize(trans * mesh->GetGeometryNormal(triIndex));
	}
	Normal GetShadeNormal(const unsigned index) const {
		return Normalize(trans * mesh->GetShadeNormal(index));
	}
	Normal GetShadeNormal(const unsigned int triIndex, const unsigned int vertIndex) const {
		return Normalize(trans * mesh->GetShadeNormal(triIndex, vertIndex));
	}
	UV GetUV(const unsigned index) const { return mesh->GetUV(index); }
	Spectrum GetColor(const unsigned index) const { return mesh->GetColor(index); }
	float GetAlpha(const unsigned index) const { return mesh->GetAlpha(index); }

	bool GetTriBaryCoords(const unsigned int index, const Point &hitPoint, float *b1, float *b2) const {
		const Triangle &tri = mesh->GetTriangles()[index];

		return Triangle::GetBaryCoords(GetVertex(tri.v[0]), GetVertex(tri.v[1]), GetVertex(tri.v[2]), hitPoint, b1, b2);
	}

	Normal InterpolateTriNormal(const unsigned int index, const float b1, const float b2) const {
		return Normalize(trans * mesh->InterpolateTriNormal(index, b1, b2));
	}

	UV InterpolateTriUV(const unsigned int index, const float b1, const float b2) const {
		return mesh->InterpolateTriUV(index, b1, b2);
	}
	
	Spectrum InterpolateTriColor(const unsigned int index, const float b1, const float b2) const {
		return mesh->InterpolateTriColor(index, b1, b2);
	}
	
	float InterpolateTriAlpha(const unsigned int index, const float b1, const float b2) const {
		return mesh->InterpolateTriAlpha(index, b1, b2);
	}

	void Sample(const unsigned int index, const float u0, const float u1, Point *p, float *b0, float *b1, float *b2) const  {
		mesh->Sample(index, u0, u1, p , b0, b1, b2);
		*p *= trans;
	}

	virtual void ApplyTransform(const Transform &t) { trans = trans * t; }

	const Transform &GetTransformation() const { return trans; }
	void SetTransformation(const Transform &t) {
		trans = t;
	}
	Point *GetVertices() const { return mesh->GetVertices(); }
	Triangle *GetTriangles() const { return mesh->GetTriangles(); }
	ExtTriangleMesh *GetExtTriangleMesh() const { return mesh; };

	virtual Properties ToProperties(const std::string &matName, const luxrays::ExtMeshCache &extMeshCache) const;
	virtual void WritePly(const std::string &fileName) const { mesh->WritePly(fileName); }

private:
	Transform trans;
	ExtTriangleMesh *mesh;
};

class ExtMeshDefinitions {
public:
	ExtMeshDefinitions() { }
	~ExtMeshDefinitions() {
		// I don't delete the ExtMesh here because they are usually stored
		// in a ExtMeshCache
	}

	bool IsExtMeshDefined(const std::string &name) const {
		return (meshsByName.count(name) > 0);
	}
	void DefineExtMesh(const std::string &name, ExtMesh *t) {
		meshes.push_back(t);
		meshsByName.insert(std::make_pair(name, t));
	}
	void DeleteExtMesh(const std::string &name) {
		// Look for the mesh
		std::map<std::string, ExtMesh *>::iterator it = meshsByName.find(name);

		if (it != meshsByName.end()) {
			// I don't delete the ExtMesh here because they are usually stored
			// in a ExtMeshCache
			meshes.erase(std::find(meshes.begin(), meshes.end(), it->second));
			meshsByName.erase(it);
		}
	}
    
	// Helper function for GetReferenceCount().
	inline static luxrays::ExtTriangleMesh *GetReferMesh(ExtMesh *object) {
		luxrays::ExtTriangleMesh *pGeometry = NULL;
		luxrays::ExtInstanceTriangleMesh *pInstance = dynamic_cast<luxrays::ExtInstanceTriangleMesh*>(object);

		if (pInstance)
			pGeometry = pInstance->GetExtTriangleMesh();
		else
			pGeometry = dynamic_cast<luxrays::ExtTriangleMesh*>(object);

		return pGeometry;
	}

    // Get the number of objects (i.e. instances) that refer pGeometry.  Used
	// to safely delete a mesh: remove a mesh from ExtMeshCache when it's refcount less 1.
	u_int GetReferenceCount(luxrays::ExtTriangleMesh *pGeometry) {
		u_int ref = 0;

		BOOST_FOREACH(ExtMesh *object, meshes) {
			if (GetReferMesh(object) == pGeometry)
				ref++;
		}

		return ref;
	}

	ExtMesh *GetExtMesh(const std::string &name) {
		// Check if the mesh has been already defined
		std::map<std::string, ExtMesh *>::const_iterator it = meshsByName.find(name);

		if (it == meshsByName.end())
			throw std::runtime_error("Reference to an undefined mesh: " + name);
		else
			return it->second;
	}

	ExtMesh *GetExtMesh(const u_int index) {
		return meshes[index];
	}

	const ExtMesh *GetExtMesh(const u_int index) const {
		return meshes[index];
	}

	u_int GetExtMeshIndex(const std::string &name) {
		return GetExtMeshIndex(GetExtMesh(name));
	}
	u_int GetExtMeshIndex(const ExtMesh *m) const {
		for (u_int i = 0; i < meshes.size(); ++i) {
			if (m == meshes[i])
				return i;
		}

		throw std::runtime_error("Reference to an undefined mesh: " + boost::lexical_cast<std::string>(m));
	}

	vector<std::string> GetExtMeshNames() const {
		vector<std::string> names;
		names.reserve(meshes.size());
		for (std::map<std::string, ExtMesh *>::const_iterator it = meshsByName.begin(); it != meshsByName.end(); ++it)
			names.push_back(it->first);

		return names;
	}

	std::vector<ExtMesh *> &GetAllMesh() { return meshes; }
	u_int GetSize() const { return static_cast<u_int>(meshes.size()); }
  
private:
	std::vector<ExtMesh *> meshes;
	std::map<std::string, ExtMesh *> meshsByName;
};

}

#endif	/* _LUXRAYS_EXTTRIANGLEMESH_H */
