/*
 * Copyright 2016 - 2023 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 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 = 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;
		constexpr SubSheet(SubSheet const&other) noexcept = default;
		SubSheet(SubSheet &&other) noexcept;
		SubSheet(
				SubSheetId pId,
				ox::CRStringView pName,
				int pColumns,
				int pRows,
				int bpp) noexcept;
		SubSheet(
				SubSheetId pId,
				ox::CRStringView pName,
				int pColumns,
				int pRows,
				ox::Vector<uint8_t> pPixels) noexcept;

		constexpr SubSheet &operator=(const SubSheet &other) noexcept = default;

		SubSheet &operator=(SubSheet &&other) noexcept;

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

		/**
		 * Reads all pixels of this sheet or its children into the given pixel list
		 * @param pixels
		 */
		void readPixelsTo(ox::Vector<uint8_t> *pPixels, int8_t pBpp) const noexcept;

		/**
		 * Reads all pixels of this sheet or its children into the given pixel list
		 * @param pixels
		 */
		void readPixelsTo(ox::Vector<uint8_t> *pPixels) const noexcept;

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

		[[nodiscard]]
		std::size_t unusedPixels() const noexcept;

		[[nodiscard]]
		uint8_t getPixel4Bpp(std::size_t idx) const noexcept;

		[[nodiscard]]
		uint8_t getPixel8Bpp(std::size_t idx) const noexcept;

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

		[[nodiscard]]
		uint8_t getPixel4Bpp(const ox::Point &pt) const noexcept;

		[[nodiscard]]
		uint8_t getPixel8Bpp(const ox::Point &pt) const noexcept;

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

		constexpr auto walkPixels(int8_t pBpp, auto callback) const noexcept {
			if (pBpp == 4) {
				const auto pixelCnt = ox::min<std::size_t>(static_cast<std::size_t>(columns * rows * PixelsPerTile) / 2,
				                                           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>(pixels[i] & 0xF);
					const auto colorIdx2 = static_cast<uint8_t>(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>(columns * rows * PixelsPerTile),
						pixels.size());
				for (std::size_t i = 0; i < pixelCnt; ++i) {
					const auto p = pixels[i];
					callback(i, p);
				}
			}
		}

		void setPixel(int8_t pBpp, uint64_t idx, uint8_t palIdx) noexcept;

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

		ox::Error setPixelCount(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(int8_t pBpp) const noexcept;

		/**
		 * Gets the offset in tiles of the desired subsheet.
		 */
		ox::Result<unsigned> getTileOffset(
				ox::SpanView<ox::StringView> const&pNamePath,
				int8_t pBpp,
				std::size_t pIt = 0,
				unsigned pCurrentTotal = 0) const noexcept;

		ox::Result<SubSheetId> getIdFor(
				ox::SpanView<ox::StringView> const&pNamePath,
				std::size_t pIt = 0) const noexcept;

		ox::Result<ox::StringView> getNameFor(SubSheetId pId) const noexcept;

	};

	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};

	constexpr TileSheet() noexcept = default;
	TileSheet(TileSheet const&other) noexcept = default;
	inline TileSheet(TileSheet &&other) noexcept:
		bpp(other.bpp),
		idIt(other.idIt),
		defaultPalette(std::move(other.defaultPalette)),
		subsheet(std::move(other.subsheet)) {
	}

	TileSheet &operator=(TileSheet const&other) noexcept;

	TileSheet &operator=(TileSheet &&other) noexcept;

	[[nodiscard]]
	SubSheetIdx validateSubSheetIdx(
			SubSheetIdx const&pIdx,
			std::size_t pIdxIt,
			const SubSheet *pSubsheet) 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]]
	SubSheetIdx validateSubSheetIdx(const SubSheetIdx &idx) noexcept;

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

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

	[[nodiscard]]
	const SubSheet &getSubSheet(SubSheetIdx const&idx) const noexcept;

	[[nodiscard]]
	SubSheet &getSubSheet(SubSheetIdx const&idx) noexcept;

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

	[[nodiscard]]
	static ox::Error rmSubSheet(
			SubSheetIdx const&idx,
			std::size_t idxIt,
			SubSheet *pSubsheet) noexcept;

	[[nodiscard]]
	ox::Error rmSubSheet(SubSheetIdx const&idx) noexcept;

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

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

	ox::Result<SubSheetId> getIdFor(ox::CRStringView path) const noexcept;

	ox::Result<unsigned> getTileOffset(ox::CRStringView pNamePath) const noexcept;

	ox::Result<ox::StringView> getNameFor(SubSheetId pId) const noexcept;

	[[nodiscard]]
	ox::Vector<uint8_t> pixels() const noexcept;

};

using TileSheetV3 = 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(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;

}
