#pragma once
// based off of https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_4.htm
// (mostly)

#include <raymath.h>
#include <iostream>
#include <vector>
#include <print>
#include "MPFW_Physics.h"

namespace MPFW {
	namespace Quake {
		namespace Maps{


			namespace Debug {
				enum DebugState {
					NONE,
					HEADER,
					MODELS,
					VERTICES,
					EDGES,
					TEXINFO,
					LEDGES,
					FACES,
					DONE
				};
			}


			Vector3 qVec2RLVec(Vector3 q);
			Vector3 RLVec2qVec(Vector3 q);

			struct DirectoryEntry {
				long offset;
				long size;
			};


			

			struct BSPHeader {
				long version; // must be 0x17
				DirectoryEntry entities; // list of entities
				DirectoryEntry planes; // planes
				DirectoryEntry miptex; // wall textures
				DirectoryEntry vertices; // map vertices
				DirectoryEntry visilist; // leaves visibility lists
				DirectoryEntry nodes; // bsp nodes
				DirectoryEntry texinfo; // texture info for faces
				DirectoryEntry faces; // faces of each surface
				DirectoryEntry lightmaps; // wall light maps
				DirectoryEntry clipnodes; // clip nodes
				DirectoryEntry leaves; // bsp leaves
				DirectoryEntry lface; // list of faces
				DirectoryEntry edges; // edges of faces
				DirectoryEntry ledges; // list of edges
				DirectoryEntry models; // list of models
			};

			struct qBoundingBox {
				Vector3 min,max;
			};

			struct qBoundingBoxShort {
				short minX, minY, minZ, maxX, maxY, maxZ;
			};

			struct qModel {
				qBoundingBox bound;
				Vector3 origin;
				long node_id0, // first bsp node
					 node_id1, // first clip node
					 node_id2, // second clip node
					 node_id3; // usually zero
				long numleafs;
				long face_id,
				     face_num;
			};
			struct Edge {
				unsigned short vertex0, vertex1;
			};
			struct Surface {
				Vector3 vectorS;
				float distS;
				Vector3 vectorT;
				float distT;
				unsigned long textureId;
				unsigned long animated;
			};
			struct Face {
				unsigned short planeId;
				unsigned short side;
				long ledgeId;
				unsigned short ledgeNum;
				unsigned short texinfoId;

				unsigned char typelight, baselight;
				unsigned char light[2];
				long lightmap;
			};


			struct CalculatedFace {
				std::vector<Vector3> vertices;
				std::vector<Vector2> texCoords;
				int glTextureId, glTextureWidth, glTextureHeight, glLightmapTextureId;
				bool hasLightMap = false;
				bool skyFace = false;
				bool triggerFace = false;
				bool clipFace = false;
				
				unsigned char baselight;

			};


			struct Plane {
				Vector3 normal;
				float dist;
				long type;
			};
			
			struct cMipHeader {
				long numtex;
				void *offset;
			};

			using MipHeader = std::vector<long>;

			struct cMiptex{
				unsigned char name[16];
				unsigned long width, height, 
							  offset1, offset2, offset4, offset8;
			};

			struct rlMipTex {
				std::string name;
				unsigned long width, height;
				unsigned long glTextureID;
				int type;
			};


			struct Node {
				long planeId;
				unsigned short front, back;
				qBoundingBoxShort box;
				unsigned short faceId, faceNum;
			};
			struct Leaf {
				long type, vislist;
				qBoundingBoxShort bound;
				unsigned short lfaceId, lfaceNum;
				unsigned char sndWater, sndSky, sndSlime, sndLava;
			};


			struct qColor {
				unsigned char r, g, b;
			};


			class Palette {
			public:
				std::vector<qColor> data;
				Palette() { data = std::vector<qColor>(256); }
				Palette(std::string path) { LoadPalette(path); }

				void LoadPalette(std::string path);
				~Palette() {}
			};

			struct MapData {
				BSPHeader header;

				std::vector<unsigned char> lightmap;
				std::vector<Vector3> vertices;
				std::vector<Edge> edges;
				std::vector<qModel> models;

				std::vector<Surface> texInfo;
				std::vector<Face> faces;
				std::vector<int> ledges;
				std::vector<rlMipTex> textures;
				
				std::vector<Plane> planes;

				std::vector<CalculatedFace> renderFaces;

				std::vector<rlMipTex> lightMaps; // purely for unloading <3
			};

			class MapFile {
			public:
				Debug::DebugState ds = Debug::NONE;
				MapData data;
				Palette* pal;
				Physics::CollisionMap* collMap;
				int modelsCDBG = 0;
				int verticesCDBG = 0;
				int edgesCDBG = 0;
				int texInfoCDBG = 0;
				int facesCDBG = 0;
				
				MapFile(){}

				[[warning("Prefer using an empty map file linked to a command handler for map loading with CFGs.")]]
				MapFile(std::string path) {
					LoadBSPMap(path);
				}

				void LoadBSPMap(std::string path);
				~MapFile(){}
			};

			BoundingBox GenerateBoundingBoxPolygon(std::vector<Vector3> poly);
		}
	}
}



template<>
struct std::formatter<MPFW::Quake::Maps::DirectoryEntry> {


	constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
	auto format(const MPFW::Quake::Maps::DirectoryEntry& in, std::format_context& ctx) const {
		return std::format_to(ctx.out(), "([MPFW::Quake::Maps::DirectoryEntry]: at pos {}, {}B in size)", in.offset, in.size);
	}

};


template<>
struct std::formatter<MPFW::Quake::Maps::MapData> {


	constexpr auto parse(std::format_parse_context& ctx) { return ctx.begin(); }
	auto format(const MPFW::Quake::Maps::MapData& in, std::format_context& ctx) const {
		return std::format_to(ctx.out(),
			"([MPFW::Quake::Maps::MapFile]:\n"
			"    Vertices: {}\n"
			"    Edges: {}\n"
			"    Models: {}\n"
			"    TexInfo: {}\n"
			"    Faces: {}\n"
			"    Ledges: {}\n"
			")", in.vertices.size(), in.edges.size(), in.models.size(), in.texInfo.size(), in.faces.size(), in.ledges.size());
	}

};

