
#include <raylib.h>
#include "MPFW_Quake.h"
#include "MPFW_Utils.h"
#include <sstream>
#include <fstream>
#include <string>
#include <iostream>
#include <algorithm>
#include "MPFW_Atlasing.h"

#include <rlgl.h>


void MPFW::Quake::Maps::MapFile::LoadBSPMap(std::string path)
{
	std::ifstream fileHandle(path, std::ios::binary); // open the file as bin

	std::string buffer;
	ds = Debug::NONE;
	if (fileHandle.is_open() && fileHandle.good()) {
		std::stringstream sstr;			// get out of working with streams
		sstr << fileHandle.rdbuf();    // as fast as possible
		buffer = sstr.str();		  // 3 LINES [WR SPEEDRUN]

		// operating on files as a string or a char array is simpler
	   // than writing some fancy << fuckery (even though it is nice
	  // to see some << fuckery work without any moving parts)

	}
	else {
		fileHandle.close();
		std::print("Bad file handle.\n");
		ds = Debug::DONE;
		throw std::runtime_error("Bad file handle in Quake BSP parsing.\n");
	}

	fileHandle.close(); // close it as soon as we can get file loaded
	// as to not disturb other programs


// we can now start the parsing
// hehehe >:3



// if file is smaller than header
	if (buffer.size() < 124) {
		std::print("Bad file content size.\n");
		throw std::runtime_error("File is smaller than header. Can't parse.");
	}

	ds = Debug::HEADER;
	data.header.version = 0; // initialize version for addition

	data.header.version += buffer[0];
	data.header.version += buffer[1] << 8;
	data.header.version += buffer[2] << 16;
	data.header.version += buffer[3] << 24;





	std::vector<DirectoryEntry> vecdir;



	for (int i = 4; i < 123; i += 8) {

		DirectoryEntry nd{ 0,0 }; // new directory;

		nd.offset = Utils::Strings::StringIndexToInteger_4b_le(buffer, i);
		nd.size = Utils::Strings::StringIndexToInteger_4b_le(buffer, i + 4);


		vecdir.push_back(nd);
	}



	// redistribution of entries
	data.header.entities = vecdir[0];
	data.header.planes = vecdir[1];
	data.header.miptex = vecdir[2];
	data.header.vertices = vecdir[3];
	data.header.visilist = vecdir[4];
	data.header.nodes = vecdir[5];
	data.header.texinfo = vecdir[6];
	data.header.faces = vecdir[7];
	data.header.lightmaps = vecdir[8];
	data.header.clipnodes = vecdir[9];
	data.header.leaves = vecdir[10];
	data.header.lface = vecdir[11];
	data.header.edges = vecdir[12];
	data.header.ledges = vecdir[13];
	data.header.models = vecdir[14];


	data.models.clear();
	data.edges.clear();
	data.faces.clear();
	data.ledges.clear();
	data.texInfo.clear();
	data.vertices.clear();



	// Loading Models
   // we're loading models first because that's 
  // what the spec does <3

#ifdef MPFW_QUAKE_SLOW_LOADING
	data.models.resize(data.header.models.size / 64);

	modelsCDBG = 0;
	for (int i = 0; i < data.header.models.size / 64; i++) {
		int base = data.header.models.offset + 64 * i;


		qModel qm;


		qm.bound.min.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
		qm.bound.min.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
		qm.bound.min.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
		qm.bound.max.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 12);
		qm.bound.max.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 16);
		qm.bound.max.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 20);


		qm.origin.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 24);
		qm.origin.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 28);
		qm.origin.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 32);

		qm.node_id0 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 36);
		qm.node_id1 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 40);
		qm.node_id2 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 44);
		qm.node_id3 = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 48);


		qm.numleafs = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 52);
		qm.face_id = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 56);
		qm.face_num = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 60);


		data.models[i] = qm;
		modelsCDBG++;


		// std::print("Model {}/{}\n", i, data.header.models.size / 64);

	}
#else
	std::string modelsStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.models.offset, data.header.models.size);
	data.models.resize(data.header.models.size / 64);
	std::memcpy(data.models.data(), modelsStr.data(), data.header.models.size);
#endif


	// parsing vertices
#ifdef MPFW_QUAKE_SLOW_LOADING
	verticesCDBG = 0;
	data.vertices.resize(data.header.vertices.size / 12);

	for (int i = 0; i < data.header.vertices.size / 12; i++) {
		int base = data.header.vertices.offset + (12 * i);

		Vector3 v;

		v.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
		v.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
		v.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);

		data.vertices[i] = v;

		// std::print("Vertex {}/{}\n", i, data.header.vertices.size / 12);
		verticesCDBG++;
	}

