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

#pragma once

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

#include <nostalgia/gfx/tilesheet.hpp>

#include "worldobject.hpp"

namespace jasper::world {

namespace ngfx = nostalgia::gfx;

struct SpriteDoc {
	constexpr static auto TypeName = "net.drinkingtea.jasper.world.SpriteDoc";
	constexpr static auto TypeVersion = 1;
	constexpr static auto Preloadable = true;
	ox::String tilesheetPath;
	ox::Vector<ngfx::SubSheetId> subsheetId;
};

struct DocObjRef {
	constexpr static auto TypeName = "net.drinkingtea.jasper.world.DocObjRef";
	constexpr static auto TypeVersion = 1;
	uint64_t worldObjectSetId{};
	ObjectId worldObjectId{};
	bool unique{};
};

constexpr bool operator==(DocObjRef const &a, DocObjRef const &b) noexcept {
	return a.worldObjectSetId == b.worldObjectSetId
	    && a.worldObjectId == b.worldObjectId
	    && a.unique == b.unique;
}

OX_MODEL_BEGIN(DocObjRef)
	OX_MODEL_FIELD_RENAME(worldObjectSetId, world_object_set_id)
	OX_MODEL_FIELD_RENAME(worldObjectId, world_object_id)
	OX_MODEL_FIELD(unique)
OX_MODEL_END()

struct TileDoc {
	constexpr static auto TypeName = "net.drinkingtea.jasper.world.TileDoc";
	constexpr static auto TypeVersion = 1;
	DocObjRef obj;
	uint8_t type = 0;
	uint8_t palBank = 0;
	uint8_t topLayerAttachment{};
	uint8_t bottomLayerAttachment{};
	uint8_t leftLayerAttachment{};
	uint8_t rightLayerAttachment{};
};

OX_MODEL_BEGIN(TileDoc)
	OX_MODEL_FIELD(obj)
	OX_MODEL_FIELD(type)
	OX_MODEL_FIELD_RENAME(palBank, pal_bank)
	OX_MODEL_FIELD_RENAME(topLayerAttachment, top_layer_attachment)
	OX_MODEL_FIELD_RENAME(bottomLayerAttachment, bottom_layer_attachment)
	OX_MODEL_FIELD_RENAME(leftLayerAttachment, left_layer_attachment)
	OX_MODEL_FIELD_RENAME(rightLayerAttachment, right_layer_attachment)
OX_MODEL_END()


struct ObjectSetEntry {
	constexpr static auto TypeName = "net.drinkingtea.jasper.world.ObjectSetEntry";
	constexpr static auto TypeVersion = 1;
	ox::String path;
	uint64_t id{};
};

OX_MODEL_BEGIN(ObjectSetEntry)
	OX_MODEL_FIELD(path)
	OX_MODEL_FIELD(id)
OX_MODEL_END()


struct WorldDoc {
	using TileMapRow = ox::Vector<TileDoc>;
	using TileMapLayer = ox::Vector<TileMapRow>;
	using TileMap = ox::Array<TileMapLayer, 3>;
	constexpr static auto TypeName = "net.drinkingtea.jasper.world.WorldDoc";
	constexpr static auto TypeVersion = 1;
	ox::Vector<ObjectSetEntry> objSets; // paths
	uint64_t objSetIdIdx{};
	int32_t columns{};
	int32_t rows{};
	TileMap tiles;
};

[[nodiscard]]
constexpr ox::Result<ox::String> objectSetPath(
		WorldDoc const &wd, uint64_t setId) noexcept {
	auto const obj = ox::find_if(wd.objSets.begin(), wd.objSets.end(),
		[setId](ObjectSetEntry const &e) {
			return e.id == setId;
		});
	OX_RETURN_ERROR(ox::Error(obj == wd.objSets.end(), "Obj set not found"));
	return obj->path;
}

[[nodiscard]]
constexpr ox::Result<uint64_t> objectSetId(
		WorldDoc const &wd, ox::StringViewCR setPath) noexcept {
	auto const obj = ox::find_if(wd.objSets.begin(), wd.objSets.end(),
		[setPath](ObjectSetEntry const &e) {
			return e.path == setPath;
		});
	OX_RETURN_ERROR(ox::Error(obj == wd.objSets.end(), "Obj set not found"));
	return obj->id;
}

OX_MODEL_BEGIN(WorldDoc)
	OX_MODEL_FIELD_RENAME(objSets, object_sets)
	OX_MODEL_FIELD_RENAME(objSetIdIdx, object_set_id_idx)
	OX_MODEL_FIELD(columns)
	OX_MODEL_FIELD(rows)
	OX_MODEL_FIELD(tiles)
OX_MODEL_END()

[[nodiscard]]
constexpr bool valid(WorldDoc const &doc) noexcept {
	for (auto &lyr : doc.tiles) {
		if (lyr.size() < static_cast<size_t>(doc.rows)) {
			return false;
		}
		for (auto &row : lyr) {
			if (row.size() < static_cast<size_t>(doc.columns)) {
				return false;
			}
		}
	}
	return true;
}

constexpr ox::Error repair(WorldDoc &doc) noexcept {
	for (auto &lyr : doc.tiles) {
		lyr.resize(static_cast<size_t>(doc.rows));
		for (auto &row : lyr) {
			row.resize(static_cast<size_t>(doc.columns));
		}
	}
	return {};
}

[[nodiscard]]
auto &tile(
	ox::CommonRefWith<WorldDoc> auto &doc, size_t lyr,
	ox::IntegerRange_c<500> auto col, ox::IntegerRange_c<500> auto row) noexcept {
	return doc.tiles[lyr][static_cast<size_t>(row)][static_cast<size_t>(col)];
}

void resize(WorldDoc &doc, ox::Size const &sz) noexcept;

}

template<>
struct ox::hash<jasper::world::DocObjRef> {
	[[nodiscard]]
	constexpr size_t operator()(jasper::world::DocObjRef const &v) const noexcept {
		return static_cast<uint32_t>(v.worldObjectSetId) 
		     | (static_cast<uint32_t>(v.worldObjectId) << 15)
		     | (static_cast<uint32_t>(v.unique) << 31);
	}
};
