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

#pragma once

#include <ox/fs/fs.hpp>
#include <ox/std/array.hpp>
#include <ox/std/point.hpp>
#include <ox/std/size.hpp>
#include <ox/std/span.hpp>
#include <ox/std/types.hpp>
#include <ox/model/def.hpp>

#include <nostalgia/core/ptidxconv.hpp>

#include "palette.hpp"

namespace nostalgia::core {

// Predecessor to TileSheet, kept for backward compatibility
struct TileSheetV1 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaGraphic";
	static constexpr auto TypeVersion = 1;
	int8_t bpp = 0;
	// rows and columns are really only used by TileSheetEditor
	int rows = 1;
	int columns = 1;
	ox::FileAddress defaultPalette;
	Palette pal;
	ox::Vector<uint8_t> pixels = {};
};

struct TileSheetV2 {
	using SubSheetIdx = ox::Vector<std::size_t, 4>;

	struct SubSheet {
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet";
		static constexpr auto TypeVersion = 1;
		ox::String name;
		int columns = 0;
		int rows = 0;
		ox::Vector<SubSheet> subsheets;
		ox::Vector<uint8_t> pixels;
		constexpr SubSheet() noexcept = default;
		constexpr SubSheet(ox::CRStringView pName, int pColumns, int pRows, int bpp) noexcept:
			name(pName),
			columns(pColumns),
			rows(pRows),
			pixels(static_cast<size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
		}
	};

	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
	static constexpr auto TypeVersion = 2;
	int8_t bpp = 4;
	ox::FileAddress defaultPalette;
	SubSheet subsheet{"Root", 1, 1, bpp};

};

using SubSheetId = int32_t;

struct TileSheetV3 {
	using SubSheetIdx = ox::Vector<std::size_t, 4>;

	struct SubSheet {
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet";
		static constexpr auto TypeVersion = 3;
		SubSheetId id = 0;
		ox::String name;
		int columns = 0;
		int rows = 0;
		ox::Vector<SubSheet> subsheets;
		ox::Vector<uint8_t> pixels;
		constexpr SubSheet() noexcept = default;
		inline SubSheet(
				SubSheetId pId,
				ox::CRStringView pName,
				int pColumns,
				int pRows,
				int bpp) noexcept:
				id(pId),
				name(pName),
				columns(pColumns),
				rows(pRows),
				pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
		}

	};

	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
	static constexpr auto TypeVersion = 3;
	int8_t bpp = 4;
	SubSheetId idIt = 0;
	ox::FileAddress defaultPalette;
	SubSheet subsheet{0, "Root", 1, 1, bpp};

};

struct TileSheet {
	using SubSheetIdx = ox::Vector<std::size_t, 4>;

	struct SubSheet {
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet.SubSheet";
		static constexpr auto TypeVersion = 4;
		SubSheetId id = 0;
		ox::String name;
		int columns = 0;
		int rows = 0;
		ox::Vector<SubSheet> subsheets;
		ox::Vector<uint8_t> pixels;

		constexpr SubSheet() noexcept = default;
		inline SubSheet(
				SubSheetId pId,
				ox::CRStringView pName,
				int pColumns,
				int pRows,
				int bpp) noexcept:
				id(pId),
				name(pName),
				columns(pColumns),
				rows(pRows),
				pixels(static_cast<std::size_t>(columns * rows * PixelsPerTile) / (bpp == 4 ? 2u : 1u)) {
		}
		inline SubSheet(
				SubSheetId pId,
				ox::CRStringView pName,
				int pColumns,
				int pRows,
				ox::Vector<uint8_t> pPixels) noexcept:
				id(pId),
				name(pName),
				columns(pColumns),
				rows(pRows),
				pixels(std::move(pPixels)) {
		}

