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

#pragma once

#include <ox/claw/write.hpp>
#include <ox/std/def.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>

#include "asset.hpp"

namespace keel {

class Context;

class Wrap {
	public:
		virtual ~Wrap() = default;
		[[nodiscard]]
		virtual ox::StringLiteral typeName() const noexcept = 0;
		[[nodiscard]]
		virtual int typeVersion() const noexcept = 0;
		[[nodiscard]]
		virtual ox::UAnyPtr moveToCopy() noexcept = 0;
};

template<typename T>
class WrapT: public Wrap {
	public:
		[[nodiscard]]
		virtual constexpr T &obj() noexcept = 0;

		[[nodiscard]]
		ox::UAnyPtr moveToCopy() noexcept final {
			return new T{std::move(obj())};
		}

};

template<typename T>
class WrapRef final: public WrapT<T> {
	private:
		T &m_obj;

	public:
		constexpr explicit WrapRef(T &obj): m_obj{obj} {}

		[[nodiscard]]
		ox::StringLiteral typeName() const noexcept override {
			return ox::ModelTypeName_v<T>;
		}

		[[nodiscard]]
		int typeVersion() const noexcept override {
			return ox::ModelTypeVersion_v<T>;
		}

		[[nodiscard]]
		constexpr T &obj() noexcept override {
			return m_obj;
		}

};

template<typename T>
class WrapInline final: public WrapT<T> {
	private:
		T m_obj;

	public:
		constexpr WrapInline() = default;

		template<typename... Args>
		constexpr explicit WrapInline(Args &&...args): m_obj(ox::forward<Args>(args)...) {
		}

		[[nodiscard]]
		ox::StringLiteral typeName() const noexcept override {
			return ox::ModelTypeName_v<T>;
		}

		[[nodiscard]]
		int typeVersion() const noexcept override {
			return ox::ModelTypeVersion_v<T>;
		}

		[[nodiscard]]
		constexpr T &obj() noexcept override {
			return m_obj;
		}

};

template<typename T, typename... Args>
constexpr ox::UPtr<Wrap> makeWrap(Args &&...args) noexcept {
	return ox::make_unique<WrapInline<T>>(ox::forward<Args>(args)...);
}

template<typename T>
constexpr T &wrapCast(Wrap &ptr) noexcept {
	return static_cast<WrapT<T>&>(ptr).obj();
}

class BaseConverter {
	public:
		constexpr virtual ~BaseConverter() noexcept = default;

		[[nodiscard]]
		constexpr virtual ox::StringLiteral srcTypeName() const noexcept = 0;

		[[nodiscard]]
		constexpr virtual int srcTypeVersion() const noexcept = 0;

		[[nodiscard]]
		constexpr virtual bool srcMatches(ox::StringViewCR pSrcTypeName, int pSrcTypeVersion) const noexcept = 0;

		[[nodiscard]]
		constexpr virtual bool dstMatches(ox::StringViewCR dstTypeName, int dstTypeVersion) const noexcept = 0;

		virtual ox::Result<ox::UPtr<Wrap>> convertPtrToPtr(keel::Context &ctx, Wrap &src) const noexcept = 0;

		virtual ox::Result<ox::UPtr<Wrap>> convertBuffToPtr(
				Context &ctx, ox::BufferView const &srcBuff) const noexcept = 0;

		[[nodiscard]]
		constexpr bool matches(
				ox::StringViewCR srcTypeName, int const srcTypeVersion,
				ox::StringViewCR dstTypeName, int const dstTypeVersion) const noexcept {
			return srcMatches(srcTypeName, srcTypeVersion)
			    && dstMatches(dstTypeName, dstTypeVersion);
		}

};


template<auto Func>
class ConverterFunc final: public BaseConverter {
	private:
		template<typename SrcType, typename DstType>
		struct ParamPack {
			using Src = ox::remove_const_t<SrcType>;
			using Dst = DstType;
		};

		template<typename Src, typename Dst>
		static ParamPack<Src, Dst> extractParams(ox::Error (*)(Context&, Src&, Dst&)) {
			return {};
		}

	public:
		using SrcType = typename decltype(extractParams(Func))::Src;
		using DstType = typename decltype(extractParams(Func))::Dst;

		[[nodiscard]]
		constexpr ox::StringLiteral srcTypeName() const noexcept override {
			return ox::ModelTypeName_v<SrcType>;
		}

		[[nodiscard]]
		constexpr int srcTypeVersion() const noexcept override {
			return ox::ModelTypeVersion_v<SrcType>;
		}

		[[nodiscard]]
		constexpr bool srcMatches(ox::StringViewCR pSrcTypeName, int pSrcTypeVersion) const noexcept override {
			constexpr auto SrcTypeName = ox::requireModelTypeName<SrcType>();
			constexpr auto SrcTypeVersion = ox::requireModelTypeVersion<SrcType>();
			return pSrcTypeName == SrcTypeName
			    && pSrcTypeVersion == SrcTypeVersion;
		}

