/*
 * Copyright 2015 - 2023 gary@drinkingtea.net
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/mc/write.hpp>
#ifdef OX_USE_STDLIB
#include <ox/oc/write.hpp>
#endif
#include <ox/std/buffer.hpp>
#include <ox/std/string.hpp>
#include <ox/std/stringview.hpp>

#include "format.hpp"

namespace ox {

namespace detail {

struct TypeInfoCatcher {

	const char *name = nullptr;
	int version = 0;

	template<typename T>
	constexpr ox::Error setTypeInfo(
			const char *pName = T::TypeName,
			int pVersion = T::TypeVersion,
			const Vector<String>& = {},
			std::size_t = 0) noexcept {
		this->name = pName;
		this->version = pVersion;
		return {};
	}

	constexpr Error field(...) noexcept {
		return OxError(0);
	}

	static constexpr auto opType() {
		return OpType::Write;
	}

};

template<typename T, typename = int>
struct type_version {
	static constexpr auto value = -1;
};

template<typename T>
struct type_version<T, decltype((void) T::TypeVersion, -1)> {
	static constexpr auto value = T::TypeVersion;
};

template<typename T>
constexpr const char *getTypeName(const T *t) noexcept {
	TypeInfoCatcher tnc;
	oxIgnoreError(model(&tnc, t));
	return tnc.name;
}

template<typename T>
constexpr int getTypeVersion(const T *t) noexcept {
	TypeInfoCatcher tnc;
	oxIgnoreError(model(&tnc, t));
	return tnc.version;
}

template<typename T>
ox::Error writeClawHeader(Writer_c auto &writer, const T *t, ClawFormat fmt) noexcept {
	switch (fmt) {
		case ClawFormat::Metal:
			oxReturnError(write(writer, "M2;"));
			break;
		case ClawFormat::Organic:
			oxReturnError(write(writer, "O1;"));
			break;
		default:
			return OxError(1);
	}
	oxReturnError(write(writer, detail::getTypeName(t)));
	oxReturnError(writer.put(';'));
	const auto tn = detail::getTypeVersion(t);
	if (tn > -1) {
		oxReturnError(ox::itoa(tn, writer));
	}
	oxReturnError(writer.put(';'));
	return {};
}

}

Result<Buffer> writeClaw(
		const auto &t,
		ClawFormat fmt = ClawFormat::Metal,
		std::size_t buffReserveSz = 2 * units::KB) noexcept {
	Buffer out(buffReserveSz);
	BufferWriter bw(&out, 0);
	oxReturnError(detail::writeClawHeader(bw, &t, fmt));
#ifdef OX_USE_STDLIB
	if (fmt == ClawFormat::Metal) {
		oxReturnError(writeMC(bw, t));
	} else if (fmt == ClawFormat::Organic) {
		oxRequire(data, writeOC(t));
		oxReturnError(bw.write(data.data(), data.size()));
	}
#else
	if (fmt != ClawFormat::Metal) {
		return OxError(1, "OC is not supported in this build");
	}
	oxReturnError(writeMC(bw, t));
#endif
	out.resize(bw.tellp());
	return out;
}

}
