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

#pragma once

#include <ox/fs/fs.hpp>
#include <ox/std/error.hpp>
#include <ox/std/size.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>

#include <nostalgia/core/tilesheet.hpp>

namespace nostalgia::scene {

struct SpriteDoc {

	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SpriteDoc";
	constexpr static auto TypeVersion = 1;
	constexpr static auto Preloadable = true;

	ox::String tilesheetPath;
	ox::Vector<core::SubSheetId> subsheetId;

};

struct TileDoc {

	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.TileDoc";
	constexpr static auto TypeVersion = 1;
	constexpr static auto Preloadable = true;

	core::SubSheetId subsheetId = -1;
	ox::String subsheetPath;
	uint8_t type = 0;
	ox::Array<uint8_t, 4> layerAttachments;

	[[nodiscard]]
	constexpr ox::Result<core::SubSheetId> getSubsheetId(core::TileSheet const&ts) const noexcept {
		// prefer the already present ID
		if (subsheetId > -1) {
			return subsheetId;
		}
		return getIdFor(ts, subsheetPath);
	}

	[[nodiscard]]
	constexpr ox::Result<ox::StringView> getSubsheetPath(
			core::TileSheet const&ts) const noexcept {
		// prefer the already present path
		if (!subsheetPath.len()) {
			return core::getNameFor(ts, subsheetId);
		}
		return ox::StringView(subsheetPath);
	}

};

oxModelBegin(TileDoc)
	oxModelFieldRename(subsheet_id, subsheetId)
	oxModelFieldRename(subsheet_path, subsheetPath)
	oxModelField(type)
	oxModelFieldRename(layer_attachments, layerAttachments)
oxModelEnd()

struct SceneDoc {

	using TileMapRow = ox::Vector<TileDoc>;
	using TileMapLayer = ox::Vector<TileMapRow>;
	using TileMap = ox::Vector<TileMapLayer>;

	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneDoc";
	constexpr static auto TypeVersion = 1;
	constexpr static auto Preloadable = true;

	ox::String tilesheet; // path
	ox::Vector<ox::String> palettes; // paths
	TileMap tiles;

	[[nodiscard]]
	constexpr ox::Size size(std::size_t layerIdx) const noexcept {
		const auto &layer = this->tiles[layerIdx];
		const auto rowCnt = static_cast<int>(layer.size());
		if (!rowCnt) {
			return {};
		}
		auto colCnt = layer[0].size();
		// find shortest row (they should all be the same, but you know this data
		// could come from a file)
		for (auto const&row : layer) {
			colCnt = ox::min(colCnt, row.size());
		}
		return {static_cast<int>(colCnt), rowCnt};
	}
};

oxModelBegin(SceneDoc)
	oxModelField(tilesheet)
	oxModelField(palettes)
	oxModelField(tiles)
oxModelEnd()


constexpr void setTopEdge(uint8_t &layerAttachments, unsigned val) noexcept {
	const auto val8 = static_cast<uint8_t>(val);
	layerAttachments = (layerAttachments & 0b11111100) | val8;
}
constexpr void setBottomEdge(uint8_t &layerAttachments, unsigned val) noexcept {
	const auto val8 = static_cast<uint8_t>(val);
	layerAttachments = (layerAttachments & 0b11110011) | static_cast<uint8_t>(val8 << 2);
}
constexpr void setLeftEdge(uint8_t &layerAttachments, unsigned val) noexcept {
	const auto val8 = static_cast<uint8_t>(val);
	layerAttachments = (layerAttachments & 0b11001111) | static_cast<uint8_t>(val8 << 4);
}
constexpr void setRightEdge(uint8_t &layerAttachments, unsigned val) noexcept {
	const auto val8 = static_cast<uint8_t>(val);
	layerAttachments = (layerAttachments & 0b00111111) | static_cast<uint8_t>(val8 << 6);
}

[[nodiscard]]
constexpr unsigned topEdge(uint8_t layerAttachments) noexcept {
	return layerAttachments & 0b11;
}
[[nodiscard]]
constexpr unsigned bottomEdge(uint8_t layerAttachments) noexcept {
	return (layerAttachments >> 2) & 0b11;
}
[[nodiscard]]
constexpr unsigned leftEdge(uint8_t layerAttachments) noexcept {
	return (layerAttachments >> 4) & 0b11;
}
[[nodiscard]]
constexpr unsigned rightEdge(uint8_t layerAttachments) noexcept {
	return (layerAttachments >> 6) & 0b11;
}


struct SceneStatic {

	constexpr static auto TypeName = "net.drinkingtea.nostalgia.scene.SceneStatic";
	constexpr static auto TypeVersion = 1;
	constexpr static auto Preloadable = true;

	struct Tile {
		uint16_t &tileMapIdx;
		uint8_t &tileType;
		uint8_t &layerAttachments;
		constexpr Tile(uint16_t &pTileMapIdx, uint8_t &pTileType, uint8_t &pLayerAttachments) noexcept:
			tileMapIdx(pTileMapIdx),
			tileType(pTileType),
			layerAttachments(pLayerAttachments) {
		}
	};
	struct Layer {
		uint16_t &columns;
		uint16_t &rows;
		ox::Vector<uint16_t> &tileMapIdx;
		ox::Vector<uint8_t> &tileType;
		ox::Vector<uint8_t> &layerAttachments;
		constexpr Layer(
				uint16_t &pColumns,
				uint16_t &pRows,
				ox::Vector<uint16_t> &pTileMapIdx,
				ox::Vector<uint8_t> &pTileType,
				ox::Vector<uint8_t> &pLayerAttachments) noexcept:
			columns(pColumns),
			rows(pRows),
			tileMapIdx(pTileMapIdx),
			tileType(pTileType),
			layerAttachments(pLayerAttachments) {
		}
		[[nodiscard]]
		constexpr Tile tile(std::size_t i) noexcept {
			return {tileMapIdx[i], tileType[i], layerAttachments[i]};
		}
		constexpr auto setDimensions(ox::Size dim) noexcept {
			columns = static_cast<uint16_t>(dim.width);
			rows = static_cast<uint16_t>(dim.height);
			const auto tileCnt = static_cast<unsigned>(columns * rows);
			tileMapIdx.resize(tileCnt);
			tileType.resize(tileCnt);
			layerAttachments.resize(tileCnt);
		}
	};

	ox::FileAddress tilesheet;
	ox::Vector<ox::FileAddress> palettes;
	// tile layer data
	ox::Vector<uint16_t> columns;
	ox::Vector<uint16_t> rows;
	ox::Vector<ox::Vector<uint16_t>> tileMapIdx;
	ox::Vector<ox::Vector<uint8_t>>  tileType;
	ox::Vector<ox::Vector<uint8_t>>  layerAttachments;

	[[nodiscard]]
	constexpr Layer layer(std::size_t i) noexcept {
		return {
			columns[i],
			rows[i],
			tileMapIdx[i],
			tileType[i],
			layerAttachments[i],
		};
	}

	constexpr auto setLayerCnt(std::size_t layerCnt) noexcept {
		this->layerAttachments.resize(layerCnt);
		this->columns.resize(layerCnt);
		this->rows.resize(layerCnt);
		this->tileMapIdx.resize(layerCnt);
		this->tileType.resize(layerCnt);
	}

};

oxModelBegin(SceneStatic)
	oxModelField(tilesheet)
	oxModelField(palettes)
	oxModelField(columns)
	oxModelField(rows)
	oxModelField(tileMapIdx)
	oxModelField(tileType)
	oxModelField(layerAttachments)
oxModelEnd()

}
