/*
 * 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/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>

#include "unionsizecatcher.hpp"

namespace ox {

template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t alignOf(const T &t) noexcept;

template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t sizeOf(const T *t) noexcept;

template<typename PlatSpec>
class SizeCatcher: public ModelHandlerBase<SizeCatcher<PlatSpec>, OpType::Reflect> {
	private:
		std::size_t m_size = 0;

	public:
		constexpr explicit SizeCatcher() noexcept = default;

		template<typename T>
		constexpr ox::Error setTypeInfo(
				const char* = T::TypeName,
				int = T::TypeVersion) noexcept {
			return {};
		}

		template<typename T>
		constexpr ox::Error setTypeInfo(
				const char*,
				int,
				const Vector<String>&,
				std::size_t) noexcept {
			return {};
		}

		template<typename T, bool force>
		constexpr ox::Error field(const char*, UnionView<T, force>) noexcept;

		template<typename T>
		constexpr ox::Error field(const char*, const T *val) noexcept;

		template<typename T>
		constexpr ox::Error field(const char*, const T **val, std::size_t cnt) noexcept;

		[[nodiscard]]
		constexpr auto size() const noexcept {
			return m_size;
		}

	private:
		constexpr void pad(const auto *val) noexcept;
};

template<typename PlatSpec>
template<typename T, bool force>
constexpr ox::Error SizeCatcher<PlatSpec>::field(const char*, const UnionView<T, force> val) noexcept {
	pad(val.get());
	UnionSizeCatcher<PlatSpec> sc;
	OX_RETURN_ERROR(model(sc.interface(), val.get()));
	m_size += sc.size();
	return {};
}

template<typename PlatSpec>
template<typename T>
constexpr ox::Error SizeCatcher<PlatSpec>::field(const char*, const T *val) noexcept {
	pad(val);
	m_size += sizeOf<PlatSpec>(val);
	return {};
}

template<typename PlatSpec>
template<typename T>
constexpr ox::Error SizeCatcher<PlatSpec>::field(const char*, const T **val, std::size_t cnt) noexcept {
	for (std::size_t i = 0; i < cnt; ++i) {
		OX_RETURN_ERROR(field("", &val[i]));
	}
	return {};
}

template<typename PlatSpec>
constexpr void SizeCatcher<PlatSpec>::pad(const auto *val) noexcept {
	const auto a = alignOf<PlatSpec>(*val);
	const auto excess = m_size % a;
	if (excess) {
		m_size += a - excess;
	}
}

template<typename PlatSpec, typename T>
[[nodiscard]]
constexpr std::size_t sizeOf(const T *t) noexcept {
	if constexpr(ox::is_integral_v<T>) {
		return sizeof(T);
	} else if constexpr(ox::is_pointer_v<T>) {
		return sizeof(typename PlatSpec::PtrType);
	} else {
		SizeCatcher<PlatSpec> sc;
		const auto err = model(sc.interface(), t);
		oxAssert(err, "Could not get size of type");
		return sc.size();
	}
}

}
