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

#include <nostalgia/core/consts.hpp>
#include <nostalgia/core/ptidxconv.hpp>
#include "tilesheeteditormodel.hpp"
#include "tilesheetpixels.hpp"

namespace nostalgia::core {

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

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

ox::Error TileSheetPixels::buildShader() noexcept {
	const auto Vshad = ox::sfmt(VShad, glutils::GlslVersion);
	const auto Fshad = ox::sfmt(FShad, glutils::GlslVersion);
	return glutils::buildShaderProgram(Vshad, Fshad).moveTo(&m_shader);
}

void TileSheetPixels::draw(bool update, const geo::Vec2 &scroll) noexcept {
	glUseProgram(m_shader);
	glBindVertexArray(m_bufferSet.vao);
	if (update) {
		glutils::sendVbo(m_bufferSet);
	}
	const auto 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);
}

void TileSheetPixels::initBufferSet(const geo::Vec2 &paneSize) noexcept {
	// vao
	m_bufferSet.vao = glutils::generateVertexArrayObject();
	glBindVertexArray(m_bufferSet.vao);
	// vbo & ebo
	m_bufferSet.vbo = glutils::generateBuffer();
	m_bufferSet.ebo = glutils::generateBuffer();
	setBufferObjects(paneSize);
	glutils::sendVbo(m_bufferSet);
	glutils::sendEbo(m_bufferSet);
	// vbo layout
	const auto posAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPosition"));
	glEnableVertexAttribArray(posAttr);
	glVertexAttribPointer(posAttr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
	const auto colorAttr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vColor"));
	glEnableVertexAttribArray(colorAttr);
	glVertexAttribPointer(colorAttr, 3, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
	                      reinterpret_cast<void*>(2 * sizeof(float)));
}

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

void TileSheetPixels::setPixelBufferObject(const geo::Vec2 &paneSize, unsigned vertexRow, float x, float y, Color16 color, float *vbo, GLuint *ebo) const noexcept {
	const auto [xmod, ymod] = pixelSize(paneSize);
	x *= xmod;
	y *= -ymod;
	x -= 1.0f;
	y += 1.0f - ymod;
	const auto r = redf(color), g = greenf(color), b = bluef(color);
	// don't worry, these memcpys gets optimized to something much more ideal
	const float vertices[VertexVboLength] = {
			       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
	};
	memcpy(vbo, vertices, sizeof(vertices));
	const GLuint elms[VertexEboLength] = {
			vertexRow + 0, vertexRow + 1, vertexRow + 2,
			vertexRow + 2, vertexRow + 3, vertexRow + 0,
	};
	memcpy(ebo, elms, sizeof(elms));
}

void TileSheetPixels::setBufferObjects(const geo::Vec2 &paneSize) noexcept {
	// set buffer lengths
	const auto subSheet = m_model->activeSubSheet();
	const auto pal = m_model->pal();
	const auto width = subSheet->columns * TileWidth;
	const auto height = subSheet->rows * TileHeight;
	const auto pixels = static_cast<unsigned>(width * height);
	m_bufferSet.vertices.resize(pixels * VertexVboLength);
	m_bufferSet.elements.resize(pixels * VertexEboLength);
	// set pixels
	subSheet->walkPixels(m_model->img().bpp, [&](std::size_t i, uint8_t p) {
		auto color = pal->color(p);
		const auto pt = idxToPt(static_cast<int>(i), subSheet->columns);
		const auto fx = static_cast<float>(pt.x);
		const auto fy = static_cast<float>(pt.y);
		const auto vbo = &m_bufferSet.vertices[i * VertexVboLength];
		const auto ebo = &m_bufferSet.elements[i * VertexEboLength];
		if (i * VertexVboLength + VertexVboLength > m_bufferSet.vertices.size()) {
			return;
		}
		if (i * VertexEboLength + VertexEboLength > m_bufferSet.elements.size()) {
			return;
		}
		if (m_model->pixelSelected(i)) {
			const auto r = red16(color) / 2;
			const auto g = (green16(color) + 20) / 2;
			const auto b = (blue16(color) + 31) / 2;
			color = color16(r, g, b);
		}
		setPixelBufferObject(paneSize, i * VertexVboRows, fx, fy, color, vbo, ebo);
	});
}

}