		[[nodiscard]]
		constexpr std::size_t size() const noexcept {
			return static_cast<std::size_t>(columns) * static_cast<std::size_t>(rows);
		}

	};

	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.TileSheet";
	static constexpr auto TypeVersion = 4;
	int8_t bpp = 4;
	SubSheetId idIt = 0;
	ox::FileAddress defaultPalette;
	SubSheet subsheet{0, "Root", 1, 1, bpp};

	constexpr TileSheet() noexcept = default;

};

[[nodiscard]]
std::size_t idx(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;

[[nodiscard]]
size_t getTileCnt(TileSheet const&ts) noexcept;

[[nodiscard]]
TileSheet::SubSheet const*getSubsheet(TileSheet const&ts, SubSheetId id) noexcept;

[[nodiscard]]
size_t getTileIdx(TileSheet const&ts, SubSheetId id) noexcept;

[[nodiscard]]
uint8_t getPixel4Bpp(TileSheet::SubSheet const&ss, std::size_t idx) noexcept;

[[nodiscard]]
uint8_t getPixel8Bpp(TileSheet::SubSheet const&ss, std::size_t idx) noexcept;

[[nodiscard]]
uint8_t getPixel(TileSheet::SubSheet const&ss, int8_t pBpp, std::size_t idx) noexcept;

[[nodiscard]]
uint8_t getPixel4Bpp(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;

[[nodiscard]]
uint8_t getPixel8Bpp(TileSheet::SubSheet const&ss, ox::Point const&pt) noexcept;

[[nodiscard]]
uint8_t getPixel(TileSheet::SubSheet const&ss, int8_t pBpp, ox::Point const&pt) noexcept;

constexpr void walkPixels(TileSheet::SubSheet const&ss, int8_t pBpp, auto callback) noexcept {
	if (pBpp == 4) {
		const auto pixelCnt = ox::min<std::size_t>(
				static_cast<std::size_t>(ss.columns * ss.rows * PixelsPerTile) / 2,
				ss.pixels.size());
		//oxAssert(pixels.size() == pixelCnt, "Pixel count does not match rows and columns");
		for (std::size_t i = 0; i < pixelCnt; ++i) {
			const auto colorIdx1 = static_cast<uint8_t>(ss.pixels[i] & 0xF);
			const auto colorIdx2 = static_cast<uint8_t>(ss.pixels[i] >> 4);
			callback(i * 2 + 0, colorIdx1);
			callback(i * 2 + 1, colorIdx2);
		}
	} else {
		const auto pixelCnt = ox::min<std::size_t>(
				static_cast<std::size_t>(ss.columns * ss.rows * PixelsPerTile),
				ss.pixels.size());
		for (std::size_t i = 0; i < pixelCnt; ++i) {
			const auto p = ss.pixels[i];
			callback(i, p);
		}
	}
}

void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept;

void setPixel(TileSheet::SubSheet &ss, int8_t pBpp, ox::Point const&pt, uint8_t palIdx) noexcept;

ox::Error setPixelCount(TileSheet::SubSheet &ss, int8_t pBpp, std::size_t cnt) noexcept;

/**
 * Gets a count of the pixels in this sheet, and not that of its children.
 * @param pBpp bits per pixel, need for knowing how to count the pixels
 * @return a count of the pixels in this sheet
 */
[[nodiscard]]
unsigned pixelCnt(TileSheet::SubSheet const&ss, int8_t pBpp) noexcept;

/**
 * validateSubSheetIdx takes a SubSheetIdx and moves the index to the
 * preceding or parent sheet if the current corresponding sheet does
 * not exist.
 * @param idx SubSheetIdx to validate and correct
 * @return a valid version of idx
 */
[[nodiscard]]
TileSheet::SubSheetIdx validateSubSheetIdx(TileSheet const&ts, TileSheet::SubSheetIdx const&idx) noexcept;

[[nodiscard]]
TileSheet::SubSheet const&getSubSheet(
		TileSheet::SubSheetIdx const&idx,
		std::size_t idxIt,
		TileSheet::SubSheet const&pSubsheet) noexcept;

[[nodiscard]]
TileSheet::SubSheet &getSubSheet(
		TileSheet::SubSheetIdx const&idx,
		std::size_t idxIt,
		TileSheet::SubSheet &pSubsheet) noexcept;

[[nodiscard]]
TileSheet::SubSheet const&getSubSheet(TileSheet const&ts, TileSheet::SubSheetIdx const&idx) noexcept;

[[nodiscard]]
TileSheet::SubSheet &getSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept;

ox::Error addSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept;

ox::Error rmSubSheet(
		TileSheet &ts,
		TileSheet::SubSheetIdx const&idx,
		std::size_t idxIt,
		TileSheet::SubSheet &pSubsheet) noexcept;

ox::Error rmSubSheet(TileSheet &ts, TileSheet::SubSheetIdx const&idx) noexcept;

[[nodiscard]]
uint8_t getPixel4Bpp(
		TileSheet const&ts,
		ox::Point const&pt,
		TileSheet::SubSheetIdx const&subsheetIdx) noexcept;

[[nodiscard]]
uint8_t getPixel8Bpp(
		TileSheet const&ts,
		ox::Point const&pt,
		TileSheet::SubSheetIdx const&subsheetIdx) noexcept;

ox::Result<SubSheetId> getIdFor(TileSheet const&ts, ox::CRStringView path) noexcept;

ox::Result<unsigned> getTileOffset(TileSheet const&ts, ox::CRStringView pNamePath) noexcept;

ox::Result<ox::StringView> getNameFor(TileSheet::SubSheet const&ss, SubSheetId pId) noexcept;

ox::Result<ox::StringView> getNameFor(TileSheet const&ss, SubSheetId pId) noexcept;

[[nodiscard]]
ox::Vector<uint8_t> pixels(TileSheet &ts) noexcept;

using TileSheetV4 = TileSheet;

struct CompactTileSheet {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.CompactTileSheet";
	static constexpr auto TypeVersion = 1;
	int8_t bpp = 0;
	ox::FileAddress defaultPalette;
	ox::Vector<uint8_t> pixels = {};
};

oxModelBegin(TileSheetV1)
	oxModelField(bpp)
	oxModelField(rows)
	oxModelField(columns)
	oxModelField(defaultPalette)
	oxModelField(pal)
	oxModelField(pixels)
oxModelEnd()

oxModelBegin(TileSheetV2::SubSheet)
	oxModelField(name)
	oxModelField(rows)
	oxModelField(columns)
	oxModelField(subsheets)
	oxModelField(pixels)
oxModelEnd()

oxModelBegin(TileSheetV2)
	oxModelField(bpp)
	oxModelField(defaultPalette)
	oxModelField(subsheet)
oxModelEnd()

oxModelBegin(TileSheetV3::SubSheet)
	oxModelField(name)
	oxModelField(rows)
	oxModelField(columns)
	oxModelField(subsheets)
	oxModelField(pixels)
oxModelEnd()

oxModelBegin(TileSheetV3)
	oxModelField(bpp)
	oxModelField(idIt)
	oxModelField(defaultPalette)
	oxModelField(subsheet)
oxModelEnd()

oxModelBegin(TileSheetV4::SubSheet)
	oxModelField(id)
	oxModelField(name)
	oxModelField(rows)
	oxModelField(columns)
	oxModelField(subsheets)
	oxModelField(pixels)
oxModelEnd()

oxModelBegin(TileSheetV4)
	oxModelField(bpp)
	oxModelField(idIt)
	oxModelField(defaultPalette)
	oxModelField(subsheet)
oxModelEnd()

oxModelBegin(CompactTileSheet)
	oxModelField(bpp)
	oxModelField(defaultPalette)
	oxModelField(pixels)
oxModelEnd()

ox::Vector<uint32_t> resizeTileSheetData(
		ox::Vector<uint32_t> const&srcPixels,
		ox::Size const&srcSize,
		int scale = 2) noexcept;

}