#else
	std::string verticesStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.vertices.offset, data.header.vertices.size);
	data.vertices.resize(data.header.vertices.size / 12);
	std::memcpy(data.vertices.data(), verticesStr.data(), data.header.vertices.size);
#endif

	// parsing edges
#ifdef MPFW_QUAKE_SLOW_LOADING
	edgesCDBG = 0;
	data.edges.resize(data.header.edges.size / 4);

	for (int i = 0; i < data.header.edges.size / 4; i++) {
		int base = data.header.edges.offset + (4 * i);
		Edge e;
		e.vertex0 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
		e.vertex1 = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 2);
		data.edges[i] = e;
		edgesCDBG++;
	}
#else

	std::string edgeStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.edges.offset, data.header.edges.size);
	data.edges.resize(data.header.edges.size / 4);
	std::memcpy(data.edges.data(), edgeStr.data(), data.header.edges.size);
#endif

	ds = Debug::TEXINFO;

#ifdef MPFW_QUAKE_SLOW_LOADING
	texInfoCDBG = 0;
	data.texInfo.resize(data.header.texinfo.size / 40);
	for (int i = 0; i < data.header.texinfo.size / 40; i++) {
		int base = data.header.texinfo.offset + (40 * i);
		Surface s;

		s.vectorS.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base);
		s.vectorS.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 4);
		s.vectorS.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 8);
		s.distS = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 12);
		s.vectorT.x = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 16);
		s.vectorT.y = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 20);
		s.vectorT.z = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 24);
		s.distT = Utils::Strings::StringIndexToFloat_4b_le(buffer, base + 28);
		s.textureId = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 32);
		s.animated = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 36);

		data.texInfo[i] = s;
		texInfoCDBG++;
	}
#else

	std::string texInfoStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.texinfo.offset, data.header.texinfo.size);
	data.texInfo.resize(data.header.texinfo.size / 40);
	std::memcpy(data.texInfo.data(), texInfoStr.data(), data.header.texinfo.size);
#endif

#ifdef MPFW_QUAKE_SLOW_LOADING
	for (int i = 0; i < data.header.ledges.size / 2; i++) {
		int base = data.header.ledges.offset + (2 * i);


		data.ledges.push_back(Utils::Strings::StringIndexToInteger_2b_le(buffer, base));

	}
#else


	std::string ledgesStr = Utils::Strings::IterativeStringExcerpt(buffer, data.header.ledges.offset, data.header.ledges.size);
	data.ledges.resize(data.header.ledges.size / 4);
	std::memcpy(data.ledges.data(), ledgesStr.data(), data.header.ledges.size);


#endif


	// parsing faces
#ifdef MPFW_QUAKE_SLOW_LOADING

	for (int i = 0; i < data.header.faces.size / 20; i++) {
		int base = data.header.faces.offset + (20 * i);

		Face f;

		f.planeId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base);
		f.side = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 2);

		f.ledgeId = (signed long)(Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 4));

		f.ledgeNum = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 8);


		f.texinfoId = Utils::Strings::StringIndexToInteger_2b_le(buffer, base + 10);

		f.typelight = buffer[base + 12];
		f.baselight = buffer[base + 13];
		f.light[0] = buffer[base + 14];
		f.light[1] = buffer[base + 15];
		f.lightmap = Utils::Strings::StringIndexToInteger_4b_le(buffer, base + 16);


		data.faces.push_back(f);

	}


#else

	std::string facesStr = Utils::Strings::IterativeStringExcerpt(
		buffer,
		data.header.faces.offset,
		data.header.faces.size
	);
	data.faces.resize(data.header.faces.size / 20);
	std::memcpy(data.faces.data(), facesStr.data(), data.header.faces.size);
