#include "bsp/BSP.h"

#include <SDL3/SDL_iostream.h>
#include <SDL3/SDL_log.h>
#include <set>


#include <cstring>

/* changes handedness by swapping z and y */
template<typename T>
static inline void change_swizzle(T& v) {
	auto tmp = v.y;
	v.y = v.z;
	v.z = tmp;
}

using namespace HLBSP;

static inline void copy_data(void* file_data, std::string& dst, Lump& lump) {
	dst.resize(lump.len);
	std::memcpy(dst.data(), (uint8_t*)file_data + (size_t)lump.offset, lump.len);
}

template<typename T>
static inline void copy_data(void* file_data, std::vector<T>& dst, Lump& lump) {
	dst.resize(lump.len / sizeof(T));
	std::memcpy(dst.data(), ((uint8_t*)file_data) + lump.offset, lump.len);
}

static inline glm::vec2 calc_tex_coords(glm::vec3 v, const TexInfo& t) {
	change_swizzle(v);
	return glm::vec2(
		t.shift_s + glm::dot(v, t.shift_s_dir),
		t.shift_t + glm::dot(v, t.shift_t_dir)
	);
}

void BSP::load_vertices() {
	SDL_Log("Loading vertices");
	std::set<int> present_faces;
	std::vector<Face> visible_faces;
	// if (visibility_test) {
	// 	auto leaf_idx = determine_leaf(cam_pos);

	// 	auto fr_planes = frustum(view);

	// 	if (leaf_idx == last_leaf)
	// 		return;

	// 	last_leaf = leaf_idx;
	// 	auto& cam_leaf = leaves[leaf_idx];


	// 	std::vector<Leaf> visible_leafs;
	// 	for (auto& leaf : leaves) {

	// 		const auto min = leaf.bb_mins;
	// 		const auto max = leaf.bb_maxes;

	// 		const glm::vec3 bounding_planes[8] = {
	// 			{ min.x, min.y, min.z },
	// 			{ max.x, min.y, min.z },
	// 			{ max.x, max.y, min.z },
	// 			{ min.x, max.y, min.z },
	// 			{ min.x, min.y, max.z },
	// 			{ max.x, min.y, max.z },
	// 			{ max.x, max.y, max.z },
	// 			{ min.x, max.y, max.z },
	// 		};

	// 		if (determine_visibility(cam_leaf, leaf, fr_planes, bounding_planes))
	// 			visible_leafs.push_back(leaf);
	// 	}

	// 	for (const auto& leaf : visible_leafs) {
	// 		for (size_t i = 0; i < leaf.n_mark_surfaces; i++) {
	// 			auto idx = mark_surfaces[leaf.first_mark_surface_idx + i];
	// 			if (present_faces.contains(idx))
	// 				continue;

	// 			present_faces.insert(idx);
	// 			visible_faces.push_back(faces[idx]);
	// 		}
	// 	}
	// } else {
		visible_faces = faces;
	// }

	textured_vertices.clear();

	for (auto& face : visible_faces) {
		auto& tex_info = tex_infos[face.tex_info_idx];

		for (int16_t i = 1, j = 2; j < face.n_surf_edges; i++, j++) {
			vec3 pos[3] = {
			processed_vertices[face.first_surf_edge_idx],
			processed_vertices[face.first_surf_edge_idx+i],
			processed_vertices[face.first_surf_edge_idx+j],
			};

			vec3 face_normal = glm::normalize(glm::cross(pos[1] - pos[0], pos[2] - pos[0]));

			textured_vertices.push_back(Vertex{
				.pos = pos[0],
				.uv = calc_tex_coords(processed_vertices[face.first_surf_edge_idx], tex_info),
				.norm = face_normal
			});
			textured_vertices.push_back(Vertex{
				.pos = pos[1],
				.uv = calc_tex_coords(processed_vertices[face.first_surf_edge_idx+i], tex_info),
				.norm = face_normal
			});
			textured_vertices.push_back(Vertex{
				.pos = pos[2],
				.uv = calc_tex_coords(processed_vertices[face.first_surf_edge_idx+j], tex_info),
				.norm = face_normal
			});
		}
	}
	SDL_Log("Done loading vertices");
}

int BSP::get_index_from_surfedge(int surfedge) {
	int surf = surfedges[surfedge];
	if(surf >= 0) {
		return edges[surf].vertex_indices[0];
	}
	else {
		return edges[-surf].vertex_indices[1];
	}
}

