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

#include <keel/context.hpp>
#include <keel/media.hpp>
#include <keel/typeconv.hpp>

namespace keel {

static ox::Result<BaseConverter const*> findConverter(
		ox::SpanView<Converter> const &converters,
		ox::StringViewCR srcTypeName,
		int const srcTypeVersion,
		ox::StringViewCR dstTypeName,
		int const dstTypeVersion) noexcept {
	for (auto const &c : converters) {
		if (c.converter().matches(srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion)) {
			return &c.converter();
		}
	}
	return ox::Error{1, "Could not find converter"};
};

static ox::Result<ox::UPtr<Wrap>> convert(BaseConverter const &c, Context &ctx, ox::BufferView const &src) noexcept {
	return c.convertBuffToPtr(ctx, src);
}

static ox::Result<ox::UPtr<Wrap>> convert(BaseConverter const &c, Context &ctx, Wrap &src) noexcept {
	return c.convertPtrToPtr(ctx, src);
}

static ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		ox::SpanView<Converter> const &converters,
		auto &src,
		ox::StringViewCR srcTypeName,
		int const srcTypeVersion,
		ox::StringViewCR dstTypeName,
		int const dstTypeVersion) noexcept {
	// look for direct converter
	auto const [c, err] = findConverter(
			converters, srcTypeName, srcTypeVersion, dstTypeName, dstTypeVersion);
	if (!err) {
		return convert(*c, ctx, src);
	}
	// try to chain multiple converters
	for (auto const &subConverter : converters) {
		if (!subConverter.converter().dstMatches(dstTypeName, dstTypeVersion)) {
			continue;
		}
		auto const [intermediate, chainErr] =
			convert(ctx, converters, src, srcTypeName, srcTypeVersion,
			        subConverter.converter().srcTypeName(), subConverter.converter().srcTypeVersion());
		if (!chainErr) {
			return subConverter.converter().convertPtrToPtr(ctx, *intermediate);
		}
	}
	return ox::Error{1, "Could not convert between types"};
}

ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		ox::BufferView const &srcBuffer,
		ox::StringViewCR dstTypeName,
		int const dstTypeVersion) noexcept {
	OX_REQUIRE(hdr, readAssetHeader(srcBuffer));
	return convert(
			ctx,
			converters(ctx),
			srcBuffer,
			hdr.clawHdr.typeName,
			hdr.clawHdr.typeVersion,
			dstTypeName,
			dstTypeVersion);
}

ox::Result<ox::UPtr<Wrap>> convert(
		Context &ctx,
		Wrap &src,
		ox::StringViewCR dstTypeName,
		int const dstTypeVersion) noexcept {
	return convert(
			ctx,
			converters(ctx),
			src,
			src.typeName(),
			src.typeVersion(),
			dstTypeName,
			dstTypeVersion);
}

}
