#ifndef TRIANGLE_H
#define TRIANGLE_H
#include <core/bind/core_bind.h>
#include <core/os/file_access.h>
#include <core/reference.h>
#include <core/resource.h>
#include <scene/resources/mesh.h>
#include <cassert>

class TriangleSet : public Reference {
	GDCLASS(TriangleSet, Reference)
protected:
	PoolVector<Vector3> vertices;
	PoolVector<Vector3> normals;
	PoolVector<Vector2> uvs1;
	PoolVector<Vector2> uvs2;
	PoolVector<int> indices;
	static void _bind_methods();
	float minp[3];
	float maxp[3];
	float cd[3];
	float minn[3];
	float maxn[3];
	float cdn[3];
	int minx, miny, maxx, maxy;
	PoolVector<uint8_t> get_data(const Ref<Image> &vimage, const Ref<Image> &nimage) {
		int i, j, width = vimage->get_width(), height = vimage->get_height();
		const PoolVector<uint8_t> &vdata = vimage->get_data(), &ndata = nimage->get_data();
		assert(vdata.size() == ndata.size());
		uint32_t *h_table = memnew_arr(uint32_t, height + 1);
		h_table[0] = (uint32_t)height;
		uint64_t *src_buffer = memnew_arr(uint64_t, width);
		uint8_t *dst_buffer = memnew_arr(uint8_t, width * 9);
		PoolVector<uint8_t> ret;
		if (minx == maxx || miny == maxy)
			return ret;
		/* map of height offsets */
		ret.resize(ret.size() + height * 4 + 4);
		const uint32_t *pxdata = (const uint32_t *)vdata.read().ptr();
		const uint32_t *npxdata = (const uint32_t *)vdata.read().ptr();
		int out_count = ret.size();
		for (i = miny; i < maxy + 1; i++) {
			h_table[i + 1] = out_count;
			for (j = minx; j < maxx + 1; j++) {
				uint64_t data_l = (pxdata[i * width + j] & 0xFFFFFFU);
				uint64_t data_h = (npxdata[i * width + j] & 0xFFFFFFU);
				src_buffer[j] = (data_l | (data_h << 24)) & 0xFFFFFFFFFFFFULL;
			}
			uint8_t count = 1;
			uint64_t cur = src_buffer[0];
			int dst_count = 0;
			for (j = minx + 1; j < maxx + 1; j++) {
				if (cur == src_buffer[j] && count < 255 && j < maxx)
					count++;
				else {
					int k;
					for (k = 0; k < 6; k++) {
						dst_buffer[dst_count++] = (uint8_t)(cur & 0xff);
						cur >>= 8;
					}
					dst_buffer[dst_count++] = count;
					cur = src_buffer[j];
					count = 1;
				}
			}
			if (ret.size() < out_count + dst_count)
				ret.resize(out_count + dst_count * 2);
			uint8_t *dptr = ret.write().ptr();
			memcpy(&dptr[out_count], dst_buffer, dst_count);
			out_count += dst_count;
		}
		uint32_t *table_p = (uint32_t *)ret.read().ptr();
		memcpy(table_p, h_table, (height + 1) * 4);
		return ret;
	}
	static inline void draw_hline(Image *image, const float *v1, const float *v2) {
		if (v1[0] < 0 && v2[0] < 0)
			return;
		if (v1[0] >= v2[0])
			return;
		if (v1[0] >= image->get_width())
			return;
		float l = (v2[0] - v1[0]);
		Color c;
		for (int i = MAX(0, (int)v1[0] - 1); i <= MIN(image->get_width() - 1, (int)v2[0] + 1); i++) {
			float t = ((float)i - v1[0] + 1) / (l + 2.0f);
			t = CLAMP(t, 0.0f, 1.0f);
			c.r = Math::lerp(v1[2], v2[2], t);
			c.g = Math::lerp(v1[3], v2[3], t);
			c.b = Math::lerp(v1[4], v2[4], t);
			image->set_pixel(i, v1[1], c);
		}
	}
	static inline void flat_bottom_triangle(Image *image,
			const float *v1, const float *v2, const float *v3) {
		if ((v2[1] - v1[1]) < 1.0)
			return;
		double bdiv = (v2[1] - v1[1]);
		for (int scanlineY = v1[1]; scanlineY <= v2[1]; scanlineY++) {
			float t = ((double)((double)scanlineY - v1[1])) / bdiv;
			t = CLAMP(t, 0.0f, 1.0f);
			if (scanlineY < 0 || scanlineY >= image->get_height())
				continue;
			float cx1[5], cx2[5];
			cx1[0] = Math::lerp(v1[0], v2[0], t);
			cx1[1] = scanlineY;
			cx1[2] = Math::lerp(v1[2], v2[2], t);
			cx1[3] = Math::lerp(v1[3], v2[3], t);
			cx1[4] = Math::lerp(v1[4], v2[4], t);
			cx2[0] = Math::lerp(v1[0], v3[0], t);
			cx2[1] = scanlineY;
			cx2[2] = Math::lerp(v1[2], v3[2], t);
			cx2[3] = Math::lerp(v1[3], v3[3], t);
			cx2[4] = Math::lerp(v1[4], v3[4], t);
			draw_hline(image, cx1, cx2);
		}
	}
	static inline void flat_top_triangle(Image *image,
			const float *v1, const float *v2, const float *v3) {
		if ((v3[1] - v1[1]) < 1.0)
			return;
		double bdiv = (v3[1] - v1[1]);
		for (int scanlineY = v3[1]; scanlineY > v1[1] - 1; scanlineY--) {
			float t = (double)(v3[1] - (double)scanlineY) / bdiv;
			t = CLAMP(t, 0.0f, 1.0f);
			if (scanlineY < 0 || scanlineY >= image->get_height())
				continue;
			float cx1[5], cx2[5];
			cx1[0] = Math::lerp(v3[0], v1[0], t);
			cx1[1] = scanlineY;
			cx1[2] = Math::lerp(v3[2], v1[2], t);
			cx1[3] = Math::lerp(v3[3], v1[3], t);
			cx1[4] = Math::lerp(v3[4], v1[4], t);
			cx2[0] = Math::lerp(v3[0], v2[0], t);
			cx2[1] = scanlineY;
			cx2[2] = Math::lerp(v3[2], v2[2], t);
			cx2[3] = Math::lerp(v3[3], v2[3], t);
			cx2[4] = Math::lerp(v3[4], v2[4], t);
			draw_hline(image, cx1, cx2);
		}
	}
	inline float distance_squared(const float *v1, const float *v2) {
		return Vector2(v1[0], v1[1]).distance_squared_to(Vector2(v2[0], v2[1]));
	}
	inline void draw_triangle(Image *image,
			const float *v1,
			const float *v2,
			const float *v3) {
		if (v1[1] == v2[1] && v1[1] == v3[1])
			return;
		float d12 = distance_squared(v1, v2);
		float d13 = distance_squared(v1, v3);
		const float *points[] = { v1, v2, v3 };
		int i, j;
		for (i = 0; i < 3; i++)
			for (j = 0; j < 3; j++) {
				if (i == j)
					continue;
				if (points[i][1] < points[j][1])
					SWAP(points[i], points[j]);
			}
		if (points[0][1] == points[1][1]) {
			if (points[2][0] - points[0][0] < points[2][0] - points[1][0])
				flat_top_triangle(image, points[1], points[0], points[2]);
			else
				flat_top_triangle(image, points[0], points[1], points[2]);
		} else if (points[1][1] == points[2][1]) {
			if (points[1][0] - points[0][0] > points[2][0] - points[0][0])
				flat_bottom_triangle(image, points[0], points[2], points[1]);
			else
				flat_bottom_triangle(image, points[0], points[1], points[2]);
		} else {
			float y01 = points[1][1] - points[0][1];
			float y02 = points[2][1] - points[0][1];
			float p4[5];
			// Vector2 p4(points[0][0] + (y01 / y02) * (points[2][0] - points[0][0]), points[1][1]);
			float t = y01 / y02;
			assert(t <= 1.0f && t >= 0.0f);
			p4[0] = Math::lerp(points[0][0], points[2][0], t);
			p4[1] = points[1][1];
			p4[2] = Math::lerp(points[0][2], points[2][2], t);
			p4[3] = Math::lerp(points[0][3], points[2][3], t);
			p4[4] = Math::lerp(points[0][4], points[2][4], t);
			if (points[1][0] - points[0][0] > p4[0] - points[0][0])
				flat_bottom_triangle(image, points[0], p4, points[1]);
			else
				flat_bottom_triangle(image, points[0], points[1], p4);
			if (points[2][0] - points[1][0] < points[2][0] - p4[0])
				flat_top_triangle(image, p4, points[1], points[2]);
			else
				flat_top_triangle(image, points[1], p4, points[2]);
		}
	}

public:
	TriangleSet() {
	}
	~TriangleSet() {
	}
	void normalize_deltas();
	static inline float get_area(Vector3 p1, Vector3 p2, Vector3 p3) {
		return (p2 - p1).cross(p3 - p1).length() / 2.0;
	}
	static inline Vector3 get_baricentric(Vector3 pt, Vector3 p1, Vector3 p2, Vector3 p3) {
		Vector3 p;
		float area = get_area(p1, p2, p3);
		if (area == 0.0) {
			printf("bad triangle %ls %ls %ls\n", String(p1).c_str(), String(p2).c_str(), String(p3).c_str());
			return Vector3(-1, -1, -1);
		}
		Vector3 n = (p2 - p1).cross(p3 - p1);
		float d = n.dot(p1);
		float denom = n.dot(n);
		if (denom != 0.0) {
			float t = (-n.dot(pt) + d) / denom;
			p = pt + n * t;
		} else
			p = pt;
		float c = get_area(p2, p3, p);
		float u = c / area;
		float e = get_area(p3, p1, p);
		float v = e / area;
		float w = 1.0f - u - v;
		if (!(u < 0 || v < 0 || w < 0))
			printf("%f %f %f %f %f %f %ls %f %f %f\n", u, v, w, d, denom, n.length(), String(p).c_str(), c, e, area);
		return Vector3(u, v, w);
	}
	/* Same topology */
	void create_from_array_shape(const Array &arrays_base, const Array &arrays_shape);
	/* Close but not the same topology */
	void create_from_array_difference(const Array &arrays_base, int uv_index1, const Array &arrays_shape, int uv_index2);
	inline void update_bounds(float x, float y) {
		if (minx > (int)x)
			minx = (int)x;
		if (miny > (int)y)
			miny = (int)y;
		if (maxx < ceil(x))
			maxx = (int)(ceil(x));
		if (maxy < ceil(y))
			maxy = (int)(ceil(y));
	}
	void draw(Ref<Image> vimage, Ref<Image> nimage, int uv_index);
	Vector3 get_min() {
		return Vector3(minp[0], minp[1], minp[2]);
	}
	Vector3 get_max() {
		return Vector3(maxp[0], maxp[1], maxp[2]);
	}
	Vector3 get_min_normal() {
		return Vector3(minn[0], minn[1], minn[2]);
	}
	Vector3 get_max_normal() {
		return Vector3(maxn[0], maxn[1], maxn[2]);
	}
	void save(Ref<_File> fd, const String &shape_name, Ref<Image> vimage, Ref<Image> nimage);
};
#endif
