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

#pragma once

#include <ox/std/vector.hpp>
#include <ox/model/def.hpp>

#include <turbine/turbine.hpp>

#include "color.hpp"

namespace nostalgia::gfx {

struct PaletteColorV1 {
    static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.PaletteColor";
    static constexpr auto TypeVersion = 1;
    uint8_t r{}, g{}, b{}, a{};
    constexpr PaletteColorV1() noexcept = default;
    constexpr PaletteColorV1(Color16 const c) noexcept:
        r{red16(c)},
        g{green16(c)},
        b{blue16(c)},
        a{alpha16(c)} {}
    constexpr operator Color16() const noexcept { return color16(r, g, b, a); }
};

OX_MODEL_BEGIN(PaletteColorV1)
	OX_MODEL_FIELD(r)
	OX_MODEL_FIELD(g)
	OX_MODEL_FIELD(b)
	OX_MODEL_FIELD(a)
OX_MODEL_END()


struct PaletteColorV2 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.PaletteColor";
	static constexpr auto TypeVersion = 2;
	uint8_t r{}, g{}, b{}, a{};
	constexpr PaletteColorV2() noexcept = default;
	constexpr PaletteColorV2(Color16 const c) noexcept:
        r{red16(c)},
        g{green16(c)},
        b{blue16(c)},
        a{alpha16(c)} {}
	constexpr PaletteColorV2(uint8_t const r, uint8_t const g, uint8_t const b, uint8_t const a) noexcept:
        r{r}, g{g}, b{b}, a{a} {}
	constexpr operator Color16() const noexcept { return color16(r, g, b, a); }
};

OX_MODEL_BEGIN(PaletteColorV2)
	OX_MODEL_FIELD(r)
	OX_MODEL_FIELD(g)
	OX_MODEL_FIELD(b)
	OX_MODEL_FIELD(a)
OX_MODEL_END()


using PaletteColor = PaletteColorV2;


struct PalettePageV1 {
    static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.PalettePage";
    static constexpr auto TypeVersion = 1;
    ox::String name;
    ox::Vector<PaletteColorV1> colors;
    constexpr PalettePageV1() noexcept = default;
    constexpr PalettePageV1(ox::StringParam pName, ox::Vector<PaletteColorV1> pColors) noexcept:
        name(std::move(pName)), colors(std::move(pColors)) {}
    constexpr PalettePageV1(ox::StringParam pName, ox::Vector<Color16> const&pColors) noexcept:
        name(std::move(pName)) {
        colors.reserve(pColors.size());
        for (auto const c : pColors) {
            colors.emplace_back(c);
        }
    }
};

OX_MODEL_BEGIN(PalettePageV1)
	OX_MODEL_FIELD(name)
	OX_MODEL_FIELD(colors)
OX_MODEL_END()


struct PalettePageV2 {
    static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.Palette.PalettePage";
    static constexpr auto TypeVersion = 2;
    ox::String name;
    ox::Vector<PaletteColorV2> colors;
    constexpr PalettePageV2() noexcept = default;
    constexpr PalettePageV2(ox::StringParam pName, ox::Vector<PaletteColorV2> pColors) noexcept:
        name(std::move(pName)), colors(std::move(pColors)) {}
    constexpr PalettePageV2(ox::StringParam pName, ox::Vector<Color16> const&pColors) noexcept:
        name(std::move(pName)) {
        colors.reserve(pColors.size());
        for (auto const c : pColors) {
            colors.emplace_back(c);
        }
    }
};

OX_MODEL_BEGIN(PalettePageV2)
	OX_MODEL_FIELD(name)
	OX_MODEL_FIELD(colors)
OX_MODEL_END()


using PalettePage = PalettePageV2;


struct NostalgiaPalette {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.NostalgiaPalette";
	static constexpr auto TypeVersion = 1;
	ox::Vector<Color16> colors = {};
};

OX_MODEL_BEGIN(NostalgiaPalette)
	OX_MODEL_FIELD(colors)
OX_MODEL_END()


struct PaletteV1 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
	static constexpr auto TypeVersion = 1;
	ox::Vector<Color16> colors;
};

OX_MODEL_BEGIN(PaletteV1)
	OX_MODEL_FIELD(colors)
OX_MODEL_END()


struct PaletteV2 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
	static constexpr auto TypeVersion = 2;
	static constexpr auto Preloadable = true;
	ox::Vector<ox::Vector<Color16>> pages;
};

OX_MODEL_BEGIN(PaletteV2)
	OX_MODEL_FIELD(pages)
OX_MODEL_END()


struct PaletteV3 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
	static constexpr auto TypeVersion = 3;
	static constexpr auto Preloadable = true;
	struct ColorInfo {
		static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette.ColorInfo";
		static constexpr auto TypeVersion = 3;
		ox::String name;
		constexpr ColorInfo() noexcept = default;
		constexpr ColorInfo(ox::StringParam pName) noexcept: name{std::move(pName)} {}
	};
	ox::Vector<ColorInfo> colorInfo;
	ox::Vector<ox::Vector<Color16>> pages;
};

OX_MODEL_BEGIN(PaletteV3::ColorInfo)
	OX_MODEL_FIELD(name)
OX_MODEL_END()

OX_MODEL_BEGIN(PaletteV3)
	OX_MODEL_FIELD(colorInfo)
	OX_MODEL_FIELD(pages)
OX_MODEL_END()

[[nodiscard]]
constexpr bool valid(PaletteV3 const &p) noexcept {
	auto const colors = p.colorInfo.size();
	return ox::all_of(p.pages.begin(), p.pages.end(), [colors](auto const &page) {
		return page.size() == colors;
	});
}

