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

#include <keel/keel.hpp>

#include <nostalgia/gfx/gfx.hpp>

#include <jasper/world/world.hpp>

namespace jasper::world {

[[nodiscard]]
bool isUpdatingObj(ObjTileRefSet const &obj) noexcept {
	return obj.frames > 1;
}

World::World(ngfx::Context &nctx, WorldStatic const &worldStatic) noexcept:
	m_nctx{nctx},
	m_worldStatic{worldStatic} {}

ox::Error World::setupDisplay() noexcept {
	if (m_worldStatic.objTileRefSets.empty()) {
		return ox::Error{1, "WorldStatic must have at least one ref set"};
	}
	m_currentTicks = ticksMs(turbineCtx(m_nctx));
	m_objStates.clear();
	for (uint16_t objIdx{}; auto &obj : m_worldStatic.objTileRefSets) {
		if (isUpdatingObj(obj)) {
			m_objStates.emplace_back(ObjState{
				.objIdx = objIdx,
				.frame = 0,
				.nextUpdateTime = m_currentTicks + obj.intervalMs,
			});
		}
		++objIdx;
	}
	if (m_tilesheets.empty()) {
		auto &kctx = keelCtx(m_nctx);
		for (auto const &tsAddr : m_worldStatic.tilesheets) {
			OX_REQUIRE_M(ts, keel::readObj<ngfx::CompactTileSheet>(kctx, tsAddr));
			m_tilesheets.emplace_back(std::move(ts));
		}
	}
	for (auto i = 0u; auto const &palAddr : m_worldStatic.palettes) {
		OX_RETURN_ERROR(ngfx::loadBgPalette(m_nctx, i, palAddr));
		++i;
	}
	ngfx::clearCbbs(m_nctx);
	for (size_t i = 1; i < m_worldStatic.objTileRefSets.size(); ++i) {
		auto const &rs = m_worldStatic.objTileRefSets[i];
		OX_RETURN_ERROR(ngfx::loadBgTileSheet(
			m_nctx,
			rs.cbb,
			*m_tilesheets[rs.tilesheetId],
			rs.cbbIdx,
			rs.tilesheetIdx,
			rs.tileCnt));
	}
    setupTiles();
	return {};
}

ox::Result<int> World::update() noexcept {
    turbine::TimeMs nextUpdateTime = ox::MaxValue<turbine::TimeMs>;
	m_currentTicks = ticksMs(turbineCtx(m_nctx));
	for (auto &state : m_objStates) {
		OX_REQUIRE(u, updateObj(state));
		nextUpdateTime = ox::min(nextUpdateTime, u);
	}
	return static_cast<int>(nextUpdateTime - m_currentTicks);
}

void World::setupTiles() noexcept {
	m_currentTicks = ticksMs(turbineCtx(m_nctx));
	ngfx::setBgStatus(m_nctx, 0); // disable all backgrounds
	for (auto layerNo = 0u; auto const &layer : m_worldStatic.map) {
		setupLayer(layerNo, layer.cbb);
		++layerNo;
	}
}

void World::setupTile(
        uint32_t const lyr,
        uint32_t const x,
        uint32_t const y) noexcept {
    auto &t = tile(m_worldStatic, lyr, x, y);
    auto const tx = static_cast<int>(x) * 2;
    auto const ty = static_cast<int>(y) * 2;
    auto const &obj = m_worldStatic.objTileRefSets[t.objIdxRefSet];
    ngfx::setBgTile(m_nctx, lyr, tx + 0, ty + 0, {
        .tileIdx = static_cast<uint_t>(obj.cbbIdx + 0),
        .palBank = obj.palBank,
    });
    ngfx::setBgTile(m_nctx, lyr, tx + 1, ty + 0, {
        .tileIdx = static_cast<uint_t>(obj.cbbIdx + 1),
        .palBank = obj.palBank,
    });
    ngfx::setBgTile(m_nctx, lyr, tx + 0, ty + 1, {
        .tileIdx = static_cast<uint_t>(obj.cbbIdx + 2),
        .palBank = obj.palBank,
    });
    ngfx::setBgTile(m_nctx, lyr, tx + 1, ty + 1, {
        .tileIdx = static_cast<uint_t>(obj.cbbIdx + 3),
        .palBank = obj.palBank,
    });
}

ox::Result<turbine::TimeMs> World::updateObj(ObjState &state) noexcept {
	if (state.nextUpdateTime <= m_currentTicks) {
        auto &obj = m_worldStatic.objTileRefSets[state.objIdx];
		++state.frame;
		if (state.frame >= obj.frames) {
			state.frame = 0;
		}
		OX_RETURN_ERROR(ngfx::loadBgTileSheet(
			m_nctx,
			obj.cbb,
			*m_tilesheets[obj.tilesheetId],
			obj.cbbIdx,
			static_cast<size_t>(obj.tilesheetIdx + state.frame * static_cast<uint16_t>(obj.tileCnt)),
			obj.tileCnt));
		state.nextUpdateTime = m_currentTicks + obj.intervalMs;
	}
    return state.nextUpdateTime;
}

void World::setupLayer(
		uint_t const lyr,
		uint_t const cbb) noexcept {
	ngfx::clearBg(m_nctx, lyr);
	ngfx::setBgStatus(m_nctx, lyr, true);
	ngfx::setBgCbb(m_nctx, lyr, cbb);
	auto const rows = ox::min<int>(m_worldStatic.rows, ngfx::tileRows(m_nctx) / 2);
	auto const columns = ox::min<int>(m_worldStatic.columns, ngfx::tileColumns(m_nctx) / 2);
	for (auto y = 0; y < rows; ++y) {
		for (auto x = 0; x < columns; ++x) {
			setupTile(lyr, static_cast<uint32_t>(x), static_cast<uint32_t>(y));
		}
	}
}

}
