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

#include <cmath>

#include "drawcommand.hpp"

namespace nostalgia::gfx {

constexpr void iterateLine(ox::Point const &a, ox::Point const &b, auto const &f) noexcept {
	auto const rise = b.y - a.y;
	auto const run = b.x - a.x;
	auto const slope = static_cast<double>(rise) / static_cast<double>(run);
	auto y = static_cast<double>(a.y);
	auto const w = abs(run);
	if (rise > 0) {
		if (run > 0) {
			auto constexpr xmod = 1;
			for (int32_t i = 0; i < w; ++i) {
				auto const climb = static_cast<int32_t>(ceil(y + slope - y));
				f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
				y += slope * xmod;
			}
		} else if (run < 0) {
			auto constexpr xmod = -1;
			for (int32_t i = 0; i < w; ++i) {
				auto const climb = static_cast<int32_t>(floor(y + slope - y));
				f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
				y += slope * xmod;
			}
		} else {
			f(a.x, a.y, rise);
		}
	} else if (rise < 0) {
		if (run > 0) {
			auto constexpr xmod = 1;
			for (int32_t i = 0; i < w; ++i) {
				auto const climb = static_cast<int32_t>(floor(y + slope - y));
				f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
				y += slope * xmod;
			}
		} else if (run < 0) {
			auto constexpr xmod = -1;
			for (int32_t i = 0; i < w; ++i) {
				auto const climb = static_cast<int32_t>(ceil(y + slope - y));
				f(a.x + i * xmod, static_cast<int32_t>(y), climb * xmod);
				y += slope * xmod;
			}
		} else {
			f(a.x, a.y, rise - 1);
		}
	} else {
		auto const xmod = run > 0 ? 1 : -1;
		for (int32_t i = 0; i < w; ++i) {
			f(a.x + i * xmod, static_cast<int32_t>(y), 1);
		}
	}
}


DrawCommand::DrawCommand(
		TileSheet &img,
		TileSheet::SubSheetIdx subSheetIdx,
		std::size_t const idx,
		int const palIdx) noexcept:
		m_img{img},
		m_subSheetIdx{std::move(subSheetIdx)},
		m_palIdx{palIdx} {
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
	m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(subsheet, idx));
}

DrawCommand::DrawCommand(
		TileSheet &img,
		TileSheet::SubSheetIdx subSheetIdx,
		ox::SpanView<std::size_t> const &idxList,
		int const palIdx) noexcept:
		m_img(img),
		m_subSheetIdx(std::move(subSheetIdx)),
		m_palIdx(palIdx) {
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
	for (auto const idx : idxList) {
		m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(subsheet, idx));
	}
}

bool DrawCommand::append(std::size_t const idx) noexcept {
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
	if (m_changes.back().value->idx != idx && getPixel(subsheet, idx) != m_palIdx) {
		// duplicate entries are bad
		auto existing = find_if(
			m_changes.cbegin(), m_changes.cend(), [idx](auto const &c) {
			return c.idx == idx;
		});
		if (existing == m_changes.cend()) {
			m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(subsheet, idx));
			subsheet.pixels[idx] = static_cast<uint8_t>(m_palIdx);
			return true;
		}
	}
	return false;
}

bool DrawCommand::append(ox::SpanView<std::size_t> const &idxList) noexcept {
	auto out = false;
	for (auto idx : idxList) {
		out = append(idx) || out;
	}
	return out;
}

void DrawCommand::lineUpdate(ox::Point a, ox::Point b) noexcept {
	std::ignore = undo();
	m_changes.clear();
	auto &ss = getSubSheet(m_img, m_subSheetIdx);
	if (a.x < b.x) { ++b.x; }
	if (a.y < b.y) { ++b.y; }
	if (a.x > b.x) { --b.x; }
	iterateLine(a, b, [this, &ss](int32_t const x, int32_t const y, int32_t const h) {
		int const mod = h < 0 ? -1 : 1;
		auto const range = h * mod;
		for (int32_t i{}; i < range; ++i) {
			auto const idx = ptToIdx(x, y + i * mod, ss.columns * TileWidth);
			if (idx < ss.pixels.size()) {
				if (m_palIdx != getPixel(ss, idx)) {
					m_changes.emplace_back(static_cast<uint32_t>(idx), getPixel(ss, idx));
				}
			}
		}
	});
	std::ignore = redo();
}

ox::Error DrawCommand::redo() noexcept {
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
	for (auto const &c : m_changes) {
		subsheet.pixels[c.idx] = static_cast<uint8_t>(m_palIdx);
	}
	return {};
}

ox::Error DrawCommand::undo() noexcept {
	auto &subsheet = getSubSheet(m_img, m_subSheetIdx);
	for (auto const &c : m_changes) {
		subsheet.pixels[c.idx] = static_cast<uint8_t>(c.oldPalIdx);
	}
	return {};
}

int DrawCommand::commandId() const noexcept {
	return static_cast<int>(CommandId::Draw);
}

TileSheet::SubSheetIdx const &DrawCommand::subsheetIdx() const noexcept {
	return m_subSheetIdx;
}

void DrawCommand::finish() noexcept {
	setObsolete(m_changes.empty());
}

}
