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

#pragma once

#include <ox/std/defines.hpp>

#include <ox/claw/claw.hpp>
#include <ox/fs/fs.hpp>
#include <ox/model/metadata.hpp>


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

namespace keel {

// Pointer to preloaded data that can be stored in FS in place of the actual
// data.
struct PreloadPtr {
	static constexpr auto TypeName = "net.drinkingtea.keel.PreloadPtr";
	static constexpr auto TypeVersion = 2;
	uint64_t preloadAddr = 0;
};

oxModelBegin(PreloadPtr)
	oxModelField(preloadAddr)
oxModelEnd()

ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::FileAddress const&file) noexcept;
ox::Result<std::size_t> getPreloadAddr(keel::Context &ctx, ox::CRStringView file) noexcept;

#ifndef OX_BARE_METAL

template<typename T>
ox::Result<keel::AssetRef<T>> readObjFile(
		keel::Context &ctx,
		ox::StringView assetId,
		bool forceLoad) noexcept {
	constexpr auto readConvert = [](Context &ctx, const ox::Buffer &buff) -> ox::Result<T> {
		auto [obj, err] = readAsset<T>(buff);
		if (err) {
			if (err != ox::Error_ClawTypeVersionMismatch && err != ox::Error_ClawTypeMismatch) {
				return err;
			}
			oxReturnError(convert<T>(ctx, buff, &obj));
		}
		return std::move(obj);
	};
	ox::StringView path;
	if (beginsWith(assetId, "uuid://")) {
		assetId = substr(assetId, 7);
		oxRequire(p, ctx.uuidToPath.at(assetId));
		path = *p;
	} else {
		path = assetId;
	}
	if (forceLoad) {
		oxRequire(buff, ctx.rom->read(path));
		oxRequire(obj, readConvert(ctx, buff));
		oxRequire(cached, ctx.assetManager.setAsset(assetId, obj));
		return cached;
	} else {
		auto [cached, err] = ctx.assetManager.getAsset<T>(assetId);
		if (err) {
			oxRequire(buff, ctx.rom->read(path));
			oxRequire(obj, readConvert(ctx, buff));
			oxReturnError(ctx.assetManager.setAsset(assetId, obj).moveTo(&cached));
		}
		return cached;
	}
}

#else

template<typename T>
ox::Result<keel::AssetRef<T>> readObjNoCache(
		keel::Context &ctx,
		ox::CRStringView assetId) noexcept {
	if constexpr(ox::preloadable<T>::value) {
		oxRequire(addr, getPreloadAddr(ctx, assetId));
		return keel::AssetRef<T>(reinterpret_cast<const T*>(addr));
	} else {
		return OxError(1);
	}
}

#endif

void createUuidMapping(Context &ctx, ox::StringView filePath, ox::UUID const&uuid) noexcept;

ox::Error buildUuidMap(Context &ctx) noexcept;

ox::Result<ox::String> uuidToPath(Context &ctx, ox::CRStringView uuid) noexcept;

ox::Result<ox::String> uuidToPath(Context &ctx, ox::UUID const&uuid) noexcept;

ox::Error performPackTransforms(Context &ctx, ox::Buffer &clawData) noexcept;

template<typename T>
ox::Result<AssetRef<T>> setAsset(keel::Context &ctx, ox::StringView assetId, T const&asset) noexcept {
#ifndef OX_BARE_METAL
	if (assetId.len() == 0) {
		return OxError(1, "Invalid asset ID");
	}
	ox::UUIDStr idStr;
	if (assetId[0] == '/') {
		const auto [id, err] = ctx.pathToUuid.at(assetId);
		oxReturnError(err);
		idStr = id->toString();
		assetId = idStr;
	}
	return ctx.assetManager.setAsset(assetId, asset);
#else
	return OxError(1, "Not supported on this platform");
#endif
}

template<typename T>
ox::Result<keel::AssetRef<T>> readObj(
		keel::Context &ctx,
		ox::CRStringView assetId,
		[[maybe_unused]] bool forceLoad = false) noexcept {
#ifndef OX_BARE_METAL
	return readObjFile<T>(ctx, assetId, forceLoad);
#else
	return readObjNoCache<T>(ctx, assetId);
#endif
}

template<typename T>
ox::Result<keel::AssetRef<T>> readObj(
		keel::Context &ctx,
		ox::FileAddress const&file,
		[[maybe_unused]] bool forceLoad = false) noexcept {
#ifndef OX_BARE_METAL
	oxRequire(assetId, file.getPath());
	return readObj<T>(ctx, ox::StringView(assetId), forceLoad);
#else
	if constexpr(ox::preloadable<T>::value) {
		oxRequire(addr, getPreloadAddr(ctx, file));
		return keel::AssetRef<T>(reinterpret_cast<const T*>(addr));
	} else {
		return OxError(1);
	}
#endif
}

template<typename T>
ox::Error writeObj(
		keel::Context &ctx,
		ox::FileAddress const&file,
		T const&obj,
		ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept {
	oxRequire(objBuff, ox::writeClaw(obj, fmt));
	return ctx.rom->write(file, objBuff.data(), objBuff.size());
}

ox::Error setRomFs(Context &ctx, ox::UPtr<ox::FileSystem> &&fs) noexcept;

ox::Result<ox::UniquePtr<ox::FileSystem>> loadRomFs(ox::CRStringView path) noexcept;

ox::Result<char*> loadRom(ox::CRStringView path = "") noexcept;

void unloadRom(char*) noexcept;

}
