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

#include <ox/claw/write.hpp>

#include <nostalgia/gfx/consts.hpp>
#include <nostalgia/gfx/gfx.hpp>

#include "tilesheetpixelgrid.hpp"

namespace nostalgia::gfx {

void TileSheetGrid::setPixelSizeMod(float const sm) noexcept {
	m_pixelSizeMod = sm;
}

ox::Error TileSheetGrid::buildShader() noexcept {
	auto const pixelLineVshad = ox::sfmt(VShad, gl::GlslVersion);
	auto const pixelLineFshad = ox::sfmt(FShad, gl::GlslVersion);
	auto const pixelLineGshad = ox::sfmt(GShad, gl::GlslVersion);
	return glutils::buildShaderProgram(
		pixelLineVshad, pixelLineFshad, pixelLineGshad).moveTo(m_shader);
}

void TileSheetGrid::draw(bool const update, ox::Vec2 const &scroll) noexcept {
	// the lines just show up bigger on Windows for some reason
	if constexpr(ox::defines::OS == ox::OS::Windows) {
		glLineWidth(3 * m_pixelSizeMod * 0.25f);
	} else {
		glLineWidth(3 * m_pixelSizeMod * 0.5f);
	}
	glUseProgram(m_shader);
	glBindVertexArray(m_bufferSet.vao);
	if (update) {
		glutils::sendVbo(m_bufferSet);
	}
	auto const uniformScroll = glGetUniformLocation(m_shader, "gScroll");
	glUniform2f(uniformScroll, scroll.x, scroll.y);
	glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(m_bufferSet.vertices.size() / VertexVboRowLength));
	glBindVertexArray(0);
	glUseProgram(0);
}

void TileSheetGrid::initBufferSet(ox::Vec2 const &paneSize, TileSheet::SubSheet const &subsheet) noexcept {
	// vao
	m_bufferSet.vao = glutils::generateVertexArrayObject();
	glBindVertexArray(m_bufferSet.vao);
	// vbo
	m_bufferSet.vbo = glutils::generateBuffer();
	setBufferObjects(paneSize, subsheet);
	glutils::sendVbo(m_bufferSet);
	// vbo layout
	auto const pt1Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt1"));
	glEnableVertexAttribArray(pt1Attr);
	glVertexAttribPointer(
		pt1Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
	auto const pt2Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt2"));
	glEnableVertexAttribArray(pt2Attr);
	glVertexAttribPointer(pt2Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
	                      std::bit_cast<void*>(uintptr_t{2 * sizeof(float)}));
	auto const colorAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vColor"));
	glEnableVertexAttribArray(colorAttr);
	glVertexAttribPointer(colorAttr, 3, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
	                      std::bit_cast<void*>(uintptr_t{4 * sizeof(float)}));
}

void TileSheetGrid::update(ox::Vec2 const &paneSize, TileSheet::SubSheet const &subsheet) noexcept {
	glBindVertexArray(m_bufferSet.vao);
	setBufferObjects(paneSize, subsheet);
	glutils::sendVbo(m_bufferSet);
	//glutils::sendEbo(m_bufferSet);
}

void TileSheetGrid::setBufferObject(
		ox::Point const pt1,
		ox::Point const pt2,
		Color32 const c,
		ox::Span<float> const vbo,
		ox::Vec2 const &pixSize) noexcept {
	auto const x1 = static_cast<float>(pt1.x) * pixSize.x - 1.f;
	auto const y1 = 1.f - static_cast<float>(pt1.y) * pixSize.y;
	auto const x2 = static_cast<float>(pt2.x) * pixSize.x - 1.f;
	auto const y2 = 1.f - static_cast<float>(pt2.y) * pixSize.y;
	// don't worry, this gets optimized to something much more ideal
	ox::Array<float, VertexVboLength> const vertices = {x1, y1, x2, y2, redf(c), greenf(c), bluef(c)};
	ox::spancpy(vbo, ox::SpanView<float>{vertices});
}

void TileSheetGrid::setBufferObjects(ox::Vec2 const &paneSize, TileSheet::SubSheet const &subsheet) noexcept {
	if (subsheet.columns < 1 || subsheet.rows < 1) {
		m_bufferSet.vertices.clear();
		return;
	}
	auto const pixSize = pixelSize(paneSize);
	auto const set = [&](std::size_t i, ox::Point pt1, ox::Point pt2, Color32 c) {
		auto const idx = i * VertexVboLength;
		auto const vbo = ox::Span{m_bufferSet.vertices} + idx;
		setBufferObject(pt1, pt2, c, vbo, pixSize);
	};
	// set buffer length
	auto const width = subsheet.columns * TileWidth;
	auto const height = subsheet.rows * TileHeight;
	auto const tileCnt = static_cast<unsigned>(subsheet.columns + subsheet.rows);
	auto const pixelCnt = static_cast<unsigned>(width + height);
	m_bufferSet.vertices.resize(static_cast<std::size_t>(tileCnt + pixelCnt + 4) * VertexVboLength);
	// set buffer
	std::size_t i = 0;
	// pixel outlines
	constexpr auto pixOutlineColor = color32(0.4431f, 0.4901f, 0.4941f);
	for (auto x = 0; x < subsheet.columns * TileWidth + 1; ++x) {
		set(i, {x, 0}, {x, subsheet.rows * TileHeight}, pixOutlineColor);
		++i;
	}
	for (auto y = 0; y < subsheet.rows * TileHeight + 1; ++y) {
		set(i, {0, y}, {subsheet.columns * TileWidth, y}, pixOutlineColor);
		++i;
	}
	// tile outlines
	constexpr auto tileOutlineColor = color32(0.f, 0.f, 0.f);
	for (auto x = 0; x < subsheet.columns * TileWidth + 1; x += TileWidth) {
		set(i, {x, 0}, {x, subsheet.rows * TileHeight}, tileOutlineColor);
		++i;
	}
	for (auto y = 0; y < subsheet.rows * TileHeight + 1; y += TileHeight) {
		set(i, {0, y}, {subsheet.columns * TileWidth, y}, tileOutlineColor);
		++i;
	}
}

ox::Vec2 TileSheetGrid::pixelSize(ox::Vec2 const &paneSize) const noexcept {
	auto const [sw, sh] = paneSize;
	constexpr float ymod = 0.35f / 10.0f;
	auto const xmod = ymod * sh / sw;
	return {xmod * m_pixelSizeMod, ymod * m_pixelSizeMod};
}

}
