#include "TextureMapper.h"

#include <glm/mat3x3.hpp>

#include "stb_image_write.h"

#include <iostream>

TextureMapper::TextureMapper(Density &density, size_t textureSize) :
		density(density), textureSize(textureSize), albedo(
				textureSize * textureSize), normal(textureSize * textureSize), roughness(
				textureSize * textureSize), darkestColor(0.3, 0.25, 0.2, 1.0), brightestColor(
				0.9, 0.85, 0.8, 1.0) {

}
void TextureMapper::setTextureSize(size_t size) {
	textureSize = size;
	albedo.resize(textureSize * textureSize);
	normal.resize(textureSize * textureSize);
	roughness.resize(textureSize * textureSize);
}

size_t TextureMapper::getTextureSize() {
	return textureSize;
}

void TextureMapper::map(const vec3v_t &inVertices, const vec3v_t &inNormals,
		const uvec3v_t &inIndices) {
	float texf = 1. / textureSize;

	vertices.clear();
	vertices.reserve(inIndices.size() * 3);
	normals.clear();
	normals.reserve(inIndices.size() * 3);
	texcoords.clear();
	texcoords.reserve(inIndices.size() * 3);

	// 2 px margin
	size_t marginPixels = 2;
	float margin = marginPixels * texf;

	std::fill(albedo.begin(), albedo.end(), 255.f * brightestColor);
	std::fill(normal.begin(), normal.end(), ucvec4_t(255, 255, 255, 255));
	std::fill(roughness.begin(), roughness.end(), 255);

	int rects = ceil(sqrt(inIndices.size())), rx = 0, ry = 0;
	float rectSizePixel = floor(((float)textureSize - (rects + 1) * marginPixels) / rects);
	float rectSize = rectSizePixel / textureSize;
	printf("Rects: %d, Size: %f", rects, rectSize);
	for (size_t i = 0; i < inIndices.size(); i++) {
		glm::vec3 p[3], n[3];
		glm::uvec3 idx = inIndices[i];
		indices.push_back(
				glm::uvec3(vertices.size(), vertices.size() + 1,
						vertices.size() + 2));
		for (size_t j = 0; j < 3; j++) {
			p[j] = inVertices[idx[j]];
			vertices.push_back(p[j]);
			n[j] = inNormals[idx[j]];
			normals.push_back(n[j]);
		}

		glm::vec3 ex = glm::normalize(p[1] - p[0]);
		glm::vec3 ey = glm::normalize(glm::cross(ex, p[2] - p[0]));
		glm::vec3 ez = glm::normalize(glm::cross(ex, ey));
		glm::mat3 base = glm::mat3(ex, ey, ez);
		glm::vec3 t[3] = { p[0] * base, p[1] * base, p[2] * base };
		glm::vec3 lower = min(min(t[0], t[1]), t[2]);
		glm::vec3 upper = max(max(t[0], t[1]), t[2]);
		t[0] -= lower;
		t[1] -= lower;
		t[2] -= lower;
		glm::vec3 extent = upper - lower;
		float s = std::max(std::max(extent.x, extent.y), extent.z);
		t[0] *= 0.9 * rectSize / s;
		t[1] *= rectSize / s;
		t[2] *= rectSize / s;
		glm::vec3 off(margin + rx * (rectSize + margin), 0, margin + ry * (rectSize + margin));
		t[0] += off;
		t[1] += off;
		t[2] += off;
		texcoords.push_back(glm::vec2(t[0].x, t[0].z));
		texcoords.push_back(glm::vec2(t[1].x, t[1].z));
		texcoords.push_back(glm::vec2(t[2].x, t[2].z));
#if 0
		//if (rx == 0 && ry == 0) {
		t[0].y = 0;
		t[1].y = 0;
		t[2].y = 0;
//			cout << "ftex " << texf << endl;
//			cout << "t[0]  " << t[0].x << " " << t[0].z << endl;
//			cout << "t[1]  " << t[1].x << " " << t[1].z << endl;
//			cout << "t[2]  " << t[2].x << " " << t[2].z << endl;
//			cout << "t[0]p " << t[0].x * ntex << " " << (1 - t[0].z) * ntex << endl;
//			cout << "t[1]p " << t[1].x * ntex << " " << (1 - t[1].z) * ntex << endl;
//			cout << "t[2]p " << t[2].x * ntex << " " << (1 - t[2].z) * ntex << endl;
		// fill texture
		// fill whole rect for now
		int rpix = floor(rectSize * textureSize);

		glm::vec3 eb = t[1] - t[0];
		float leb = glm::length(eb);
		glm::vec3 ec = t[2] - t[0];
		float lec = glm::length(ec);
//		mat2 textureBase = mat2(vec2(eb.x, eb.z), vec2(ec.x, ec.z));
//		mat2 itextureBase = inverse(textureBase);
		glm::mat3 textureBase = glm::mat3(eb, glm::cross(eb, ec), ec);
		glm::mat3 itextureBase = glm::inverse(textureBase);

//		eb = normalize(eb);
//		ec = normalize(ec);

		glm::vec3 db = p[1] - p[0];
		glm::vec3 dc = p[2] - p[0];

		glm::vec3 mb = glm::normalize(p[1] - p[0]);
		glm::vec3 mc = glm::normalize(p[2] - p[0]);
//		mat2 faceBase = mat2(vec2(mb.x, mb.z), vec2(mc.x, mc.z));
//		mat2 ifaceBase = inverse(faceBase);

		glm::mat3 faceBase = glm::mat3(db, glm::cross(db, dc), dc);
		glm::mat3 ifaceBase = glm::inverse(faceBase);

		glm::vec3 tmin = min(min(t[0], t[1]), t[2]);
		tmin = tmin * glm::vec3((float) textureSize);
		tmin = floor(tmin) - glm::vec3(2.0f);
		//tmin = tmin - glm::vec3(1.0f);
		tmin = max(tmin, glm::vec3(0.1));

//		glm::vec3 tmax = min(
//				ceil(
//						max(max(t[0], t[1]), t[2])
//								* glm::vec3((float) textureSize))
//						+ glm::vec3(1.0f), glm::vec3(textureSize - 1));

		glm::vec3 tmax = max(max(t[0], t[1]), t[2]);
		tmax = tmax * glm::vec3((float) textureSize);
		tmax = ceil(tmax) + glm::vec3(2.0f);
		tmax = min(tmax, glm::vec3(textureSize));

		// TODO: borders!
		for (int ix = tmin.x; ix < tmax.x; ix++) {
			for (int iy = (textureSize - tmax.z); iy < (textureSize - tmin.z);
					iy++) {

				// https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle#2049593

//				for (int iy = tmin.z; iy < tmax.z; iy++) {

//					cout << "pix " << ix << " " << iy << endl;
//					cout << "uv  " << float(ix) * texf << " " << 1.f - float(iy) * texf << endl;

				// pixel to uv space
				glm::vec3 res = glm::vec3(float(ix) * texf, 0.f,
						1.f - float(iy) * texf) - t[0];
				// cout << "r   = " << r.x << " " << r.y << endl;
				glm::vec3 local = faceBase * itextureBase * res;

				// cout << "res = " << res.x << " " << res.y << endl;
//				vec2 res = ifaceBase * textureBase * r * faceBase;
				//
//				cout << "pix " << ix << " " << res.x << " " << res.y << " " << res.z << endl;
//				float fb = dot(r, eb) / leb;
//				float fc = dot(r, ec) / lec;
				glm::vec3 sp = p[0] + local; //res.x * mb + res.y * mc;
				float v = (float) density(sp, false);
//
				//v = pow(v, 3);
				// default color
//					float r = 0.8 + v * 0.2, g = 0.75 + 0.2 * v, b = 0.7
//							+ 0.2 * v;

				glm::vec3 rgb = glm::vec3(255)
						* glm::mix(darkestColor, brightestColor, v);
				albedo[ix + (textureSize - iy) * textureSize][0] = rgb.x;
				albedo[ix + (textureSize - iy) * textureSize][1] = rgb.y;
				albedo[ix + (textureSize - iy) * textureSize][2] = rgb.z;

//				diff[ix + iy * ntex] = v * 255;
//				diff[ix + iy * ntex][0] = (32 * rx) % 255;
//				diff[ix + iy * ntex][1] = (32 * ry) % 255;
//				diff[ix + iy * ntex][2] = 0; //(0.5 + sp.z) * 255;

//				float rd = 1.f; //0.7 + 0.3 * length(r) * rects;
//				albedo[ix + (textureSize - iy) * textureSize][0] = rd * (sp.x) * 255;
//				albedo[ix + (textureSize - iy) * textureSize][1] = rd * (sp.y) * 255;
//				albedo[ix + (textureSize - iy) * textureSize][2] = rd * (sp.z) * 255;

//					albedo[ix + iy * textureSize][0] = length(res) * rects * 255;
//					albedo[ix + iy * textureSize][1] = length(res) * rects * 255;
//					albedo[ix + iy * textureSize][2] = length(res) * rects * 255;

//									diff[ix + iy * ntex][0] = r.x * rects * 200;
//									diff[ix + iy * ntex][1] = -r.z * rects * 200;
//									diff[ix + iy * ntex][2] = 0;

//													diff[ix + iy * ntex][0] = 128 + (res.x - 0.5) * 200;
//													diff[ix + iy * ntex][1] = 128 + (res.y - 0.5) * 200;
//													diff[ix + iy * ntex][2] = 0; //res.z * 255;

			}
			//}
		}
		rx++;
		if (rx >= rects) {
			rx = 0;
			ry++;
		}
#endif
	}
//
//	for (int ix = 0; ix < ntex; ix++) {
//		for (int iy = 0; iy < ntex; iy++) {
//
//			float v = (float) noise.octaveNoise0_1(ix * texf * frequency,
//					iy * texf * frequency, 0.5 * frequency, 4);
//			diff[ix + (ntex - iy) * ntex] = 5 + v * 250;
//		}
//	}
	stbi_write_png("albedo.png", textureSize, textureSize, 3, albedo.data(), 0);
}
