/*
 * Copyright 2015 - 2022 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 https://mozilla.org/MPL/2.0/.
 */

#pragma once

#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include <ox/std/memory.hpp>

#include "fieldcounter.hpp"
#include "optype.hpp"

namespace ox {

struct TypeNameCatcher {

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

	constexpr TypeNameCatcher() noexcept = default;

	template<typename T = std::nullptr_t>
	constexpr void setTypeInfo(const char *n = T::TypeName, int v = 0) noexcept {
		this->name = n;
		this->version = v;
	}

	template<typename T>
	constexpr Error field(const char*, T*, std::size_t) noexcept {
		return OxError(0);
	}

	template<typename T>
	constexpr Error field(const char*, T) noexcept {
		return OxError(0);
	}

	template<typename ...Args>
	constexpr Error fieldCString(Args&&...) noexcept {
		return OxError(0);
	}

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

};

struct TypeInfoCatcher {

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

	constexpr TypeInfoCatcher() noexcept = default;

	template<typename T = std::nullptr_t>
	constexpr void setTypeInfo(const char *n = T::TypeName, int v = T::TypeVersion, int = 0) noexcept {
		this->name = n;
		this->version = v;
	}

	template<typename T>
	constexpr Error field(const char*, T*, std::size_t) noexcept {
		return OxError(0);
	}

	template<typename T>
	constexpr Error field(const char*, T) noexcept {
		return OxError(0);
	}

	template<typename T>
	constexpr Error fieldCString(const char*, T) noexcept {
		return OxError(0);
	}

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

};

template<typename T>
constexpr int getModelTypeVersion() noexcept {
	auto a = std::allocator<T>();
	auto t = a.allocate(1);
	TypeInfoCatcher nc;
	oxIgnoreError(model(&nc, t));
	a.deallocate(t, 1);
	return nc.version;
}

template<typename T>
constexpr int getModelTypeVersion(T *val) noexcept {
	TypeInfoCatcher nc;
	oxIgnoreError(model(&nc, val));
	return nc.version;
}

template<typename T>
consteval int requireModelTypeVersion() noexcept {
	constexpr auto version = getModelTypeVersion<T>();
	static_assert(version != 0, "TypeVersion is required");
	return version;
}

template<typename T, typename Str = const char*>
constexpr Str getModelTypeName() noexcept {
	auto a = std::allocator<T>();
	auto t = a.allocate(1);
	TypeNameCatcher nc;
	oxIgnoreError(model(&nc, t));
	a.deallocate(t, 1);
	return nc.name;
}

template<typename T, typename Str = const char*>
constexpr Str getModelTypeName(T *val) noexcept {
	TypeNameCatcher nc;
	oxIgnoreError(model(&nc, val));
	return nc.name;
}

template<typename T>
consteval auto requireModelTypeName() noexcept {
	constexpr auto name = getModelTypeName<T>();
	return name;
}

template<typename T, typename Str = const char*>
constexpr auto ModelTypeName_v = getModelTypeName<T, Str>();

template<typename T, typename Str = const char*>
constexpr auto ModelTypeVersion_v = requireModelTypeVersion<T>();

}
