#include "Mesh.h"
#include "gl.h"

#include <glm/geometric.hpp>

#include <map>
#include <limits>
#include <stack>
#include <fstream>

using namespace std;
using namespace glm;

static const uvec3::value_type InvalidIndex =
		numeric_limits<uvec3::value_type>::max();

Mesh::Mesh() {

}
Mesh::Mesh(Mesh &other) {
	positions = other.positions;
	normals = other.normals;
	texcoords = other.texcoords;

	faceNormals = other.faceNormals;
	indices = other.indices;
	patches = other.patches;
	edges = other.edges;

	adjacents = other.adjacents;
	tangents = other.tangents;
	bitangents = other.bitangents;
}

Mesh::Mesh(Mesh &&other) {
	positions.swap(other.positions);
	normals.swap(other.normals);
	texcoords.swap(other.texcoords);

	faceNormals.swap(other.faceNormals);
	indices.swap(other.indices);
	patches.swap(other.patches);
	edges.swap(other.edges);

	adjacents.swap(other.adjacents);
	tangents.swap(other.tangents);
	bitangents.swap(other.bitangents);
}

Mesh& Mesh::operator=(Mesh &&other) {
	positions.swap(other.positions);
	normals.swap(other.normals);
	texcoords.swap(other.texcoords);

	faceNormals.swap(other.faceNormals);
	indices.swap(other.indices);
	patches.swap(other.patches);
	edges.swap(other.edges);

	adjacents.swap(other.adjacents);
	tangents.swap(other.tangents);
	bitangents.swap(other.bitangents);
	return *this;
}

void Mesh::generateNormals() {
	normals.clear();
	normals.resize(positions.size(), vec3(0));
#pragma omp parallel for
	for (size_t i = 0; i < indices.size(); i++) {
		int a = indices[i].x, b = indices[i].y, c = indices[i].z;
		vec3 faceNormal = normalize(
				cross(positions[b] - positions[a],
						positions[c] - positions[a]));
		normals[a] += faceNormal;
		normals[b] += faceNormal;
		normals[c] += faceNormal;
	}
	for (size_t i = 0; i < normals.size(); i++)
		normals[i] = normalize(normals[i]);

}

void Mesh::generateFaceNormals() {
	faceNormals.clear();
	faceNormals.resize(indices.size(), vec3(uninitialize));
#pragma omp parallel for
	for (size_t i = 0; i < indices.size(); i++) {
		int a = indices[i].x, b = indices[i].y, c = indices[i].z;
		faceNormals[i] = normalize(
				cross(positions[b] - positions[a],
						positions[c] - positions[a]));
	}
}

void Mesh::generateEdges() {
	edges.clear();
	map<pair<uint32_t, uint32_t>, uint32_t> edgeMap;
	for (size_t iTri = 0; iTri < indices.size(); iTri++) {
		uvec3 idx = indices[iTri];
		for (size_t k = 0; k < 3; k++) {
			int a = idx[k];
			int b = idx[(k + 1) % 3];
			uvec4 edge1(std::min(a, b), std::max(a, b), iTri, 0);
			auto key = std::make_pair(edge1.x, edge1.y);
			auto it1 = edgeMap.find(key);
			if (it1 != edgeMap.end()) {
				edges[it1->second].w = iTri;
			} else {
				size_t idx = edges.size();
				edges.push_back(edge1);
				edgeMap[key] = idx;
			}
		}
	}
}

void Mesh::generateTangentBasis() {

	for (size_t i = 0; i < positions.size(); i += 3) {

		// Shortcuts for vertices
		vec3 & v0 = positions[i + 0];
		vec3 & v1 = positions[i + 1];
		vec3 & v2 = positions[i + 2];

		// Shortcuts for UVs
		vec2 & uv0 = texcoords[i + 0];
		vec2 & uv1 = texcoords[i + 1];
		vec2 & uv2 = texcoords[i + 2];

		// Edges of the triangle : postion delta
		vec3 deltaPos1 = v1 - v0;
		vec3 deltaPos2 = v2 - v0;

		// UV delta
		vec2 deltaUV1 = uv1 - uv0;
		vec2 deltaUV2 = uv2 - uv0;

		float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
		vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r;
		vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r;
		// Set the same tangent for all three vertices of the triangle.
		// They will be merged later, in vboindexer.cpp
		tangents.push_back(tangent);
		tangents.push_back(tangent);
		tangents.push_back(tangent);

		// Same thing for binormals
		bitangents.push_back(bitangent);
		bitangents.push_back(bitangent);
		bitangents.push_back(bitangent);

	}
}

void Mesh::generateAdjacent() {
	adjacents.resize(indices.size(), uvec3(InvalidIndex));
	for (size_t i = 0; i < edges.size(); i++) {
		int a = edges[i].z;
		int b = edges[i].w;

		uvec3 &aa = adjacents[a];
		for (size_t j = 0; j < 3; j++) {
			if (aa[j] == InvalidIndex) {
				aa[j] = b;
				break;
			}
		}
		uvec3 &ab = adjacents[b];
		for (size_t j = 0; j < 3; j++) {
			if (ab[j] == InvalidIndex) {
				ab[j] = a;
				break;
			}
		}
	}
}

