/***************************************************************************
 * Copyright 1998-2015 by authors (see AUTHORS.txt)                        *
 *                                                                         *
 *   This file is part of LuxRender.                                       *
 *                                                                         *
 * Licensed under the Apache License, Version 2.0 (the "License");         *
 * you may not use this file except in compliance with the License.        *
 * You may obtain a copy of the License at                                 *
 *                                                                         *
 *     http://www.apache.org/licenses/LICENSE-2.0                          *
 *                                                                         *
 * Unless required by applicable law or agreed to in writing, software     *
 * distributed under the License is distributed on an "AS IS" BASIS,       *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*
 * See the License for the specific language governing permissions and     *
 * limitations under the License.                                          *
 ***************************************************************************/

#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/color/color.h"
#include "luxrays/core/geometry/uv.h"
#include "luxrays/core/geometry/triangle.h"
#include "luxrays/core/geometry/frame.h"
#include "luxrays/core/geometry/motionsystem.h"
#include "luxrays/core/trianglemesh.h"
#include "luxrays/utils/properties.h"

namespace luxrays {

/*
 * The inheritance scheme used here:
 * 
 *         | => TriangleMesh => |
 * Mesh => |                    |=> ExtTriangleMesh
 *         | =>      ExtMesh => |
 * 
 *         | => InstanceTriangleMesh => |
 * Mesh => |                            |=> ExtInstanceTriangleMesh
 *         | =>              ExtMesh => |
 * 
 *         | => MotionTriangleMesh => |
 * Mesh => |                          |=> ExtMotionTriangleMesh
 *         | =>            ExtMesh => |
 */
	
class ExtMesh : virtual public Mesh {
public:
	ExtMesh() { }
	virtual ~ExtMesh() { }

	virtual 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 float time, const u_int triIndex) const = 0;
	virtual Normal GetShadeNormal(const float time, const u_int triIndex, const u_int vertIndex) const = 0;
	virtual Normal GetShadeNormal(const float time, const u_int vertIndex) const = 0;
	virtual UV GetUV(const u_int vertIndex) const = 0;
	virtual Spectrum GetColor(const u_int vertIndex) const = 0;
	virtual float GetAlpha(const u_int vertIndex) const = 0;

	virtual bool GetTriBaryCoords(const float time, const u_int triIndex, const Point &hitPoint, float *b1, float *b2) const = 0;
    void GetDifferentials(const float time, const u_int triIndex, const Normal &shadeNormal,
        Vector *dpdu, Vector *dpdv,
        Normal *dndu, Normal *dndv) const;
	// Note: GetLocal2World can return NULL
	virtual void GetLocal2World(const float time, luxrays::Transform &t) const = 0;

	virtual Normal InterpolateTriNormal(const float time, const u_int triIndex, const float b1, const float b2) const = 0;
	virtual UV InterpolateTriUV(const u_int triIndex, const float b1, const float b2) const = 0;
	virtual Spectrum InterpolateTriColor(const u_int triIndex, const float b1, const float b2) const = 0;
	virtual float InterpolateTriAlpha(const u_int triIndex, const float b1, const float b2) const = 0;

	// This can be a very expansive function to run
	virtual float GetMeshArea(const float time) const = 0;
	virtual float GetTriangleArea(const float time, const unsigned int triIndex) const = 0;
	virtual void Sample(const float time, const u_int triIndex, const float u0, const float u1,
		Point *p, float *b0, float *b1, float *b2) const = 0;

