#include "Polygoniser.h"

#include <functional>
#include <map>

#include <glm/gtc/epsilon.hpp>

#include "MarchingCube.h"

using namespace glm;

#define ISOCELL 0
static const int isocells = 50;

Polygonizer::Polygonizer(Density &density, float isolevel) :
		density(density), isolevel(isolevel), isocellIsolevel(0), isocellVersion(
				0) {
}

void Polygonizer::setIsoLevel(float isolevel) {
	this->isolevel = isolevel;
}

#if ISOCELL
void Polygonizer::updateIsocell() {
	if (density.getVersion() == isocellVersion && isocellIsolevel == isolevel)
	return;
	isocell.clear();
	isocell.resize(isocells * isocells * isocells);

	const float lowerIso = isolevel * 0.7;
	const float upperIso = isolevel * 1.3;
	const float step = 1. / isocells;
#pragma omp parallel for
	for (size_t ix = 0; ix < isocells; ix++) {
		float xmin = ix * step;
		float xmax = xmin + step;
		for (size_t iy = 0; iy < isocells; iy++) {
			float ymin = iy * step;
			float ymax = ymin + step;

			float xyz = density(vec3(xmin, ymin, 0));
			float Xyz = density(vec3(xmax, ymin, 0));
			float xYz = density(vec3(xmin, ymax, 0));
			float XYz = density(vec3(xmax, ymax, 0));
			bool zAllUnder = (xyz < lowerIso) && (Xyz < lowerIso)
			&& (xYz < lowerIso) && (XYz < lowerIso);
			bool zAllOver = (xyz > upperIso) && (Xyz > upperIso)
			&& (xYz > upperIso) && (XYz > upperIso);

			for (size_t iz = 0; iz < isocells; iz++) {
				float zmin = iz * step;
				float zmax = zmin + step;

				float xyZ = density(vec3(xmin, ymin, zmax));
				float XyZ = density(vec3(xmax, ymin, zmax));
				float xYZ = density(vec3(xmin, ymax, zmax));
				float XYZ = density(vec3(xmax, ymax, zmax));
				bool ZAllUnder = (xyZ < lowerIso) && (XyZ < lowerIso)
				&& (xYZ < lowerIso) && (XYZ < lowerIso);
				bool ZAllOver = (xyZ > upperIso) && (XyZ > upperIso)
				&& (xYZ > upperIso) && (XYZ > upperIso);

				isocell[ix * isocells * isocells + iy * isocells + iz] =
				!((zAllUnder && ZAllUnder) || (zAllOver && ZAllOver));

				xyz = xyZ;
				Xyz = XyZ;
				xYz = xYZ;
				XYz = XYZ;
				zAllUnder = ZAllUnder;
				zAllOver = ZAllOver;
			}
		}
	}

	size_t n = 0;
	for(size_t i = 0; i < isocell.size(); i++)
	if(isocell[i]) n++;
	printf("%lu / %lu", n, isocell.size());
	isocellVersion = density.getVersion();
	isocellIsolevel = isolevel;
}

void Polygonizer::polygonize(const vec3 &lower, const vec3 &upper,
		float resolution) {

	vec3 l = round(lower * vec3(1.f / resolution));
	vec3 u = round(upper * vec3(1.f / resolution));
	for (size_t ix = l.x; ix < u.x; ix++) {
		double x = (double(ix) + 0.5) * resolution;
		for (size_t iy = l.y; iy < u.y; iy++) {
			double y = (double(iy) + 0.5) * resolution;
			for (size_t iz = l.z; iz < u.z; iz++) {
				double z = (double(iz) + 0.5) * resolution;

				GRIDCELL gridCell;
				gridCell.p[0] = vec3(x, y, z);
				gridCell.p[1] = vec3(x + resolution, y, z);
				gridCell.p[2] = vec3(x + resolution, y + resolution, z);
				gridCell.p[3] = vec3(x, y + resolution, z);
				gridCell.p[4] = vec3(x, y, z + resolution);
				gridCell.p[5] = vec3(x + resolution, y, z + resolution);
				gridCell.p[6] = vec3(x + resolution, y + resolution,
						z + resolution);
				gridCell.p[7] = vec3(x, y + resolution, z + resolution);

				for (size_t iCell = 0; iCell < 8; iCell++) {
					gridCell.val[iCell] = density(gridCell.p[iCell]);
				}

				TRIANGLE tris[6];
				int nTris = Polygonise(gridCell, isolevel, tris);
				for (int iTri = 0; iTri < nTris; iTri++) {
					vec3 *ps = tris[iTri].p;

					// skip degenerate positions
					if (all(epsilonEqual(ps[0], ps[1], 1e-8f))
							|| all(epsilonEqual(ps[0], ps[2], 1e-8f))
							|| all(epsilonEqual(ps[2], ps[1], 1e-8f)))
					continue;

					uvec3 idc;
					for (size_t k = 0; k < 3; k++) {
						vec3 &p = tris[iTri].p[k];
						std::map<vec3, int>::iterator vit = vertexLookup.find(
								p);
						if (vit != vertexLookup.end()) {
							idc[k] = vit->second;
						} else {
							idc[k] = positions.size();
							positions.push_back(p);
							vertexLookup[p] = idc[k];
						}
					}
					indices.push_back(idc);
				}
			}
		}
	}
}