constexpr ox::Error repair(PaletteV3 &p) noexcept {
	auto const colors = p.colorInfo.size();
	for (auto &page : p.pages) {
		page.resize(colors);
	}
	return {};
}


struct PaletteV4 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.core.Palette";
	static constexpr auto TypeVersion = 4;
	static constexpr auto Preloadable = true;
	ox::Vector<ox::String> colorNames;
	ox::Vector<PalettePageV1> pages;
};

OX_MODEL_BEGIN(PaletteV4)
	OX_MODEL_FIELD(colorNames)
	OX_MODEL_FIELD(pages)
OX_MODEL_END()

[[nodiscard]]
constexpr bool valid(PaletteV4 const &p) noexcept {
	auto const colors = p.colorNames.size();
	return ox::all_of(p.pages.begin(), p.pages.end(), [colors](PalettePageV1 const &page) {
		return page.colors.size() == colors;
	});
}

constexpr ox::Error repair(PaletteV4 &p) noexcept {
	auto const colors = p.colorNames.size();
	for (auto &page : p.pages) {
		page.colors.resize(colors);
	}
	return {};
}


struct PaletteV5 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.Palette";
	static constexpr auto TypeVersion = 5;
	static constexpr auto Preloadable = true;
	ox::Vector<ox::String> colorNames;
	ox::Vector<PalettePageV2> pages;
};

OX_MODEL_BEGIN(PaletteV5)
	OX_MODEL_FIELD(colorNames)
	OX_MODEL_FIELD(pages)
OX_MODEL_END()

[[nodiscard]]
constexpr bool valid(PaletteV5 const&p) noexcept {
	auto const colors = p.colorNames.size();
	return ox::all_of(p.pages.begin(), p.pages.end(), [colors](PalettePageV2 const&page) {
		return page.colors.size() == colors;
	});
}

constexpr ox::Error repair(PaletteV5 &p) noexcept {
	auto const colors = p.colorNames.size();
	for (auto &page : p.pages) {
		page.colors.resize(colors);
	}
	return {};
}


using Palette = PaletteV5;


struct CompactPaletteV1 {
	static constexpr auto TypeName = "net.drinkingtea.nostalgia.gfx.CompactPalette";
	static constexpr auto TypeVersion = 1;
	static constexpr auto Preloadable = true;
	ox::Vector<ox::Vector<Color16>> pages{};
};

OX_MODEL_BEGIN(CompactPaletteV1)
	OX_MODEL_FIELD(pages)
OX_MODEL_END()

[[nodiscard]]
constexpr bool valid(CompactPaletteV1 const&p) noexcept {
	size_t colors{};
	for (auto const&page : p.pages) {
		colors = ox::max(colors, page.size());
	}
	return ox::all_of(p.pages.begin(), p.pages.end(), [colors](ox::Vector<Color16> const&page) {
		return page.size() == colors;
	});
}

constexpr ox::Error repair(CompactPaletteV1 &p) noexcept {
	size_t colors{};
	for (auto const&page : p.pages) {
		colors = ox::max(colors, page.size());
	}
	for (auto &page : p.pages) {
		page.resize(colors);
	}
	return {};
}


using CompactPalette = CompactPaletteV1;


[[nodiscard]]
constexpr Color16 color(Palette const&pal, size_t const page, size_t const idx) noexcept {
	if (page < pal.pages.size() && idx < pal.pages[page].colors.size()) [[likely]] {
		return pal.pages[page].colors[idx];
	}
	return 0;
}

[[nodiscard]]
constexpr Color16 color(CompactPalette const&pal, size_t const page, size_t const idx) noexcept {
	if (page < pal.pages.size() && idx < pal.pages[page].size()) [[likely]] {
		return pal.pages[page][idx];
	}
	return 0;
}

[[nodiscard]]
constexpr Color16 color(Palette const&pal, size_t const idx) noexcept {
	return color(pal, 0, idx);
}

[[nodiscard]]
constexpr Color16 color(CompactPalette const&pal, size_t const idx) noexcept {
	return color(pal, 0, idx);
}

[[nodiscard]]
constexpr auto &colors(Palette &pal, size_t const page = 0) noexcept {
	return pal.pages[page].colors;
}

[[nodiscard]]
constexpr auto &colors(CompactPalette &pal, size_t const page = 0) noexcept {
	return pal.pages[page];
}

[[nodiscard]]
constexpr auto &colors(Palette const&pal, size_t const page = 0) noexcept {
	return pal.pages[page];
}

[[nodiscard]]
constexpr auto &colors(CompactPalette const&pal, size_t const page = 0) noexcept {
	return pal.pages[page];
}

[[nodiscard]]
constexpr size_t colorCnt(Palette const&pal, size_t const page = 0) noexcept {
	if (page < pal.pages.size()) [[likely]] {
		return pal.pages[page].colors.size();
	}
	return 0;
}

[[nodiscard]]
constexpr size_t colorCnt(CompactPalette const&pal, size_t const page = 0) noexcept {
	if (page < pal.pages.size()) [[likely]] {
		return pal.pages[page].size();
	}
	return 0;
}

[[nodiscard]]
constexpr size_t largestPage(Palette const&pal) noexcept {
	size_t out{};
	for (auto const&page : pal.pages) {
		out = ox::max(out, page.colors.size());
	}
	return out;
}

[[nodiscard]]
constexpr size_t largestPage(CompactPalette const&pal) noexcept {
	size_t out{};
	for (auto const&page : pal.pages) {
		out = ox::max(out, page.size());
	}
	return out;
}

}