int BSP::determine_leaf(glm::vec3 cam_pos) {
	/* use SDF of planes to determine relative position with respect to partitioning planes */
	int idx = 0;
	/* positive values are node indices, negative values are leaf indices */
	while (idx >= 0) {
		const auto& plane = planes[nodes[idx].plane];
		const auto dist = glm::dot(plane.norm, cam_pos) - plane.dist;

		if (dist >= 0)
			idx = nodes[idx].children[0];
		else
			idx = nodes[idx].children[1];
	}

	return -idx - 1;
}


bool BSP::determine_visibility(const Leaf& cam_leaf, const Leaf& leaf, const std::array<glm::vec4, 6>& frustum, const glm::vec3 box_verts[8]) {
	/* perform fustrum culling */
	//return box_in_frustum(frustum, box_verts);
	return true;
}

static std::vector<std::map<std::string, std::string>> load_entities(const std::string& in) {
	/* TODO */
	return {
		{{"test", "this"}},
	};
}

static std::vector<MipTexture> load_mip_textures(const u8* data, u32 offset) {
	const TextureLump* lump = reinterpret_cast<const TextureLump*>(data + offset);
	std::vector<MipTexture> ret;
	ret.resize(lump->n_mip_textures);

	for(size_t i = 0; i < ret.size(); i++) {
		ret[i] = *reinterpret_cast<const MipTexture*>(data + lump->offsets[i]);
	}

	return ret;
}

BSP::BSP(const std::string& fname) : filename(fname) {
	size_t fsize;
	uint8_t* fdata = (uint8_t*)SDL_LoadFile(fname.c_str(), &fsize);
	if(!fdata) {
		SDL_Log("Can't load bsp file: %s", fname.c_str());
		return;
	}

	file_data.resize(fsize);
	memcpy(file_data.data(), fdata, fsize);
	header = reinterpret_cast<Header*>(file_data.data());


	if(header->version != 30) {
		SDL_Log("BSP file not expected version (Half Life has version 30)!");
	}

	SDL_Log("Loading entities\n");
	std::string entities_buff;
	copy_data(file_data.data(), entities_buff, header->entities);
	entities = load_entities(entities_buff);

	SDL_Log("Loading planes\n");
	copy_data(file_data.data(), planes, header->planes);
	/* change swizzle */
	for (auto& plane : planes) {
		change_swizzle(plane.norm);
	}

	//SDL_Log("Loading textures\n");
	//textures = load_mip_textures(file_data.data(), header->textures.offset);

	SDL_Log("Loading vertices\n");
	copy_data(file_data.data(), vertices, header->vertices);
	for (auto& vertex : vertices) {
		change_swizzle(vertex);
	}

	SDL_Log("Loading nodes\n");
	copy_data(file_data.data(), nodes, header->nodes);
	for (auto& node : nodes) {
		change_swizzle(node.bb_mins);
		change_swizzle(node.bb_maxes);
	}

	SDL_Log("Loading texinfo\n");
	copy_data(file_data.data(), tex_infos, header->texinfo);

	SDL_Log("Loading faces\n");
	copy_data(file_data.data(), faces, header->faces);

	SDL_Log("Loading lightmap\n");
	lightmap.lights = reinterpret_cast<rgb*>(file_data.data()+header->lighting.offset);

	SDL_Log("Loading clip nodes\n");
	copy_data(file_data.data(), clip_nodes, header->clip_nodes);
	
	SDL_Log("Loading leaves\n");
	copy_data(file_data.data(), leaves, header->leaves);
	for (auto& leaf : leaves) {
		change_swizzle(leaf.bb_mins);
		change_swizzle(leaf.bb_maxes);
	}

	SDL_Log("Loading mark surfaces\n");
	copy_data(file_data.data(), mark_surfaces, header->mark_surfaces);

	SDL_Log("Loading edges\n");
	copy_data(file_data.data(), edges, header->edges);

	SDL_Log("Loading surfedges\n");
	copy_data(file_data.data(), surfedges, header->surf_edges);
	processed_vertices.reserve(surfedges.size());
	/* use this to build our processed_vertices, idea thanks to gzalo's HalfMapper */
	for(const auto& s : surfedges) {
		processed_vertices.push_back(vertices[edges[s > 0? s : -s].vertex_indices[s<=0]]);
	}

	SDL_Log("Loading models\n");
	copy_data(file_data.data(), models, header->models);
	for (auto& model : models) {
		change_swizzle(model.bb_mins);
		change_swizzle(model.bb_maxes);
	}

	size_t max_vertex_count = 0;

	for (const auto& face : faces) {
		max_vertex_count += (face.n_surf_edges - 2) * 3;
	}
	
	SDL_Log("Creating vertex buffer of size %zu\n", max_vertex_count);
	textured_vertices.reserve(max_vertex_count);
}