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

#include <ox/std/point.hpp>
#include <keel/media.hpp>

#include <nostalgia/gfx/consts.hpp>

#include "tilesheeteditorview.hpp"

namespace nostalgia::gfx {

TileSheetEditorView::TileSheetEditorView(
		studio::Context &sctx, ox::StringViewCR path, studio::UndoStack &undoStack):
	m_model(sctx, path, undoStack),
	m_pixelsDrawer(m_model) {
	glBindVertexArray(0);
	// build shaders
	OX_THROW_ERROR(m_pixelsDrawer.buildShader());
	OX_THROW_ERROR(m_pixelGridDrawer.buildShader());
	m_model.activeSubsheetChanged.connect(this, &TileSheetEditorView::setActiveSubsheet);
}

void TileSheetEditorView::draw() noexcept {
	glClearColor(0.37f, 0.37f, 0.37f, 1.f);
	glClear(GL_COLOR_BUFFER_BIT);
	m_pixelsDrawer.draw(updated(), m_scrollOffset);
	m_pixelGridDrawer.draw(updated(), m_scrollOffset);
}

void TileSheetEditorView::scrollV(ox::Vec2 const &paneSz, float wheel, bool zoomMod) noexcept {
	auto const &s = m_model.activeSubSheet();
	auto const pixelSize = m_pixelsDrawer.pixelSize(paneSz);
	ImVec2 const sheetSize(pixelSize.x * static_cast<float>(s.columns) * TileWidth,
	                       pixelSize.y * static_cast<float>(s.rows) * TileHeight);
	if (zoomMod) {
		m_pixelSizeMod = ox::clamp(m_pixelSizeMod + wheel * 0.02f, 0.55f, 2.f);
		m_pixelsDrawer.setPixelSizeMod(m_pixelSizeMod);
		m_pixelGridDrawer.setPixelSizeMod(m_pixelSizeMod);
		m_updated = true;
	} else {
		m_scrollOffset.y -= wheel * 0.1f;
	}
	// adjust scroll offset in both cases because the image can be zoomed
	// or scrolled off screen
	m_scrollOffset.y = ox::clamp(m_scrollOffset.y, 0.f, sheetSize.y / 2);
}

void TileSheetEditorView::scrollH(ox::Vec2 const &paneSz, float wheelh) noexcept {
	auto const &s = m_model.activeSubSheet();
	auto const pixelSize = m_pixelsDrawer.pixelSize(paneSz);
	ImVec2 const sheetSize(pixelSize.x * static_cast<float>(s.columns) * TileWidth,
	                       pixelSize.y * static_cast<float>(s.rows) * TileHeight);
	m_scrollOffset.x += wheelh * 0.1f;
	m_scrollOffset.x = ox::clamp(m_scrollOffset.x, -(sheetSize.x / 2), 0.f);
}

void TileSheetEditorView::insertTile(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) noexcept {
	auto pt = clickPoint(paneSize, clickPos);
	auto const &s = m_model.activeSubSheet();
	pt.x = ox::min(pt.x, s.columns * TileWidth - 1);
	pt.y = ox::min(pt.y, s.rows * TileHeight - 1);
	auto const tileIdx = ptToIdx(pt, s.columns) / PixelsPerTile;
	m_model.insertTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
}

void TileSheetEditorView::deleteTile(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) noexcept {
	auto const pt = clickPoint(paneSize, clickPos);
	auto const &s = m_model.activeSubSheet();
	auto const tileIdx = ptToIdx(pt, s.columns) / PixelsPerTile;
	m_model.deleteTiles(m_model.activeSubSheetIdx(), tileIdx, 1);
}

void TileSheetEditorView::clickDraw(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) noexcept {
	auto const pt = clickPoint(paneSize, clickPos);
	m_model.drawCommand(pt, m_palIdx);
}

void TileSheetEditorView::clickLine(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) noexcept {
	auto const pt = clickPoint(paneSize, clickPos);
	m_model.drawLineCommand(pt, m_palIdx);
}

void TileSheetEditorView::clickSelect(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) noexcept {
	auto const pt = clickPoint(paneSize, clickPos);
	m_model.select(pt);
}

void TileSheetEditorView::clickFill(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) noexcept {
	auto const pt = clickPoint(paneSize, clickPos);
	m_model.fill(pt, static_cast<uint8_t>(m_palIdx));
}

void TileSheetEditorView::releaseMouseButton(TileSheetTool const tool) noexcept {
	switch (tool) {
		case TileSheetTool::Draw:
		case TileSheetTool::Fill:
		case TileSheetTool::Line:
			m_model.endDrawCommand();
			break;
		case TileSheetTool::Select:
			m_model.completeSelection();
			break;
		case TileSheetTool::None:
			break;
	}
}

void TileSheetEditorView::resizeView(ox::Vec2 const &sz) noexcept {
	m_viewSize = sz;
	initView();
}

bool TileSheetEditorView::updated() const noexcept {
	return m_updated || m_model.updated();
}

ox::Error TileSheetEditorView::markUpdated() noexcept {
	m_updated = true;
	return {};
}

void TileSheetEditorView::ackUpdate() noexcept {
	m_updated = false;
	m_pixelsDrawer.update(m_viewSize);
	m_pixelGridDrawer.update(m_viewSize, m_model.activeSubSheet());
	m_model.ackUpdate();
}

void TileSheetEditorView::initView() noexcept {
	m_pixelsDrawer.initBufferSet(m_viewSize);
	m_pixelGridDrawer.initBufferSet(m_viewSize, m_model.activeSubSheet());
}

ox::Point TileSheetEditorView::clickPoint(ox::Vec2 const &paneSize, ox::Vec2 const &clickPos) const noexcept {
	auto [x, y] = clickPos;
	auto const pixDrawSz = m_pixelsDrawer.pixelSize(paneSize);
	x /= paneSize.x;
	y /= paneSize.y;
	x += -m_scrollOffset.x / 2;
	y += m_scrollOffset.y / 2;
	x /= pixDrawSz.x;
	y /= pixDrawSz.y;
	return {static_cast<int>(x * 2), static_cast<int>(y * 2)};
}

ox::Error TileSheetEditorView::setActiveSubsheet(TileSheet::SubSheetIdx const&) noexcept {
	initView();
	return {};
}

}
