/*
 * 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/bit.hpp>
#include <ox/std/error.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/typetraits.hpp>
#include <ox/std/vector.hpp>

#include "optype.hpp"
#include "types.hpp"

namespace ox {

using FieldName = String;

enum class PrimitiveType: uint8_t {
	UnsignedInteger = 0,
	SignedInteger = 1,
	Bool = 2,
	// Float = 3, reserved, but not implemented
	String = 4,
	Struct = 5,
	Union = 6,
};

struct DescriptorField {
	// order of fields matters

	static constexpr auto TypeName = "net.drinkingtea.ox.DescriptorField";
	static constexpr auto TypeVersion = 2;

	// do not serialize type
	const struct DescriptorType *type = nullptr;
	String fieldName;
	int subscriptLevels = 0;
	String typeName; // gives reference to type for lookup if type is null
	int typeVersion = 0;
	bool list = false;

	constexpr DescriptorField() noexcept = default;

	constexpr DescriptorField(const DescriptorType *pType, String pFieldName,
	                          int pSubscriptLevels, String pTypeName, int pTypeVersion) noexcept:
		type(pType),
		fieldName(std::move(pFieldName)),
		subscriptLevels(pSubscriptLevels),
		typeName(std::move(pTypeName)),
		typeVersion(pTypeVersion) {
	}

	constexpr DescriptorField(const DescriptorField &other) noexcept:
		type(other.type),
		fieldName(other.fieldName),
		subscriptLevels(other.subscriptLevels),
		typeName(other.typeName) {
	}

	constexpr DescriptorField(DescriptorField &&other) noexcept:
		type(other.type),
		fieldName(other.fieldName),
		subscriptLevels(other.subscriptLevels),
		typeName(std::move(other.typeName)) {
		other.type = {};
		other.subscriptLevels = {};
	}

	constexpr ~DescriptorField() noexcept = default;

	constexpr DescriptorField &operator=(const DescriptorField &other) noexcept = delete;

	constexpr DescriptorField &operator=(DescriptorField &&other) noexcept = delete;

};

using FieldList = Vector<DescriptorField>;

struct DescriptorType {

	static constexpr auto TypeName = "net.drinkingtea.ox.DescriptorType";
	static constexpr auto TypeVersion = 2;

	String typeName;
	int typeVersion = 0;
	PrimitiveType primitiveType = PrimitiveType::UnsignedInteger;
	// fieldList only applies to structs
	FieldList fieldList;
	// - number of bytes for integer and float types
	// - number of fields for structs and lists
	int64_t length = 0;
	bool preloadable = false;

	constexpr DescriptorType() noexcept = default;

	constexpr explicit DescriptorType(String tn, int typeVersion) noexcept:
		typeName(std::move(tn)), typeVersion(typeVersion) {
	}

	constexpr DescriptorType(String tn, PrimitiveType t, int b) noexcept:
		typeName(std::move(tn)),
		primitiveType(t),
		length(b) {
	}

	constexpr DescriptorType(String tn, PrimitiveType t, FieldList fl) noexcept:
		typeName(std::move(tn)),
		primitiveType(t),
		fieldList(std::move(fl)) {
	}

};


template<typename T>
constexpr Error model(T *io, CommonPtrWith<DescriptorType> auto *type) noexcept {
	io->template setTypeInfo<DescriptorType>();
	oxReturnError(io->field("typeName", &type->typeName));
	oxReturnError(io->field("typeVersion", &type->typeVersion));
	if constexpr(ox_strcmp(T::opType(), OpType::Reflect) == 0) {
		uint8_t pt = 0;
		oxReturnError(io->field("primitiveType", &pt));
	} else {
		auto pt = type ? static_cast<uint8_t>(type->primitiveType) : 0;
		oxReturnError(io->field("primitiveType", &pt));
		type->primitiveType = static_cast<PrimitiveType>(pt);
	}
	oxReturnError(io->field("fieldList", &type->fieldList));
	oxReturnError(io->field("length", &type->length));
	oxReturnError(io->field("preloadable", &type->preloadable));
	return OxError(0);
}

template<typename T>
constexpr Error model(T *io, CommonPtrWith<DescriptorField> auto *field) noexcept {
	io->template setTypeInfo<DescriptorField>();
	oxReturnError(io->field("typeName", &field->typeName));
	oxReturnError(io->field("fieldName", &field->fieldName));
	oxReturnError(io->field("subscriptLevels", &field->subscriptLevels));
	// defaultValue is unused now, but leave placeholder for backwards compatibility
	int defaultValue = 0;
	oxReturnError(io->field("defaultValue", &defaultValue));
	return OxError(0);
}

template<typename ReaderBase>
class TypeDescReader;

template<typename T>
constexpr Error model(TypeDescReader<T> *io, CommonPtrWith<DescriptorField> auto *field) noexcept {
	io->template setTypeInfo<DescriptorField>(DescriptorField::TypeName, DescriptorField::TypeVersion, 4);
	oxReturnError(io->field("typeName", &field->typeName));
	auto &typeStore = io->typeStore();
	auto &[type, err] = typeStore->at(field->typeName).value;
	oxReturnError(err);
	field->type = type.get();
	oxReturnError(io->field("fieldName", &field->fieldName));
	oxReturnError(io->field("subscriptLevels", &field->subscriptLevels));
	// defaultValue is unused now, but placeholder for backwards compatibility
	int defaultValue = 0;
	oxReturnError(io->field("defaultValue", &defaultValue));
	return OxError(0);
}

}
