/*
 * Copyright 2015 - 2025 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/error.hpp>

#include "desctypes.hpp"

namespace ox {

template<typename Reader, typename T>
class DataWalker {
	template<typename ReaderBase, typename FH>
	friend constexpr Error parseField(const DescriptorField &field, ReaderBase *rdr, DataWalker<ReaderBase, FH> *walker) noexcept;

	private:
		Vector<const DescriptorType*> m_typeStack;
		T m_fieldHandler;
		Vector<FieldName> m_path;
		Vector<String> m_typePath;

	public:
		constexpr DataWalker(DescriptorType *type, T fieldHandler) noexcept;

		constexpr Result<const DescriptorType*> type() const noexcept;

		constexpr Error read(const DescriptorField&, Reader *rdr) noexcept;

	protected:
		constexpr void pushNamePath(const FieldName &fn) noexcept;

		constexpr void popNamePath() noexcept;

		constexpr void pushType(const DescriptorType *type) noexcept;

		constexpr void popType() noexcept;

};

template<typename Reader, typename T>
constexpr DataWalker<Reader, T>::DataWalker(DescriptorType *type, T fieldHandler) noexcept: m_fieldHandler(fieldHandler) {
	m_typeStack.push_back(type);
}

template<typename Reader, typename T>
constexpr Result<const DescriptorType*> DataWalker<Reader, T>::type() const noexcept {
	OX_REQUIRE(out, m_typeStack.back());
	return *out;
}

template<typename Reader, typename T>
constexpr Error DataWalker<Reader, T>::read(const DescriptorField &f, Reader *rdr) noexcept {
	// get const ref of paths
	const auto &pathCr = m_path;
	const auto &typePathCr = m_typePath;
	return m_fieldHandler(pathCr, typePathCr, f, rdr);
}

template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::pushNamePath(const FieldName &fn) noexcept {
	m_path.emplace_back(fn);
}

template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::popNamePath() noexcept {
	m_path.pop_back();
}

template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::pushType(const DescriptorType *type) noexcept {
	m_typeStack.push_back(type);
}

template<typename Reader, typename T>
constexpr void DataWalker<Reader, T>::popType() noexcept {
	m_typeStack.pop_back();
}

template<typename Reader, typename FH>
static constexpr Error parseField(const DescriptorField &field, Reader *rdr, DataWalker<Reader, FH> *walker) noexcept {
	walker->pushNamePath(field.fieldName);
	if (field.subscriptLevels) {
		// add array handling
		OX_REQUIRE(arrayLen, rdr->arrayLength(field.fieldName.c_str(), true));
		auto child = rdr->child(field.fieldName.c_str());
		OX_RETURN_ERROR(child.setTypeInfo(field.type->typeName.c_str(), field.type->typeVersion, field.type->typeParams, arrayLen));
		DescriptorField f(field); // create mutable copy
		--f.subscriptLevels;
		String subscript;
		for (std::size_t i = 0; i < arrayLen; i++) {
			subscript = "[";
			subscript += static_cast<uint64_t>(i);
			subscript += "]";
			walker->pushNamePath(subscript);
			OX_RETURN_ERROR(parseField(f, &child, walker));
			walker->popNamePath();
		}
		rdr->nextField();
	} else {
		switch (field.type->primitiveType) {
			case PrimitiveType::UnsignedInteger:
			case PrimitiveType::SignedInteger:
			case PrimitiveType::Bool:
			case PrimitiveType::String:
				OX_RETURN_ERROR(walker->read(field, rdr));
				break;
			case PrimitiveType::Struct:
			case PrimitiveType::Union:
				if (rdr->fieldPresent(field.fieldName.c_str())) {
					auto child = rdr->child(field.fieldName.c_str());
					walker->pushType(field.type);
					OX_RETURN_ERROR(model(&child, walker));
					walker->popType();
					rdr->nextField();
				} else {
					// skip and discard absent field
					int discard;
					OX_RETURN_ERROR(rdr->field(field.fieldName.c_str(), &discard));
				}
				break;
		}
	}
	walker->popNamePath();
	return {};
}

template<typename Reader, typename FH>
constexpr Error model(Reader *rdr, DataWalker<Reader, FH> *walker) noexcept {
	OX_REQUIRE(type, walker->type());
	auto typeName = type->typeName.c_str();
	auto typeVersion = type->typeVersion;
	auto typeParams = type->typeParams;
	auto &fields = type->fieldList;
	OX_RETURN_ERROR(rdr->setTypeInfo(typeName, typeVersion, typeParams, fields.size()));
	for (const auto &field : fields) {
		OX_RETURN_ERROR(parseField(field, rdr, walker));
	}
	return {};
}

template<typename Reader, typename Handler>
constexpr Error walkModel(DescriptorType *type, Reader_c auto &reader, Handler handler) noexcept {
	DataWalker<Reader, Handler> walker(type, handler);
	Reader rdr(reader);
	return model(&rdr, &walker);
}

template<typename Reader, typename Handler>
constexpr Error walkModel(DescriptorType *type, const char *data, std::size_t dataLen, Handler handler) noexcept {
	DataWalker<Reader, Handler> walker(type, handler);
	Reader rdr(reinterpret_cast<const uint8_t*>(data), dataLen);
	return model(&rdr, &walker);
}

}