#endif



	for (int i = 0; i < data.lightMaps.size(); i++) {
		rlUnloadTexture(data.lightMaps[i].glTextureID);
	}
	data.lightMaps.clear();

	std::print("Loading mip textures manually for now\n");


	for (int i = 0; i < data.textures.size(); i++) {
		rlUnloadTexture(data.textures[i].glTextureID);
	}
	cMipHeader cmh;

	cmh.numtex = Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset);
	std::vector<long> texOffsets;
	// std::memcpy(texOffsets.data(), Utils::Strings::IterativeStringExcerpt(buffer, data.header.miptex.offset + 4, data.header.miptex.size - 4).data(), data.header.miptex.size - 4);
	for (int i = 0; i < cmh.numtex; i++) {
		texOffsets.push_back(Utils::Strings::StringIndexToInteger_4b_le(buffer, data.header.miptex.offset + 4 + 4 * i));
	}

	
	for (int i = 0; i < cmh.numtex; i++) {
		unsigned int base = static_cast<unsigned int>(texOffsets[i]) + (unsigned)data.header.miptex.offset;

		 // this stuff is ¤ undocumented! ¤
	    // screw the unofficial quake specs
		if (texOffsets[i] == -1) {
			// is a texoffset of 0xFFFFFFFF a reference to the fact that
		   // it starts right after the mipheader? probably not, but it
		  // makes the texture loading code happy so... :shrug:
			base = (unsigned)data.header.miptex.offset + 4 + texOffsets.size() * 4;
		}

		cMiptex t;
		std::memcpy(&t, Utils::Strings::IterativeStringExcerpt(buffer, base, 40).data(), 40);

		Image img = GenImageColor(t.width, t.height, BLACK);
		for (int y = 0; y < t.height; y++) {
			for (int x = 0; x < t.width; x++) {
				ImageDrawPixel(&img, x, y,
					{
						pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].r,
						pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].g,
						pal->data[(unsigned char)buffer[base + t.offset1 + (t.width * y + x)]].b,
						255
					}
				);
			}
		}

		Texture2D temp = LoadTextureFromImage(img);

		rlMipTex mt;
		mt.name = std::string((const char*)t.name);
		mt.glTextureID = temp.id;
		mt.height = temp.height;
		mt.width = temp.width;
		mt.type = temp.format;

		UnloadImage(img);
		data.textures.push_back(mt);

	}

	data.renderFaces.clear();

	data.lightmap.clear();

	data.lightmap.resize(data.header.lightmaps.size);
	std::memcpy(data.lightmap.data(), Utils::Strings::IterativeStringExcerpt(buffer, data.header.lightmaps.offset, data.header.lightmaps.size).data(), data.header.lightmaps.size);

	std::print("Precalculating faces and texture coordinates\n");

	data.planes.clear();
	data.planes.resize(data.header.planes.size / sizeof(Plane));

	std::memcpy(
		data.planes.data(),
		Utils::Strings::IterativeStringExcerpt(buffer, data.header.planes.offset, data.header.planes.size).data(),
		data.header.planes.size
	);

	std::vector<Physics::Polygon> collPolygons;
	std::vector<Vector3> totalRlVec;
	for (int i = 0; i < data.faces.size(); i++) {
		CalculatedFace cface;

		for (int k = 0; k < data.faces[i].ledgeNum; k++) {
			if (data.ledges[(unsigned int)(data.faces[i].ledgeId) + k] < 0) {
				cface.vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
					data.vertices[
						data.edges[
							abs(
								data.ledges[
									data.faces[i].ledgeId + k
								]
							)
						].vertex0
					]
				));
			}
			else {
				cface.vertices.push_back(MPFW::Quake::Maps::qVec2RLVec(
					data.vertices[
						data.edges[
							abs(
								data.ledges[
									data.faces[i].ledgeId + k
								]
							)
						].vertex1
					]
				));
			}
			totalRlVec.push_back(cface.vertices[cface.vertices.size() - 1]); // push back the last vertex for collision

		}


		cface.glTextureId = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].glTextureID;
		cface.glTextureWidth = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width;
		cface.glTextureHeight = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height;

		collPolygons.push_back(cface.vertices);
		for (int j = 0; j < cface.vertices.size(); j++) {

			cface.texCoords.push_back(Vector2(
				(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorS) + data.texInfo[data.faces[i].texinfoId].distS) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width,
				(Vector3DotProduct(MPFW::Quake::Maps::RLVec2qVec(cface.vertices[j]), data.texInfo[data.faces[i].texinfoId].vectorT) + data.texInfo[data.faces[i].texinfoId].distT) / data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height
			));
		}

		cface.baselight = data.faces[i].baselight;
		if (
			Utils::Strings::IterativeStringExcerpt(data.textures[data.texInfo[data.faces[i].texinfoId].textureId].name, 0, 3) == "sky"
			) {
			cface.skyFace = true;
		}
		if (data.textures[data.texInfo[data.faces[i].texinfoId].textureId].name == "clip") cface.clipFace = true;
		if (data.textures[data.texInfo[data.faces[i].texinfoId].textureId].name == "trigger") cface.triggerFace = true;
		int lmbase = data.faces[i].lightmap;
		if (lmbase != -1) {

			// int lmw = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].width;
			// int lmh = data.textures[data.texInfo[data.faces[i].texinfoId].textureId].height;


			std::vector<Vector3> polygon;

			for (int v = 0; v < cface.vertices.size(); v++) {
				polygon.push_back(RLVec2qVec(cface.vertices[v]));
			}

			BoundingBox faceBB = GenerateBoundingBoxPolygon(polygon);

			int planeType = data.planes[data.faces[i].planeId].type;
			int valueForgotten =
				planeType == 0 || planeType == 3 ? 0 :
				planeType == 1 || planeType == 4 ? 1 :
				2;

			int lmw, lmh;

			switch (valueForgotten) {
			case 0: // forgetting x

				lmw = static_cast<int>((faceBB.max.y - faceBB.min.y) / 16);
				lmh = static_cast<int>((faceBB.max.z - faceBB.min.z) / 16);

				break;
			case 1: // forgetting y

				lmw = static_cast<int>((faceBB.max.x - faceBB.min.x) / 16);
				lmh = static_cast<int>((faceBB.max.z - faceBB.min.z) / 16);

				break;
			case 2: // forgetting z

				lmw = static_cast<int>((faceBB.max.x - faceBB.min.x) / 16);
				lmh = static_cast<int>((faceBB.max.y - faceBB.min.y) / 16);

				break;
			default:
				throw;
			}

			Image lightmap = GenImageColor(lmw, lmh, BLANK);
			cface.hasLightMap = true;
			for (int y = 0; y < lmh; y++) {
				for (int x = 0; x < lmw; x++) {
					unsigned char lightValue = data.lightmap[y * lmw + x + lmbase];
					unsigned char invertedLightValue = 255 - lightValue;
					// std::cout << "light value : " << (int)invertedLightValue << "\n";
					ImageDrawPixel(&lightmap, x, y, {
						0,
						0,
						0,
						invertedLightValue
						});
				}
			}

			Texture lightmapTexture = LoadTextureFromImage(lightmap);

			rlMipTex lightmapRLMipTex;
			lightmapRLMipTex.glTextureID = lightmapTexture.id;
			lightmapRLMipTex.width = lightmapTexture.width;
			lightmapRLMipTex.height = lightmapTexture.height;
			lightmapRLMipTex.type = lightmapTexture.format;
			data.lightMaps.push_back(lightmapRLMipTex);

			cface.glLightmapTextureId = lightmapTexture.id;
			SetTextureFilter(lightmapTexture, TEXTURE_FILTER_BILINEAR);
			UnloadImage(lightmap);

		}

		data.renderFaces.push_back(cface);


	}



	Physics::CollisionMapGenParams cmgp;
	
	*collMap = Physics::GenerateCollisionMap(collPolygons, totalRlVec, cmgp, std::format("{}", std::hash<std::string>{}(path)));
}
Vector3 MPFW::Quake::Maps::qVec2RLVec(Vector3 q) {
	return { q.y / 2, q.z / 2, q.x / 2 };
}
Vector3 MPFW::Quake::Maps::RLVec2qVec(Vector3 q) {
	return { q.z * 2, q.x * 2, q.y * 2 };
}

BoundingBox MPFW::Quake::Maps::GenerateBoundingBoxPolygon(std::vector<Vector3> poly)
{
	std::vector<Vector3> polyCopy = poly;

	std::sort(polyCopy.begin(), polyCopy.end(), [](Vector3 a, Vector3 b) {
		return Vector3Distance({ 0,0,0 }, a) < Vector3Distance({ 0,0,0 }, b);
		});

	BoundingBox retval;
	retval.min = polyCopy[0];
	retval.max = polyCopy[polyCopy.size() - 1];
	return retval;
}

void MPFW::Quake::Maps::Palette::LoadPalette(std::string path)
{
	std::ifstream i(path, std::ios::binary);
	if (i.is_open() && i.good()) {
		data.clear();
		data.resize(256);
		std::stringstream s;
		s << i.rdbuf();
		std::string d = s.str();

		std::memcpy(data.data(), d.data(), 256 * 3);

	}
	else throw std::runtime_error("can't open palette file " + path);
}
