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

#include "tilesheeteditormodel.hpp"
#include "tilesheetpixels.hpp"

namespace nostalgia::gfx {

const glutils::ProgramSource TileSheetPixels::s_programSrc = {
		.shaderParams = {
			{
				.len = 2,
				.name = ox::String("vPosition"),
			},
			{
				.len = 3,
				.name = ox::String("vColor"),
			},
		},
		.vertShader = ox::sfmt(R"(
		{}
		in vec2 vPosition;
		in vec3 vColor;
		out vec3 fColor;
		uniform vec2 vScroll;
		void main() {
			gl_Position = vec4(vPosition + vScroll, 0.0, 1.0);
			fColor = vColor;
		})", gfx::gl::GlslVersion),
		.fragShader = ox::sfmt(R"(
		{}
		in vec3 fColor;
		out vec4 outColor;
		void main() {
			//outColor = vec4(0.0, 0.7, 1.0, 1.0);
			outColor = vec4(fColor, 1.0);
		})", gfx::gl::GlslVersion),
};

TileSheetPixels::TileSheetPixels(TileSheetEditorModel &model) noexcept: m_model(model) {
}

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

ox::Error TileSheetPixels::buildShader() noexcept {
	return glutils::buildShaderProgram(s_programSrc).moveTo(m_shader);
}

void TileSheetPixels::draw(bool const update, ox::Vec2 const &scroll) noexcept {
	glUseProgram(m_shader);
	glBindVertexArray(m_bufferSet.vao);
	if (update) {
		glutils::sendVbo(m_bufferSet);
	}
	auto const uniformScroll = glGetUniformLocation(m_shader, "vScroll");
	glUniform2f(uniformScroll, scroll.x, scroll.y);
	glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_bufferSet.elements.size()), GL_UNSIGNED_INT, nullptr);
	glBindVertexArray(0);
	glUseProgram(0);
}

void TileSheetPixels::initBufferSet(ox::Vec2 const &paneSize) noexcept {
	m_bufferSet.vao = glutils::generateVertexArrayObject();
	m_bufferSet.vbo = glutils::generateBuffer();
	m_bufferSet.ebo = glutils::generateBuffer();
	update(paneSize);
	glutils::setupShaderParams(m_shader, s_programSrc.shaderParams);
}

void TileSheetPixels::update(ox::Vec2 const &paneSize) noexcept {
	glBindVertexArray(m_bufferSet.vao);
	setBufferObjects(paneSize);
	glutils::sendVbo(m_bufferSet);
	glutils::sendEbo(m_bufferSet);
}

ox::Vec2 TileSheetPixels::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};
}

void TileSheetPixels::setPixelBufferObject(
		ox::Vec2 const &paneSize,
		unsigned const vertexRow,
		float x, float y,
		Color16 const color,
		ox::Span<float> const vbo,
		ox::Span<GLuint> const ebo) const noexcept {
	auto const [xmod, ymod] = pixelSize(paneSize);
	x *= xmod;
	y *= -ymod;
	x -= 1.0f;
	y += 1.0f - ymod;
	auto const r = redf(color), g = greenf(color), b = bluef(color);
	// don't worry, these memcpys gets optimized to something much more ideal
	std::array const vertices = {
			       x,        y, r, g, b, // bottom left
			x + xmod,        y, r, g, b, // bottom right
			x + xmod, y + ymod, r, g, b, // top right
			       x, y + ymod, r, g, b, // top left
	};
	ox::spancpy(vbo, ox::SpanView<float>{vertices});
	std::array const elms = {
			vertexRow + 0, vertexRow + 1, vertexRow + 2,
			vertexRow + 2, vertexRow + 3, vertexRow + 0,
	};
	ox::spancpy(ebo, ox::SpanView<GLuint>{elms});
}

void TileSheetPixels::setBufferObjects(ox::Vec2 const &paneSize) noexcept {
	// set buffer lengths
	auto const &subSheet = m_model.activeSubSheet();
	if (subSheet.columns < 1 || subSheet.rows < 1) {
		m_bufferSet.vertices.clear();
		m_bufferSet.elements.clear();
		return;
	}
	auto const &pal = m_model.pal();
	auto const width = subSheet.columns * TileWidth;
	auto const height = subSheet.rows * TileHeight;
	auto const pixels = static_cast<size_t>(width) * static_cast<size_t>(height);
	auto const vboLen = static_cast<size_t>(s_programSrc.vboLen);
	oxAssert(subSheet.pixels.size() == pixels, "incorrect pixels");
	m_bufferSet.vertices.resize(pixels * vboLen);
	m_bufferSet.elements.resize(pixels * VertexEboLength);
	// set pixels
	for (size_t i = 0; i < subSheet.pixels.size(); ++i) {
		auto const p = subSheet.pixels[i];
		auto color = gfx::color(pal, m_model.palettePage(), p);
		auto const pt = idxToPt(static_cast<int>(i), subSheet.columns);
		auto const fx = static_cast<float>(pt.x);
		auto const fy = static_cast<float>(pt.y);
		auto const vbo = ox::Span{m_bufferSet.vertices} + i * vboLen;
		auto const ebo = ox::Span{m_bufferSet.elements} + i * VertexEboLength;
		if (i * vboLen + vboLen > m_bufferSet.vertices.size()) {
			return;
		}
		if (i * VertexEboLength + VertexEboLength > m_bufferSet.elements.size()) {
			return;
		}
		if (m_model.pixelSelected(i)) {
			color = applySelectionColor(color);
		}
		setPixelBufferObject(paneSize, static_cast<unsigned>(i * VertexVboRows), fx, fy, color, vbo, ebo);
	}
}

}
