/*
 * 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/byteswap.hpp>
#include <ox/std/bstring.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/types.hpp>
#include <ox/std/vector.hpp>

#include "desctypes.hpp"
#include "fieldcounter.hpp"
#include "metadata.hpp"
#include "modelhandleradaptor.hpp"
#include "optype.hpp"
#include "typenamecatcher.hpp"
#include "types.hpp"
#include "typestore.hpp"

namespace ox {

namespace detail {

template<typename T>
constexpr int indirectionLevels_v = 0;

template<typename T>
constexpr int indirectionLevels_v<T*> = 1 + indirectionLevels_v<T>;

template<typename T, std::size_t sz>
constexpr int indirectionLevels_v<T[sz]> = 1 + indirectionLevels_v<T>;

}

class TypeDescWriter {

	private:
		TypeStore *m_typeStore = nullptr;
		DescriptorType *m_type = nullptr;

	public:
		explicit constexpr TypeDescWriter(TypeStore *typeStore = nullptr) noexcept;

		constexpr ~TypeDescWriter() noexcept = default;

		template<typename T = std::nullptr_t>
		constexpr void setTypeInfo(const char *name = T::TypeName,
		                           int version = T::TypeVersion,
								   int fields = ModelFieldCount_v<T>) noexcept;

		template<typename T>
		constexpr Error field(const char *name, const T *val, std::size_t valLen) noexcept;

		template<typename T>
		constexpr Error field(const char *name, T val) noexcept;

		template<typename T>
		constexpr Error field(const char *name, const T *val) noexcept;

		template<typename ...Args>
		constexpr Error fieldCString(const char *name, Args&&...) noexcept;

		[[nodiscard]]
		constexpr DescriptorType *definition() noexcept {
			return m_type;
		}

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

	private:
		[[nodiscard]]
		constexpr const DescriptorType *type(const int8_t *val) const noexcept;
		[[nodiscard]]
		constexpr const DescriptorType *type(const int16_t *val) const noexcept;
		[[nodiscard]]
		constexpr const DescriptorType *type(const int32_t *val) const noexcept;
		[[nodiscard]]
		constexpr const DescriptorType *type(const int64_t *val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(const uint8_t *val) const noexcept;
		[[nodiscard]]
		constexpr const DescriptorType *type(const uint16_t *val) const noexcept;
		[[nodiscard]]
		constexpr const DescriptorType *type(const uint32_t *val) const noexcept;
		[[nodiscard]]
		constexpr const DescriptorType *type(const uint64_t *val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(const bool *val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(const char *val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(SerStr val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *type(const String *val) const noexcept;

		template<std::size_t sz>
		[[nodiscard]]
		constexpr const DescriptorType *type(const BString<sz> *val) const noexcept;

		template<typename T>
		[[nodiscard]]
		constexpr const DescriptorType *type(const T *val) const noexcept;

		template<typename T>
		[[nodiscard]]
		constexpr const DescriptorType *type(const HashMap<String, T> *val) const noexcept;

		template<typename U>
		[[nodiscard]]
		constexpr const DescriptorType *type(UnionView<U> val) const noexcept;

		[[nodiscard]]
		constexpr const DescriptorType *getType(const String &tn, int typeVersion, PrimitiveType t, int b) const noexcept;

};

constexpr TypeDescWriter::TypeDescWriter(TypeStore *typeStore) noexcept: m_typeStore(typeStore) {
}

template<typename T>
constexpr void TypeDescWriter::setTypeInfo(const char *typeName, int typeVersion, int) noexcept {
	m_type = m_typeStore->getInit(typeName, typeVersion);
	if constexpr(is_union_v<T>) {
		m_type->primitiveType = PrimitiveType::Union;
	} else {
		m_type->primitiveType = PrimitiveType::Struct;
	}
	m_type->preloadable = detail::preloadable<T>::value;
}

// array handler
template<typename T>
constexpr Error TypeDescWriter::field(const char *name, const T *val, std::size_t) noexcept {
	if (m_type) {
		constexpr typename remove_pointer<decltype(val)>::type *p = nullptr;
		const auto t = type(p);
		oxAssert(t != nullptr, "field(const char *name, T *val, std::size_t): Type not found or generated");
		m_type->fieldList.emplace_back(t, name, detail::indirectionLevels_v<T> + 1, t->typeName, t->typeVersion);
		return OxError(0);
	}
	return OxError(1);
}

template<typename T>
constexpr Error TypeDescWriter::field(const char *name, T val) noexcept {
	if (m_type) {
		if constexpr(isVector_v<typename ox::remove_pointer<T>::type> || isArray_v<typename ox::remove_pointer<T>::type>) {
			return field(name, val->data(), val->size());
		} else {
			const auto t = type(val);
			oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
			m_type->fieldList.emplace_back(t, name, 0, t->typeName, t->typeVersion);
			return OxError(0);
		}
	}
	return OxError(1);
}

template<typename T>
constexpr Error TypeDescWriter::field(const char *name, const T *val) noexcept {
	if (m_type) {
		if constexpr(isVector_v<T> || isArray_v<T>) {
			return field(name, val->data(), val->size());
		} else {
			const auto t = type(val);
			oxAssert(t != nullptr, "field(const char *name, T *val): Type not found or generated");
			m_type->fieldList.emplace_back(t, name, 0, t->typeName, t->typeVersion);
			return OxError(0);
		}
	}
	return OxError(1);
}

template<typename ...Args>
constexpr Error TypeDescWriter::fieldCString(const char *name, Args&&...) noexcept {
	String s;
	return field(name, &s);
}

template<typename T>
constexpr const DescriptorType *TypeDescWriter::type(const T *val) const noexcept {
	if constexpr(isVector_v<T>) {
		return type(val->data());
	} else {
		auto [t, err] = m_typeStore->template get<T>();
		if (!err) {
			return t;
		} else {
			TypeDescWriter dw(m_typeStore);
			const auto reflectErr = model(&dw, val);
			oxLogError(reflectErr);
			oxAssert(reflectErr, "field(const char *name, T val): Type info could not be generated");
			return dw.m_type;
		}
	}
}

template<typename T>
constexpr const DescriptorType *TypeDescWriter::type(const HashMap<String, T>*) const noexcept {
	return type(static_cast<T*>(nullptr));
}

template<typename U>
constexpr const DescriptorType *TypeDescWriter::type(UnionView<U> val) const noexcept {
	const auto t = type(val.get());
	oxAssert(t != nullptr, "field(const char *name, T val): Type not found or generated");
	return t;
}

constexpr const DescriptorType *TypeDescWriter::type(const int8_t*) const noexcept {
	constexpr auto PT = PrimitiveType::SignedInteger;
	constexpr auto Bytes = 1;
	return getType(types::Int8, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const int16_t*) const noexcept {
	constexpr auto PT = PrimitiveType::SignedInteger;
	constexpr auto Bytes = 2;
	return getType(types::Int16, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const int32_t*) const noexcept {
	constexpr auto PT = PrimitiveType::SignedInteger;
	constexpr auto Bytes = 4;
	return getType(types::Int32, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const int64_t*) const noexcept {
	constexpr auto PT = PrimitiveType::SignedInteger;
	constexpr auto Bytes = 8;
	return getType(types::Int64, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const uint8_t*) const noexcept {
	constexpr auto PT = PrimitiveType::UnsignedInteger;
	constexpr auto Bytes = 1;
	return getType(types::Uint8, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const uint16_t*) const noexcept {
	constexpr auto PT = PrimitiveType::UnsignedInteger;
	constexpr auto Bytes = 2;
	return getType(types::Uint16, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const uint32_t*) const noexcept {
	constexpr auto PT = PrimitiveType::UnsignedInteger;
	constexpr auto Bytes = 4;
	return getType(types::Uint32, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const uint64_t*) const noexcept {
	constexpr auto PT = PrimitiveType::UnsignedInteger;
	constexpr auto Bytes = 8;
	return getType(types::Uint64, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const bool*) const noexcept {
	constexpr auto PT = PrimitiveType::Bool;
	constexpr auto Bytes = 0;
	return getType(types::Bool, 0, PT, Bytes);
}

constexpr const DescriptorType *TypeDescWriter::type(const char*) const noexcept {
	constexpr auto PT = PrimitiveType::String;
	return getType(types::String, 0, PT, 0);
}

constexpr const DescriptorType *TypeDescWriter::type(SerStr) const noexcept {
	constexpr auto PT = PrimitiveType::String;
	return getType(types::String, 0, PT, 0);
}

constexpr const DescriptorType *TypeDescWriter::type(const String*) const noexcept {
	constexpr auto PT = PrimitiveType::String;
	return getType(types::String, 0, PT, 0);
}

template<std::size_t sz>
constexpr const DescriptorType *TypeDescWriter::type(const BString<sz>*) const noexcept {
	constexpr auto PT = PrimitiveType::String;
	return getType(types::String, 0, PT, 0);
}

constexpr const DescriptorType *TypeDescWriter::getType(const String &tn, int typeVersion, PrimitiveType pt, int b) const noexcept {
	auto t = m_typeStore->get(tn, typeVersion);
	if (!t.error) {
		auto type = t.value;
		oxAssert(type != nullptr, "TypeDescWriter::getType returning null DescriptorType");
		return type;
	} else {
		auto dt = ox::make_unique<DescriptorType>(tn, typeVersion);
		dt->primitiveType = pt;
		dt->length = b;
		const auto out = dt.get();
		m_typeStore->set(tn, typeVersion, std::move(dt));
		return out;
	}
}

template<typename T>
constexpr Result<DescriptorType*> buildTypeDef(TypeStore *typeStore) noexcept {
	TypeDescWriter writer(typeStore);
	ModelHandlerInterface handler(&writer);
	if (std::is_constant_evaluated()) {
		std::allocator<T> a;
		T *t = a.allocate(1);
		oxReturnError(model(&handler, t));
		a.deallocate(t, 1);
	} else {
		T *t = reinterpret_cast<T*>(ox_alloca(sizeof(T)));
		oxReturnError(model(&handler, t));
	}
	return writer.definition();
}

template<typename T>
constexpr Result<DescriptorType*> buildTypeDef(TypeStore *typeStore, T *val) noexcept {
	TypeDescWriter writer(typeStore);
	ModelHandlerInterface handler(&writer);
	oxReturnError(model(&handler, val));
	return writer.definition();
}

}
