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

#pragma once

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

#include "asset.hpp"
#include "context.hpp"

namespace keel {

class Wrap {
	public:
		virtual ~Wrap() = default;
};

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

	public:
		constexpr WrapInline() = default;

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

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

};

template<typename T, typename... Args>
constexpr auto 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<WrapInline<T>&>(ptr).obj();
}

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

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

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

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

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

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

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

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

};

template<typename SrcType, typename DstType>
class Converter: public BaseConverter {
	public:
		[[nodiscard]]
		ox::StringView srcTypeName() const noexcept final {
			return ox::requireModelTypeName<SrcType>();
		}

		[[nodiscard]]
		int srcTypeVersion() const noexcept final {
			return ox::requireModelTypeVersion<SrcType>();
		}

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

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

		ox::Result<ox::UniquePtr<Wrap>> convertPtrToPtr(keel::Context &ctx, Wrap &src) const noexcept final {
			auto dst = makeWrap<DstType>();
			oxReturnError(convert(ctx, wrapCast<SrcType>(src), wrapCast<DstType>(*dst)));
			return ox::Result<ox::UniquePtr<Wrap>>(std::move(dst));
		}

		ox::Result<ox::UniquePtr<Wrap>> convertBuffToPtr(keel::Context &ctx, ox::Buffer const&srcBuff) const noexcept final {
			oxRequireM(src, readAsset<SrcType>(srcBuff));
			auto dst = makeWrap<DstType>();
			oxReturnError(convert(ctx, src, wrapCast<DstType>(*dst)));
			return ox::Result<ox::UniquePtr<Wrap>>(std::move(dst));
		}

	protected:
		virtual ox::Error convert(keel::Context &ctx, SrcType&, DstType&) const noexcept = 0;

};

ox::Result<ox::UniquePtr<Wrap>> convert(
		keel::Context &ctx, ox::Buffer const&srcBuffer,
		ox::CRStringView dstTypeName, int dstTypeVersion) noexcept;

template<typename DstType>
ox::Result<DstType> convert(keel::Context &ctx, ox::Buffer const&srcBuffer) noexcept {
	static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
	static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
	oxRequire(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
	return std::move(wrapCast<DstType>(out));
}

template<typename DstType>
ox::Error convert(keel::Context &ctx, ox::Buffer const&buff, DstType *outObj) noexcept {
	static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
	static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
	oxRequire(outPtr, convert(ctx, buff, DstTypeName, DstTypeVersion));
	*outObj = std::move(wrapCast<DstType>(*outPtr));
	return {};
}

template<typename DstType>
ox::Result<ox::Buffer> convertBuffToBuff(keel::Context &ctx, ox::Buffer const&srcBuffer, ox::ClawFormat fmt) noexcept {
	static constexpr auto DstTypeName = ox::requireModelTypeName<DstType>();
	static constexpr auto DstTypeVersion = ox::requireModelTypeVersion<DstType>();
	oxRequire(out, convert(ctx, srcBuffer, DstTypeName, DstTypeVersion));
	return ox::writeClaw<DstType>(wrapCast<DstType>(*out), fmt);
}

template<typename From, typename To, ox::ClawFormat fmt = ox::ClawFormat::Metal>
auto transformRule(keel::Context &ctx, ox::Buffer &buff) noexcept -> ox::Error {
	oxRequire(hdr, readAssetHeader(buff));
	const auto typeId = ox::buildTypeId(
			hdr.clawHdr.typeName, hdr.clawHdr.typeVersion, hdr.clawHdr.typeParams);
	if (typeId == ox::buildTypeId<From>()) {
		oxReturnError(keel::convertBuffToBuff<To>(ctx, buff, fmt).moveTo(buff));
	}
	return {};
};


}
