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

#include "modelvalue.hpp"

namespace ox {

template<typename Handler>
class ModelHandlerInterface {
	private:
		Handler *m_handler = nullptr;

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

		template<typename T = std::nullptr_t>
		constexpr void setTypeInfo(const char *name = T::TypeName, int version = T::TypeVersion,
                                   int fields = ModelFieldCount_v<T>) noexcept {
			m_handler->setTypeInfo(name, version, 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 {
			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 {
			return m_handler->fieldCString(name, val);
		}

		constexpr Error fieldCString(const char *name, const char **val) noexcept {
			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 {
			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 field(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(ox_strcmp(Handler::opType(), OpType::Read) == 0) {
						u.setActiveField(m_handler->whichFieldPresent(name, u));
					}
					return m_handler->field(name, UnionView<ModelUnion, true>(&u, u.unionIdx()));
				}
				case ModelValue::Type::Vector:
					return m_handler->field(name, &v->template get<ModelValueVector>());
			}
			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);
		}

		constexpr Error field(const char *name, const auto *v) noexcept {
			return m_handler->field(name, v);
		}

		constexpr Error field(const char *name, auto *v) noexcept {
			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;
		}

};

}
