#include "map.hpp"
#include "chunk.hpp"
#include "tile/tile_base.hpp"
#include <glm/gtc/integer.hpp>
#include <utility>

using World::Tile::TileBase;
using World::Chunk;
using World::Map;

constexpr glm::vec<2, int> get_pos_mod(glm::vec<2, int> pos) {
	return (pos % Map::N + Map::N) % Map::N;
}

constexpr uint64_t get_chunk_id(glm::vec<2, int> pos) {
	glm::vec<2, int> pos_mod = get_pos_mod(pos);
	glm::vec<2, uint32_t> cpos = (pos - pos_mod) / Map::N;
	return ((uint64_t)cpos.x << 32) | cpos.y;
}

Chunk* Map::get_or_generate_chunk(glm::vec<2, int> pos) {
	uint64_t cid = get_chunk_id(pos);
	auto it = m_chunks.find(cid);

	if(it == m_chunks.end()) {
		it = m_chunks.try_emplace(cid, pos - get_pos_mod(pos)).first;
	}
	return &it->second;
}

Chunk* Map::get_chunk(glm::vec<2, int> pos) {
	uint64_t cid = get_chunk_id(pos);
	auto it = m_chunks.find(cid);

	if(it == m_chunks.end()) {
		return nullptr;
	}
	return &it->second;
}

const Chunk* Map::get_chunk(glm::vec<2, int> pos) const {
	uint64_t cid = get_chunk_id(pos);
	auto it = m_chunks.find(cid);

	if(it == m_chunks.end()) {
		return nullptr;
	}
	return &it->second;
}

TileBase* Map::get_tile(glm::vec<2, int> pos) {
	Chunk* c = get_chunk(pos);
	return c ? c->get(pos) : nullptr;
}

const TileBase* Map::get_tile(glm::vec<2, int> pos) const {
	const Chunk* c = get_chunk(pos);
	return c ? c->get(pos) : nullptr;
}

TileBase* Map::set_tile(glm::vec<2, int> pos, std::unique_ptr<TileBase> tile) {
	tile->m_pos = pos;
	return get_or_generate_chunk(pos)->set(pos, std::move(tile));
}

void Map::update() {
	for(auto& [cid, chunk] : m_chunks) {
		chunk.update(*this);
	}
}

void Map::render(Graphics::Context& ctx) const {
	for(auto& [cid, chunk] : m_chunks) {
		chunk.render(ctx);
	}
}