	virtual void Delete() = 0;
	virtual void WritePly(const std::string &fileName) const = 0;
};

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

	Normal *ComputeNormals();

	virtual MeshType GetType() const { return TYPE_EXT_TRIANGLE; }

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

	virtual Normal GetGeometryNormal(const float time, const u_int triIndex) const {
		return triNormals[triIndex];
	}
	virtual Normal GetShadeNormal(const float time, const u_int triIndex, const u_int vertIndex) const { return normals[tris[triIndex].v[vertIndex]]; }
	virtual Normal GetShadeNormal(const float time, const u_int vertIndex) const { return normals[vertIndex]; }
	virtual UV GetUV(const u_int vertIndex) const { return uvs[vertIndex]; }
	virtual Spectrum GetColor(const u_int vertIndex) const { return cols[vertIndex]; }
	virtual float GetAlpha(const u_int vertIndex) const { return alphas[vertIndex]; }

	virtual bool GetTriBaryCoords(const float time, const u_int triIndex, const Point &hitPoint, float *b1, float *b2) const {
		const Triangle &tri = tris[triIndex];
		return tri.GetBaryCoords(vertices, hitPoint, b1, b2);
	}

	virtual void GetLocal2World(const float time, luxrays::Transform &t) const { }

	virtual void ApplyTransform(const Transform &trans);

	virtual Normal InterpolateTriNormal(const float time, const u_int triIndex, const float b1, const float b2) const {
		if (!normals)
			return GetGeometryNormal(time, triIndex);
		const Triangle &tri = tris[triIndex];
		const float b0 = 1.f - b1 - b2;
		return Normalize(b0 * normals[tri.v[0]] + b1 * normals[tri.v[1]] + b2 * normals[tri.v[2]]);
	}

	virtual UV InterpolateTriUV(const u_int triIndex, const float b1, const float b2) const {
		if (uvs) {
			const Triangle &tri = tris[triIndex];
			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);
	}

	virtual Spectrum InterpolateTriColor(const u_int triIndex, const float b1, const float b2) const {
		if (cols) {
			const Triangle &tri = tris[triIndex];
			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);
	}

	virtual float InterpolateTriAlpha(const u_int triIndex, const float b1, const float b2) const {
		if (alphas) {
			const Triangle &tri = tris[triIndex];
			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;
	}

	virtual float GetMeshArea(const float time) const {
		return area;
	}
	
	virtual float GetTriangleArea(const float time, const unsigned int triIndex) const {
		return tris[triIndex].Area(vertices);
	}
	virtual void Sample(const float time, const u_int triIndex, const float u0, const float u1,
			Point *p, float *b0, float *b1, float *b2) const  {
		const Triangle &tri = tris[triIndex];
		tri.Sample(vertices, u0, u1, p, b0, b1, b2);
	}

	virtual void WritePly(const std::string &fileName) const;

	ExtTriangleMesh *Copy(Point *meshVertices, Triangle *meshTris, Normal *meshNormals, UV *meshUV,
			Spectrum *meshCols, float *meshAlpha) const;
	ExtTriangleMesh *Copy() const {
		return Copy(NULL, NULL, NULL, NULL, NULL, NULL);
	}

	static ExtTriangleMesh *LoadExtTriangleMesh(const std::string &fileName);

private:
	void Preprocess();

	Normal *normals; // Vertices normals
	Normal *triNormals; // Triangle normals
	UV *uvs; // Vertex uvs
	Spectrum *cols; // Vertex color
	float *alphas; // Vertex alpha
	float area;
};

class ExtInstanceTriangleMesh : public InstanceTriangleMesh, public ExtMesh {
public:
	ExtInstanceTriangleMesh(ExtTriangleMesh *m, const Transform &t) :  InstanceTriangleMesh(m, t) {
		// The mesh area is compute on demand and cached
		cachedArea = -1.f;
	}
	~ExtInstanceTriangleMesh() { };
	virtual void Delete() {	}

	virtual MeshType GetType() const { return TYPE_EXT_TRIANGLE_INSTANCE; }

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

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

	virtual bool GetTriBaryCoords(const float time, const u_int triIndex, const Point &hitPoint, float *b1, float *b2) const {
		const Triangle &tri = mesh->GetTriangles()[triIndex];

		return Triangle::GetBaryCoords(GetVertex(time, tri.v[0]),
				GetVertex(time, tri.v[1]), GetVertex(time, tri.v[2]), hitPoint, b1, b2);
	}
	
	virtual void GetLocal2World(const float time, luxrays::Transform &t) const {
		t = trans;
	}

	virtual Normal InterpolateTriNormal(const float time, const u_int triIndex, const float b1, const float b2) const {
		return Normalize(trans * ((ExtTriangleMesh *)mesh)->InterpolateTriNormal(time, triIndex, b1, b2));
	}

	virtual UV InterpolateTriUV(const u_int triIndex, const float b1, const float b2) const {
		return ((ExtTriangleMesh *)mesh)->InterpolateTriUV(triIndex, b1, b2);
	}

	virtual Spectrum InterpolateTriColor(const u_int triIndex, const float b1, const float b2) const {
		return ((ExtTriangleMesh *)mesh)->InterpolateTriColor(triIndex, b1, b2);
	}
	
	virtual float InterpolateTriAlpha(const u_int triIndex, const float b1, const float b2) const {
		return ((ExtTriangleMesh *)mesh)->InterpolateTriAlpha(triIndex, b1, b2);
	}

	virtual float GetMeshArea(const float time) const {
		if (cachedArea < 0.f) {
			float area = 0.f;
			for (u_int i = 0; i < GetTotalTriangleCount(); ++i)
				area += GetTriangleArea(0.f, i);

			// Cache the result
			cachedArea = area;
		}

		return cachedArea;
	}

	virtual float GetTriangleArea(const float time, const u_int triIndex) const {
		const Triangle &tri = mesh->GetTriangles()[triIndex];

		return Triangle::Area(GetVertex(time, tri.v[0]), GetVertex(time, tri.v[1]), GetVertex(time, tri.v[2]));
	}
	virtual void Sample(const float time, const u_int triIndex, const float u0, const float u1, Point *p, float *b0, float *b1, float *b2) const  {
		((ExtTriangleMesh *)mesh)->Sample(time, triIndex, u0, u1, p , b0, b1, b2);
		*p *= trans;
	}

	virtual void WritePly(const std::string &fileName) const { ((ExtTriangleMesh *)mesh)->WritePly(fileName); }

	virtual void ApplyTransform(const Transform &t) {
		InstanceTriangleMesh::ApplyTransform(t);

		// Invalidate the cached result
		cachedArea = -1.f;
	}