void Polygonizer::polygonize(float resolution) {
	vertexLookup.clear();
	positions.clear();
	indices.clear();

	const float isocellSize = 1.f / isocells;
	if (resolution < isocellSize) {
		updateIsocell();

		vec3 lower, upper;
		for (size_t ix = 0; ix < isocells - 1; ix++) {
			lower.x = double(ix) * isocellSize;
			upper.x = lower.x + isocellSize;
			for (size_t iy = 0; iy < isocells - 1; iy++) {
				lower.y = (double(iy) + 0.5) * isocellSize;
				upper.y = lower.y + isocellSize;
				for (size_t iz = 0; iz < isocells - 1; iz++) {
					if (!isocell[ix * isocells * isocells + iy * isocells + iz])
					continue;
					lower.z = (double(iz) + 0.5) * isocellSize;
					upper.z = lower.z + isocellSize;

					polygonize(lower, upper, resolution);
				}
			}
		}
	} else {
		polygonize(vec3(0.f), vec3(1.0f), resolution);
	}
}

#else
void Polygonizer::calculateLayer(std::vector<float> &layer, float resolution,
		size_t nSamples, float z) {
#pragma omp parallel for
	for (size_t iy = 0; iy < nSamples; iy++) {
		float y = iy * resolution;
		size_t offset = iy * nSamples;
		for (size_t ix = 0; ix < nSamples; ix++) {
			float x = ix * resolution;
			layer[offset + ix] = density(glm::vec3(x, y, z));
		}
	}
}

void Polygonizer::polygonize(float resolution, ProgressMonitor &progress) {
	vertexLookup.clear();
	mesh.positions.clear();
	mesh.indices.clear();

	mesh.positions.reserve(pow(1. / resolution, 2) * 8);
	mesh.indices.reserve(pow(1. / resolution, 2) * 8);
	size_t nSteps = 1.f / resolution;
	size_t nSamples = nSteps + 1;

	// precalculate 2 layers
	std::vector<float> layers[2];
	layers[0].resize(nSamples * nSamples);
	layers[1].resize(nSamples * nSamples);

	calculateLayer(layers[0], resolution, nSamples, 0.0);

	progress.begin("Polygonize", nSteps);

	for (size_t iz = 0; iz < nSteps; iz++) {
		std::vector<float> &zLayer = layers[iz % 2];
		std::vector<float> &ZLayer = layers[(iz + 1) % 2];
		float z = iz * resolution;
		float Z = z + resolution;
		calculateLayer(ZLayer, resolution, nSamples, Z);

		if (!progress.advance())
			break;

		for (size_t iy = 0; iy < nSteps; iy++) {
			float y = iy * resolution;
			float Y = y + resolution;
			size_t yOffset = iy * nSamples;
			size_t YOffset = yOffset + nSamples;

			GRIDCELL gridCell;
			gridCell.p[0] = vec3(0, y, z);
			gridCell.p[1] = vec3(0, y, z);
			gridCell.p[2] = vec3(0, Y, z);
			gridCell.p[3] = vec3(0, Y, z);
			gridCell.p[4] = vec3(0, y, Z);
			gridCell.p[5] = vec3(0, y, Z);
			gridCell.p[6] = vec3(0, Y, Z);
			gridCell.p[7] = vec3(0, Y, Z);

			for (size_t ix = 0; ix < nSteps; ix++) {
				float x = ix * resolution;
				float X = x + resolution;

				gridCell.p[0].x = x;
				gridCell.p[1].x = X;
				gridCell.p[2].x = X;
				gridCell.p[3].x = x;
				gridCell.p[4].x = x;
				gridCell.p[5].x = X;
				gridCell.p[6].x = X;
				gridCell.p[7].x = x;

				gridCell.val[0] = zLayer[yOffset + ix];
				gridCell.val[1] = zLayer[yOffset + ix + 1];
				gridCell.val[2] = zLayer[YOffset + ix + 1];
				gridCell.val[3] = zLayer[YOffset + ix];

				gridCell.val[4] = ZLayer[yOffset + ix];
				gridCell.val[5] = ZLayer[yOffset + ix + 1];
				gridCell.val[6] = ZLayer[YOffset + ix + 1];
				gridCell.val[7] = ZLayer[YOffset + ix];

				TRIANGLE tris[6];
				int nTris = Polygonise(gridCell, isolevel, tris);
				for (int iTri = 0; iTri < nTris; iTri++) {
					vec3 *ps = tris[iTri].p;

					// skip degenerate positions
					if (all(epsilonEqual(ps[0], ps[1], 1e-8f))
							|| all(epsilonEqual(ps[0], ps[2], 1e-8f))
							|| all(epsilonEqual(ps[2], ps[1], 1e-8f)))
						continue;

					uvec3 idc;
					for (size_t k = 0; k < 3; k++) {
						vec3 &p = tris[iTri].p[k];
						std::map<vec3, int>::iterator vit = vertexLookup.find(
								p);
						if (vit != vertexLookup.end()) {
							idc[k] = vit->second;
						} else {
							idc[k] = mesh.positions.size();
							mesh.positions.push_back(p);
							vertexLookup[p] = idc[k];
						}
					}
					mesh.indices.push_back(idc);
				}
			}
		}
	}

}

