/*
 * 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/utility.hpp>

#include "modelvalue.hpp"

namespace ox {

template<typename Handler, OpType opType_v = Handler::opType()>
class ModelHandlerInterface {
	private:
		Handler &m_handler;

	public:
		constexpr explicit ModelHandlerInterface(Handler &handler) noexcept: m_handler(handler) {
		}

		template<typename T = std::nullptr_t>
		constexpr ox::Error setTypeInfo(
			CString name = T::TypeName,
			int version = T::TypeVersion,
			Vector<String> const &typeParams = {}) noexcept {
			return m_handler.template setTypeInfo<T>(name, version, typeParams, ModelFieldCount_v<T>);
		}

		template<typename T = std::nullptr_t>
		constexpr ox::Error setTypeInfo(
			CString name,
			int version,
			Vector<String> const &typeParams,
			size_t fields) noexcept {
			return m_handler.template setTypeInfo<T>(name, version, typeParams, fields);
		}

		template<size_t len>
		constexpr Error fieldCString(CString name, char val[len]) noexcept {
			return m_handler.fieldCString(name, &val[0], len);
		}

		template<size_t len>
		constexpr Error fieldCString(CString name, char const val[len]) noexcept requires(opType_v != OpType::Read) {
			if constexpr(opType_v != OpType::Read) {
				return m_handler.fieldCString(name, &val[0], len);
			} else {
				return {};
			}
		}

		constexpr Error fieldCString(CString name, char **val) noexcept {
			return m_handler.fieldCString(name, val);
		}

		constexpr Error fieldCString(CString name, char const *const*val) noexcept requires(opType_v != OpType::Read) {
			// this check looks pointless, but it's to address a Clang bug
			if constexpr(opType_v != OpType::Read) {
				return m_handler.fieldCString(name, val);
			} else {
				return {};
			}
		}

		constexpr Error fieldCString(CString name, char const **val) noexcept requires(opType_v != OpType::Read) {
			// this check looks pointless, but it's to address a Clang bug
			if constexpr(opType_v != OpType::Read) {
				return m_handler.fieldCString(name, val);
			} else {
				return {};
			}
		}

		constexpr Error fieldCString(CString name, char **val, size_t buffLen) noexcept {
			return m_handler.fieldCString(name, val, buffLen);
		}

		constexpr Error fieldCString(CString name, char const **val, size_t buffLen) noexcept requires(opType_v != OpType::Read) {
			// this check looks pointless, but it's to address a Clang bug
			if constexpr(opType_v != OpType::Read) {
				return m_handler.fieldCString(name, val, buffLen);
			} else {
				return {};
			}
		}

		constexpr Error fieldCString(CString name, char *val, size_t buffLen) noexcept {
			return m_handler.fieldCString(name, val, buffLen);
		}

		constexpr Error fieldModelValue(char const *name, CommonPtrWith<ModelValue> auto *v) noexcept {
			switch (v->type()) {
				case ModelValue::Type::Undefined:
					break;
				case ModelValue::Type::Bool:
					return m_handler.field(name, &v->template get<bool>());
				case ModelValue::Type::UnsignedInteger8:
					return m_handler.field(name, &v->template get<uint8_t>());
				case ModelValue::Type::UnsignedInteger16:
					return m_handler.field(name, &v->template get<uint16_t>());
				case ModelValue::Type::UnsignedInteger32:
					return m_handler.field(name, &v->template get<uint32_t>());
				case ModelValue::Type::UnsignedInteger64:
					return m_handler.field(name, &v->template get<uint64_t>());
				case ModelValue::Type::SignedInteger8:
					return m_handler.field(name, &v->template get<int8_t>());
				case ModelValue::Type::SignedInteger16:
					return m_handler.field(name, &v->template get<int16_t>());
				case ModelValue::Type::SignedInteger32:
					return m_handler.field(name, &v->template get<int32_t>());
				case ModelValue::Type::SignedInteger64:
					return m_handler.field(name, &v->template get<int64_t>());
				case ModelValue::Type::String:
					return m_handler.field(name, &v->template get<String>());
				case ModelValue::Type::Object:
					return m_handler.field(name, &v->template get<ModelObject>());
				case ModelValue::Type::Union:
				{
					auto &u = v->template get<ModelUnion>();
					if constexpr(opType_v == OpType::Read) {
						u.setActiveField(whichFieldPresent(m_handler, name, u));
						return m_handler.field(name, UnionView<ModelUnion, true>(&u, u.unionIdx()));
					} else {
						return m_handler.field(name, UnionView<ModelUnion const, true>(&u, u.unionIdx()));
					}
				}
				case ModelValue::Type::Vector:
					return m_handler.field(name, &v->template get<ModelValueVector>());
				case ModelValue::Type::InlineArray:
					return m_handler.field(name, &v->template get<ModelValueArray>());
			}
			oxErrf("invalid type: {}: {}\n", name, static_cast<int>(v->type()));
			ox::panic("invalid type");
			return ox::Error(1, "invalid type");
		}

		// array handler, with callback to allow handling individual elements
		template<typename T, typename Callback>
		constexpr Error field(CString name, Callback cb) noexcept {
			return m_handler.template field<T, Callback>(name, cb);
		}

		template<typename T>
		constexpr Error field(CString name, const T *v) noexcept {
			if constexpr(ox::is_same_v<T, ModelValue>) {
				return fieldModelValue(name, v);
			} else {
				return m_handler.field(name, v);
			}
		}

		template<typename T>
		constexpr Error field(CString name, T *v) noexcept {
			if constexpr(ox::is_same_v<T, ModelValue>) {
				return fieldModelValue(name, v);
			} else {
				return m_handler.field(name, v);
			}
		}

		template<typename U, bool force = false>
		constexpr Error field(CString name, UnionView<U, force> val) noexcept {
			return m_handler.field(name, val);
		}

		constexpr Error field(CString name, auto *val, size_t len) noexcept {
			return m_handler.field(name, val, len);
		}

		/**
		 * Reads an array length from the current location in the buffer.
		 * @param name
		 * @param pass indicates that the parsing should iterate past the array length
		 */
		 [[nodiscard]]
		constexpr auto arrayLength(CString name, bool pass = true) noexcept {
			return m_handler.arrayLength(name, pass);
		}

		/**
		 * Reads an string length from the current location in the buffer.
		 */
		[[nodiscard]]
		constexpr auto stringLength(CString name) noexcept {
			return m_handler.stringLength(name);
		}

		[[nodiscard]]
		static constexpr auto opType() noexcept {
			return Handler::opType();
		}

		[[nodiscard]]
		constexpr auto handler() noexcept {
			return m_handler;
		}

	private:
		template<typename H>
		static constexpr int whichFieldPresent(H &h, CString name, ModelUnion const &u) noexcept
			requires(H::opType() == OpType::Read) {
			return h.whichFieldPresent(name, u);
		}

		template<typename H>
		static constexpr int whichFieldPresent(H&, CString, ModelUnion const&) noexcept
			requires(H::opType() != OpType::Read) {
			return 0;
		}

};

template<typename Handler, OpType opType_v = Handler::opType()>
class ModelHandlerBase {
	private:
		ModelHandlerInterface<Handler, opType_v> m_interface{*static_cast<Handler*>(this)};
	public:
		[[nodiscard]]
		constexpr auto interface() noexcept {
			return &m_interface;
		}
		[[nodiscard]]
		static constexpr OpType opType() noexcept {
			return opType_v;
		}
};


constexpr ox::Error resizeVector(auto &vec, size_t sz) {
	if constexpr(ox::is_same_v<decltype(vec.resize(0)), ox::Error>) {
		return vec.resize(sz);
	} else {
		vec.resize(sz);
		return {};
	}
}

}