	const Transform &GetTransformation() const { return trans; }
	void SetTransformation(const Transform &t) {
		trans = t;

		// Invalidate the cached result
		cachedArea = -1.f;
	}
	ExtTriangleMesh *GetExtTriangleMesh() const { return (ExtTriangleMesh *)mesh; };

private:
	mutable float cachedArea;
};

class ExtMotionTriangleMesh : public MotionTriangleMesh, public ExtMesh {
public:
	ExtMotionTriangleMesh(ExtTriangleMesh *m, const MotionSystem &ms) :
		MotionTriangleMesh(m, ms) {
		// The mesh area is compute on demand and cached
		cachedArea = -1.f;
	}
	~ExtMotionTriangleMesh() { }
	virtual void Delete() {	}

	virtual MeshType GetType() const { return TYPE_EXT_TRIANGLE_MOTION; }

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

	virtual Normal GetGeometryNormal(const float time, const u_int triIndex) const {
		const Matrix4x4 m = motionSystem.Sample(time);
		return Normalize(Transform(m) * ((ExtTriangleMesh *)mesh)->GetGeometryNormal(time, triIndex));
	}
	virtual Normal GetShadeNormal(const float time, const unsigned vertIndex) const {
		const Matrix4x4 m = motionSystem.Sample(time);
		return Normalize(Transform(m) * ((ExtTriangleMesh *)mesh)->GetShadeNormal(time, vertIndex));
	}
	virtual Normal GetShadeNormal(const float time, const u_int triIndex, const u_int vertIndex) const {
		const Matrix4x4 m = motionSystem.Sample(time);
		return Normalize(Transform(m) * ((ExtTriangleMesh *)mesh)->GetShadeNormal(time, triIndex, vertIndex));
	}
	virtual UV GetUV(const unsigned vertIndex) const { return ((ExtTriangleMesh *)mesh)->GetUV(vertIndex); }
	virtual Spectrum GetColor(const unsigned vertIndex) const { return ((ExtTriangleMesh *)mesh)->GetColor(vertIndex); }
	virtual float GetAlpha(const unsigned vertIndex) const { return ((ExtTriangleMesh *)mesh)->GetAlpha(vertIndex); }

	virtual bool GetTriBaryCoords(const float time, const u_int triIndex, const Point &hitPoint, float *b1, float *b2) const {
		const Triangle &tri = mesh->GetTriangles()[triIndex];

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

	virtual void GetLocal2World(const float time, luxrays::Transform &t) const {
		t = Transform(motionSystem.Sample(time));
	}

	virtual Normal InterpolateTriNormal(const float time, const u_int triIndex, const float b1, const float b2) const {
		const Matrix4x4 m = motionSystem.Sample(time);
		return Normalize(Transform(m) * ((ExtTriangleMesh *)mesh)->InterpolateTriNormal(time, triIndex, b1, b2));
	}

	virtual UV InterpolateTriUV(const u_int triIndex, const float b1, const float b2) const {
		return ((ExtTriangleMesh *)mesh)->InterpolateTriUV(triIndex, b1, b2);
	}
	
	virtual Spectrum InterpolateTriColor(const u_int triIndex, const float b1, const float b2) const {
		return ((ExtTriangleMesh *)mesh)->InterpolateTriColor(triIndex, b1, b2);
	}
	
	virtual float InterpolateTriAlpha(const u_int triIndex, const float b1, const float b2) const {
		return ((ExtTriangleMesh *)mesh)->InterpolateTriAlpha(triIndex, b1, b2);
	}

	virtual float GetMeshArea(const float time) const {
		if (cachedArea < 0.f) {
			float area = 0.f;
			for (u_int i = 0; i < GetTotalTriangleCount(); ++i)
				area += GetTriangleArea(0.f, i);

			// Cache the result
			cachedArea = area;
		}

		return cachedArea;
	}

	virtual float GetTriangleArea(const float time, const u_int triIndex) const {
		const Triangle &tri = mesh->GetTriangles()[triIndex];

		return Triangle::Area(GetVertex(time, tri.v[0]), GetVertex(time, tri.v[1]), GetVertex(time, tri.v[2]));
	}
	virtual void Sample(const float time, const u_int triIndex, const float u0, const float u1, Point *p, float *b0, float *b1, float *b2) const  {
		((ExtTriangleMesh *)mesh)->Sample(time, triIndex, u0, u1, p , b0, b1, b2);
		*p *= motionSystem.Sample(time);
	}

	virtual void WritePly(const std::string &fileName) const { ((ExtTriangleMesh *)mesh)->WritePly(fileName); }

	virtual void ApplyTransform(const Transform &t) {
		MotionTriangleMesh::ApplyTransform(t);

		// Invalidate the cached result
		cachedArea = -1.f;
	}

	const MotionSystem &GetMotionSystem() const { return motionSystem; }
	ExtTriangleMesh *GetExtTriangleMesh() const { return (ExtTriangleMesh *)mesh; };

private:
	mutable float cachedArea;
};

}

#endif	/* _LUXRAYS_EXTTRIANGLEMESH_H */
