/*
 * Copyright 2023 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#include "maptilehighlighter.hpp"

namespace jasper::world {

const glutils::ProgramSource MapTileHighlighter::s_programSrc = {
		.shaderParams = {
			{
				.len = 2,
				.name = ox::String("vPosition"),
			},
			{
				.len = 1,
				.name = ox::String("vSelection"),
			},
		},
		.vertShader = ox::sfmt(R"(
		{}
		in vec2 vPosition;
		in float vSelection;
		out float fSelection;
		void main() {
			gl_Position = vec4(vPosition, 0.0, 1.0);
			fSelection = vSelection;
		})", ngfx::gl::GlslVersion),
		.fragShader = ox::sfmt(R"(
		{}
		in float fSelection;
		out vec4 outColor;
		void main() {
			outColor = vec4(0.0, 0.7, 1.0, 0.4) * fSelection;
		})", ngfx::gl::GlslVersion),
};

MapTileHighlighter::MapTileHighlighter() {
	m_bufferSet.vao = glutils::generateVertexArrayObject();
	m_bufferSet.vbo = glutils::generateBuffer();
	m_bufferSet.ebo = glutils::generateBuffer();
	glBindVertexArray(m_bufferSet.vao);
	sendVbo(m_bufferSet);
	sendEbo(m_bufferSet);
	m_shader = glutils::buildShaderProgram(s_programSrc).unwrapThrow();
	glBindVertexArray(0);
}

ox::Error MapTileHighlighter::setup(ox::Size const &sz) noexcept {
	m_subsheetTilesWidth = sz.width;
	m_subsheetTilesHeight = sz.height;
	initGlBuffers();
	return {};
}

void MapTileHighlighter::draw() noexcept {
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glUseProgram(m_shader);
	glBindVertexArray(m_bufferSet.vao);
	sendVbo(m_bufferSet);
	sendEbo(m_bufferSet);
	auto const elmCnt = static_cast<GLsizei>(m_bufferSet.elements.size());
	glDrawElements(GL_TRIANGLES, elmCnt, GL_UNSIGNED_INT, nullptr);
	glBindVertexArray(0);
	glUseProgram(0);
	glDisable(GL_BLEND);
}

ox::Error MapTileHighlighter::setTileHighlight(ox::Point const &addr, bool const hl) noexcept {
	if (addr.x >= m_subsheetTilesWidth || addr.y >= m_subsheetTilesHeight) {
		return ox::Error(1, "tile addr out of bounds");
	}
	auto const vboLength = static_cast<size_t>(s_programSrc.rowLen) * 4;
	auto constexpr eboLength = 6;
	auto const [x, y] = addr;
	auto const i = static_cast<size_t>(m_subsheetTilesWidth * y + x);
	auto const vbo = &m_bufferSet.vertices[i * vboLength];
	auto const ebo = &m_bufferSet.elements[i * eboLength];
	setPixelBufferObject(
			static_cast<uint_t>(i * 4),
			static_cast<float>(x),
			static_cast<float>(y),
			hl,
			vbo,
			ebo);
	return {};
}

int MapTileHighlighter::scale() noexcept {
	return s_scale;
}

void MapTileHighlighter::setPixelBufferObject(
		uint_t vertexRow,
		float x, float y,
		bool const selected,
		float *vbo,
		GLuint *ebo) noexcept {
	auto constexpr xmod = static_cast<float>(ngfx::TileWidth) / 240.f * 4;
	auto constexpr ymod = static_cast<float>(ngfx::TileHeight) / 160.f * 4;
	x *= xmod;
	y *= -ymod;
	x -= 1.0f;
	y += 1.0f - ymod;
	auto const selection = 1.f * static_cast<float>(selected);
	// don't worry, these memcpys gets optimized to something much more ideal
	std::array const vertices{
			x,        y, selection, // bottom left
			x + xmod,        y, selection, // bottom right
			x + xmod, y + ymod, selection, // top right
			x, y + ymod, selection, // top left
	};
	memcpy(vbo, vertices.data(), sizeof(vertices));
	ox::Array<GLuint, 6> const elms{
			vertexRow + 0, vertexRow + 1, vertexRow + 2,
			vertexRow + 2, vertexRow + 3, vertexRow + 0,
	};
	memcpy(ebo, elms.data(), sizeof(elms));
}

void MapTileHighlighter::initGlBuffers() noexcept {
	auto const vboLength = static_cast<size_t>(s_programSrc.rowLen) * 4;
	auto constexpr eboLength = 6;
	auto const tileCnt =
			static_cast<size_t>(m_subsheetTilesWidth) * static_cast<size_t>(m_subsheetTilesHeight);
	m_bufferSet.vertices.resize(tileCnt * vboLength);
	m_bufferSet.elements.resize(tileCnt * eboLength);
	for (int32_t y = 0; y < m_subsheetTilesHeight; ++y) {
		for (int32_t x = 0; x < m_subsheetTilesWidth; ++x) {
			std::ignore = setTileHighlight({x, y}, false);
		}
	}
	glUseProgram(m_shader);
	glBindVertexArray(m_bufferSet.vao);
	sendVbo(m_bufferSet);
	sendEbo(m_bufferSet);
	glBindVertexArray(0);
	glUseProgram(0);
}

}