//void Polygonizer::polygonize(float resolution) {
//	vertexLookup.clear();
//	positions.clear();
//	indices.clear();
//
//	size_t nSteps = 1.f / resolution;
//	size_t nSamples = nSteps + 1;
//
//	// precalculate 2 layers
//	std::vector<float> layers[2];
//	layers[0].resize(nSamples*nSamples);
//	layers[1].resize(nSamples*nSamples);
//
//	calculateLayer(layers[0], resolution, nSamples, 0.0);
//
//	size_t nT = 0;
//	for (size_t ix = 0; ix < nSteps; ix++) {
//		double x = (double(ix) + 0.5) * resolution;
//		for (size_t iy = 0; iy < nSteps; iy++) {
//			double y = (double(iy) + 0.5) * resolution;
//			for (size_t iz = 0; iz < nSteps; iz++) {
//				double z = (double(iz) + 0.5) * resolution;
//
//				GRIDCELL gridCell;
//				gridCell.p[0] = vec3(x, y, z);
//				gridCell.p[1] = vec3(x + resolution, y, z);
//				gridCell.p[2] = vec3(x + resolution, y + resolution, z);
//				gridCell.p[3] = vec3(x, y + resolution, z);
//				gridCell.p[4] = vec3(x, y, z + resolution);
//				gridCell.p[5] = vec3(x + resolution, y, z + resolution);
//				gridCell.p[6] = vec3(x + resolution, y + resolution,
//						z + resolution);
//				gridCell.p[7] = vec3(x, y + resolution, z + resolution);
//
//				for (size_t iCell = 0; iCell < 8; iCell++) {
//					gridCell.val[iCell] = density(gridCell.p[iCell]);
//				}
//
//				TRIANGLE tris[6];
//				int nTris = Polygonise(gridCell, isolevel, tris);
//				nT += nTris;
//				for (int iTri = 0; iTri < nTris; iTri++) {
//					vec3 *ps = tris[iTri].p;
//
//					// skip degenerate positions
//					if (all(epsilonEqual(ps[0], ps[1], 1e-8f))
//							|| all(epsilonEqual(ps[0], ps[2], 1e-8f))
//							|| all(epsilonEqual(ps[2], ps[1], 1e-8f)))
//					continue;
//
//					uvec3 idc;
//					for (size_t k = 0; k < 3; k++) {
//						vec3 &p = tris[iTri].p[k];
//						std::map<vec3, int>::iterator vit = vertexLookup.find(
//								p);
//						if (vit != vertexLookup.end()) {
//							idc[k] = vit->second;
//						} else {
//							idc[k] = positions.size();
//							positions.push_back(p);
//							vertexLookup[p] = idc[k];
//						}
//					}
//					indices.push_back(idc);
//				}
//			}
//		}
//	}
//
//}
#endif
