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

#include <nostalgia/core/consts.hpp>
#include "tilesheetpixelgrid.hpp"

namespace nostalgia::core {

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

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

void TileSheetGrid::draw(bool update, const geo::Vec2 &scroll) noexcept {
	glLineWidth(3 * m_pixelSizeMod * 0.5f);
	glUseProgram(m_shader);
	glBindVertexArray(m_bufferSet.vao);
	if (update) {
		glutils::sendVbo(m_bufferSet);
	}
	const auto uniformScroll = glGetUniformLocation(m_shader, "gScroll");
	glUniform2f(uniformScroll, scroll.x, scroll.y);
	glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(m_bufferSet.vertices.size() / VertexVboRowLength));
}

void TileSheetGrid::initBufferSet(const geo::Vec2 &paneSize, const TileSheet::SubSheet &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
	const auto pt1Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt1"));
	glEnableVertexAttribArray(pt1Attr);
	glVertexAttribPointer(pt1Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float), nullptr);
	const auto pt2Attr = static_cast<GLuint>(glGetAttribLocation(m_shader, "vPt2"));
	glEnableVertexAttribArray(pt2Attr);
	glVertexAttribPointer(pt2Attr, 2, GL_FLOAT, GL_FALSE, VertexVboRowLength * sizeof(float),
	                      reinterpret_cast<void*>(2 * sizeof(float)));
	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*>(4 * sizeof(float)));
}

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

void TileSheetGrid::setBufferObjects(const geo::Vec2 &paneSize, const TileSheet::SubSheet &subsheet) noexcept {
	const auto pixSize = pixelSize(paneSize);
	const auto set = [&](unsigned i, geo::Point pt1, geo::Point pt2, Color32 c) {
		const auto vbo = &m_bufferSet.vertices[i * VertexVboLength];
		setBufferObject(pt1, pt2, c, vbo, pixSize);
	};
	// set buffer length
	const auto width = subsheet.columns * TileWidth;
	const auto height = subsheet.rows * TileHeight;
	const auto tileCnt = static_cast<unsigned>(subsheet.columns + subsheet.rows);
	const auto pixelCnt = static_cast<unsigned>(width + height);
	m_bufferSet.vertices.resize((tileCnt + pixelCnt + 4) * VertexVboLength);
	// set buffer
	auto i = 0ull;
	// 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;
	}
}

geo::Vec2 TileSheetGrid::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};
}

}