		[[nodiscard]]
		constexpr bool dstMatches(ox::StringViewCR dstTypeName, int dstTypeVersion) const noexcept override {
			constexpr auto DstTypeName = ox::StringView{ox::requireModelTypeName<DstType>()};
			constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
			return dstTypeName == DstTypeName
			    && dstTypeVersion == DstTypeVersion;
		}

		ox::Result<ox::UPtr<Wrap>> convertPtrToPtr(
				Context &ctx, Wrap &src) const noexcept override {
			ox::Result<ox::UPtr<Wrap>> dst{makeWrap<DstType>()};
			OX_RETURN_ERROR(convert(ctx, wrapCast<SrcType>(src), wrapCast<DstType>(*dst.value)));
			return dst;
		}

		ox::Result<ox::UPtr<Wrap>> convertBuffToPtr(
				Context &ctx, ox::BufferView const &srcBuff) const noexcept override {
			OX_REQUIRE_M(src, readAsset<SrcType>(srcBuff));
			ox::Result<ox::UPtr<Wrap>> dst{makeWrap<DstType>()};
			OX_RETURN_ERROR(convert(ctx, src, wrapCast<DstType>(*dst.value)));
			return dst;
		}

	protected:
		static ox::Error convert(Context &ctx, SrcType &src, DstType &dst) noexcept {
			return Func(ctx, src, dst);
		}

};

class Converter {
	private:
		ox::AllocAlias<BaseConverter> m_buff{};
	public:
		template<auto Func>
		static Converter make() noexcept {
			Converter out;
			static_assert(sizeof(ConverterFunc<Func>) <= sizeof(out.m_buff));
			new (out.m_buff.data()) ConverterFunc<Func>{};
			return out;
		}
		[[nodiscard]]
		BaseConverter const &converter() const noexcept {
			return *std::launder(m_buff.data());
		}
};

ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		ox::BufferView const &srcBuffer,
		ox::StringViewCR dstTypeName,
		int dstTypeVersion) noexcept;

ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		Wrap &src,
		ox::StringViewCR dstTypeName,
		int dstTypeVersion) noexcept;

ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		auto &src,
		ox::StringViewCR dstTypeName,
		int const dstTypeVersion) noexcept {
	WrapRef ref{src};
	return convert(ctx, static_cast<Wrap&>(ref), dstTypeName, dstTypeVersion);
}

ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		auto const &src,
		ox::StringViewCR dstTypeName,
		int const dstTypeVersion) noexcept {
	auto srcCpy = src;
	WrapRef ref{srcCpy};
	return convert(ctx, ref, dstTypeName, dstTypeVersion);
}

template<typename DstType>
ox::Result<DstType> convertObjToObj(
		Context &ctx,
		auto &src) noexcept {
	OX_REQUIRE_M(out,
		convert(ctx, WrapRef{src}, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
	return std::move(wrapCast(*out));
}

template<typename DstType>
ox::Result<DstType> convert(Context &ctx, ox::BufferView const &src) noexcept {
	OX_REQUIRE(out,
		convert(ctx, src, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
	return std::move(wrapCast<DstType>(*out));
}

template<typename DstType>
ox::Error convert(Context &ctx, ox::BufferView const &buff, DstType &outObj) noexcept {
	OX_REQUIRE(out,
		convert(ctx, buff, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
	outObj = std::move(wrapCast<DstType>(*out));
	return {};
}

template<typename DstType>
ox::Error convertObjToObj(Context &ctx, auto &src, DstType &outObj) noexcept {
	OX_REQUIRE(outPtr, convert(ctx, src, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
	outObj = std::move(wrapCast<DstType>(*outPtr));
	return {};
}

template<typename DstType>
ox::Result<ox::Buffer> convertBuffToBuff(
		Context &ctx, ox::BufferView const &src, ox::ClawFormat const fmt) noexcept {
	OX_REQUIRE(out,
		convert(ctx, src, ox::ModelTypeName_v<DstType>, ox::ModelTypeVersion_v<DstType>));
	return ox::writeClaw<DstType>(wrapCast<DstType>(*out), fmt);
}

template<typename From, typename To, ox::ClawFormat fmt = ox::ClawFormat::Metal>
ox::Result<bool> transformRule(Context &ctx, ox::Buffer &buff, ox::StringViewCR typeId) noexcept {
	if (typeId == ox::ModelTypeId_v<From>) {
		OX_RETURN_ERROR(keel::convertBuffToBuff<To>(ctx, buff, fmt).moveTo(buff));
		return true;
	}
	return false;
};

using PackTransform = ox::Result<bool>(*)(Context&, ox::Buffer &clawData, ox::StringViewCR);

}
