/*
 * Copyright 2015 - 2023 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 = nullptr;

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

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

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

		template<std::size_t len>
		constexpr Error fieldCString(const char *name, char val[len]) noexcept {
			return m_handler->fieldCString(name, &val[0], len);
		}

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

		constexpr Error fieldCString(const char *name, char **val) noexcept {
			return m_handler->fieldCString(name, val);
		}

		constexpr Error fieldCString(const char *name, const char *const*val) noexcept requires(opType_v != OpType::Read) {
			return m_handler->fieldCString(name, val);
		}

		constexpr Error fieldCString(const char *name, const char **val) noexcept requires(opType_v != OpType::Read) {
			return m_handler->fieldCString(name, val);
		}

		constexpr Error fieldCString(const char *name, char **val, std::size_t buffLen) noexcept {
			return m_handler->fieldCString(name, val, buffLen);
		}

		constexpr Error fieldCString(const char *name, const char **val, std::size_t buffLen) noexcept requires(opType_v != OpType::Read) {
			return m_handler->fieldCString(name, val, buffLen);
		}

		constexpr Error fieldCString(const char *name, char *val, std::size_t buffLen) noexcept {
			return m_handler->fieldCString(name, val, buffLen);
		}

		constexpr Error fieldModelValue(const char *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(m_handler->whichFieldPresent(name, u));
						return m_handler->field(name, UnionView<ModelUnion, true>(&u, u.unionIdx()));
					} else {
						return m_handler->field(name, UnionView<const ModelUnion, true>(&u, u.unionIdx()));
					}
				}
				case ModelValue::Type::Vector:
					return m_handler->field(name, &v->template get<ModelValueVector>());
			}
			oxErrf("invalid type: {}: {}\n", name, static_cast<int>(v->type()));
			oxPanic(OxError(1), "invalid type");
			return OxError(1, "invalid type");
		}

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

		template<typename T>
		constexpr Error field(const char *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(const char *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(const char *name, UnionView<U, force> val) noexcept {
			return m_handler->field(name, val);
		}

		constexpr Error field(const char *name, auto *val, std::size_t len) noexcept {
			return m_handler->field(name, val, len);
		}

		/**
		 * Reads an array length from the current location in the buffer.
		 * @param pass indicates that the parsing should iterate past the array length
		 */
		 [[nodiscard]]
		constexpr auto arrayLength(const char *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(const char *name) noexcept {
			return m_handler->stringLength(name);
		}

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

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

};

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

};

}