void Mesh::moveToMean() {
	vec3 mean(0);
	for (size_t i = 0; i < positions.size(); i++) {
		mean += positions[i];
	}
	mean *= vec3(1.f / positions.size());
	for (size_t i = 0; i < positions.size(); i++) {
		positions[i] -= mean;
	}
}

void Mesh::smooth() {
	if (positions.size() == 0 || indices.size() == 0)
		return;

	vec3v_t cogs(positions.size(), vec3(0.f));
	vector<int> valence(positions.size(), 0);

	for (size_t iTri = 0; iTri < indices.size(); iTri++) {
		const uvec3 &idx = indices[iTri];
		for (size_t iE = 0; iE < 3; iE++) {
			valence[idx[iE]] += 2;
			cogs[idx[iE]] += positions[idx[(iE + 1) % 3]];
			cogs[idx[iE]] += positions[idx[(iE + 2) % 3]];
		}
	}
	/*
	 for (v_it = mesh.vertices_begin(); v_it != v_end; ++v_it) {
	 cog[0] = cog[1] = cog[2] = valence = 0.0;

	 for (vv_it = mesh.vv_iter(*v_it); vv_it.is_valid(); ++vv_it) {
	 cog += mesh.point(*vv_it);
	 ++valence;
	 }
	 cogs.push_back(cog / valence);
	 }
	 for (v_it = mesh.vertices_begin(), cog_it = cogs.begin(); v_it != v_end;
	 ++v_it, ++cog_it)
	 if (!mesh.is_boundary(*v_it))
	 mesh.set_point(*v_it, *cog_it);

	 */
#pragma omp parallel for
	for (size_t i = 0; i < positions.size(); i++) {
//		vtx[i] = vtx[i] * vec3(0.8)
//				+ cogs[i] * vec3(0.2f / valence[i]);
		positions[i] = cogs[i] * vec3(1.f / valence[i]);
	}
}

void Mesh::saveAttrib(std::ostream &out, const char *prefix,
		const vec3v_t &elements) const {
	for (size_t i = 0; i < elements.size(); i++) {
		out << prefix << elements[i].x << " " << elements[i].y << " "
				<< elements[i].z << "\n";
	}
}

void Mesh::saveAttrib(std::ostream &out, const char *prefix,
		const vec2v_t &elements) const {
	for (size_t i = 0; i < elements.size(); i++) {
		out << prefix << elements[i].x << " " << elements[i].y << "\n";
	}
}

void Mesh::saveFaces(std::ostream &out, const uvec3v_t &incdices,
		size_t attribs) const {
	for (size_t i = 0; i < incdices.size(); i++) {
		out << "f";

		for (size_t j = 0; j < 3; j++) {
			int v = incdices[i][j] + 1;
			out << " " << v;
			if (attribs > 1)
				out << "/" << v;
			if (attribs > 2)
				out << "/" << v;
			out << " ";
		}

		out << "\n";
	}
}

Mesh Mesh::createPatched(float threshold) const {
	assert(adjacents.size() == indices.size());
	Mesh mesh;
	vector<bool> processed(indices.size(), false);

	for (size_t i = 0; i < indices.size(); i++) {
		if (processed[i])
			continue;
		vector<uint32_t> faces;
		map<uint32_t, uint32_t> vertexMap;

		vec3 normal = faceNormals[i];

		// check all adjacent faces
		stack<uint32_t> facesToCheck;
		facesToCheck.push(i);
		while (facesToCheck.size()) {
			uint32_t face = facesToCheck.top();
			facesToCheck.pop();
			if (face == InvalidIndex || processed[face])
				continue;
			if (dot(normal, faceNormals[face]) > threshold) {
				faces.push_back(face);
				//normal = normalize(normal + 0.5f * faceNormals[face]);
				processed[face] = true;
				facesToCheck.push(adjacents[face].x);
				facesToCheck.push(adjacents[face].y);
				facesToCheck.push(adjacents[face].z);
			}
		}

		// fill new arrays
		for (size_t j = 0; j < faces.size(); j++) {
			uvec3 idx = indices[faces[j]];
			for (size_t k = 0; k < 3; k++) {
				auto it = vertexMap.find(idx[k]);
				if (it == vertexMap.end()) {
					uint32_t newIdx = mesh.positions.size();
					mesh.positions.push_back(positions[idx[k]]);
					if (normals.size())
						mesh.normals.push_back(normals[idx[k]]);
					vertexMap[idx[k]] = newIdx;
					idx[k] = newIdx;
				} else {
					idx[k] = it->second;
				}
			}
			mesh.indices.push_back(idx);
		}
	}
	return mesh;
}

void Mesh::save(std::ostream &out) const {
	size_t nAttribs = 1;
	saveAttrib(out, "v ", positions);
	if (normals.size()) {
		nAttribs++;
		saveAttrib(out, "vn ", normals);
	}
	if (texcoords.size()) {
		nAttribs++;
		saveAttrib(out, "vt ", texcoords);
	}
	saveFaces(out, indices, nAttribs);
}

void Mesh::save(std::string filename) const {
	std::ofstream out(filename.c_str());
	save(out);
}

