/*
 * 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/array.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/error.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include <ox/std/typetraits.hpp>
#include <ox/std/units.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/modelvalue.hpp>

#include "platspecs.hpp"

namespace ox {

template<typename PlatSpec>
class Preloader;

template<typename PlatSpec>
class Preloader: public ModelHandlerBase<Preloader<PlatSpec>, OpType::Reflect> {
	private:
		using PtrType = typename PlatSpec::PtrType;
		static constexpr auto PtrSize = sizeof(PtrType);
		class UnionIdxTracker {
			private:
				int m_unionIdx = -1;
				int m_it = 0;
			public:
				constexpr UnionIdxTracker() noexcept = default;
				constexpr explicit UnionIdxTracker(int idx) noexcept: m_unionIdx(idx) {}
				constexpr auto checkAndIterate() noexcept {
					return m_unionIdx == -1 || m_it++ == m_unionIdx;
				}
		};
		ox::Buffer m_buff;
		ox::BufferWriter m_writer;
		// list of all the places where ptrs were written to buffer
		struct PtrPair {
			std::size_t loc = 0;
			typename PlatSpec::PtrType value = 0;
			constexpr PtrPair() noexcept = default;
			constexpr PtrPair(std::size_t pLoc, typename PlatSpec::PtrType pValue) noexcept:
				loc(pLoc), value(pValue) {}
		};
		ox::Vector<PtrPair> m_ptrs;
		ox::Vector<UnionIdxTracker, 8> m_unionIdx = {{}};
		class AllocStackItem {
			public:
				PtrType restore = 0;
				ox::ios_base::seekdir seekdir = ox::ios_base::end;
				constexpr AllocStackItem(PtrType pRestore, ox::ios_base::seekdir pSeekdir = ox::ios_base::end) noexcept:
						restore(pRestore), seekdir(pSeekdir) {}
		};
		ox::Vector<AllocStackItem, 8> m_allocStack;
		ox::Vector<size_t> m_allocStart{};

		constexpr Preloader() noexcept: m_writer(&m_buff) {}

	public:
		Preloader(const Preloader &src) = delete;
		Preloader(Preloader &&src) = delete;
		const Preloader &operator=(const Preloader &src) = delete;
		const Preloader &operator=(Preloader &&src) = delete;

		constexpr static ox::Result<ox::UPtr<Preloader>> make(ox::ios_base::seekdir anchor = ox::ios_base::cur,
		                                                           std::size_t sz = 0) noexcept;

		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 U, bool force>
		constexpr ox::Error field(StringViewCR, ox::UnionView<U, force> val) noexcept;

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

		template<std::size_t SmallStringSize>
		constexpr ox::Error field(StringViewCR, const ox::BasicString<SmallStringSize> *val) noexcept;

		template<typename T, std::size_t sz>
		constexpr ox::Error field(StringViewCR, const ox::Array<T, sz> *valArray) noexcept;

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

		constexpr ox::Result<std::size_t> startAlloc(size_t sz, size_t align) noexcept;

		constexpr ox::Result<std::size_t> startAlloc(size_t sz, size_t align, std::size_t restore) noexcept;

		constexpr ox::Error endAlloc() noexcept;

		constexpr ox::Error offsetPtrs(std::size_t offset) noexcept;

		[[nodiscard]]
		constexpr auto &buff() const noexcept {
			return m_buff;
		}

		template<typename T>
		constexpr ox::Error pad(const T*) noexcept;

	private:
		constexpr ox::Error fieldVector(StringViewCR name, const ox::ModelValueVector *val) noexcept;

		template<typename T, std::size_t SmallVectorSize, typename Allocator>
		constexpr ox::Error fieldVector(StringViewCR, const ox::Vector<T, SmallVectorSize, Allocator> *val) noexcept;

		constexpr ox::Error fieldVector(StringViewCR, const auto *val, ox::VectorMemMap<PlatSpec> vecVal) noexcept;

		constexpr ox::Error fieldArray(StringViewCR name, ox::ModelValueArray const*val) noexcept;

		constexpr bool unionCheckAndIt() noexcept;

		[[nodiscard]]
		constexpr size_t calcPadding(size_t align) const noexcept;
};

template<typename PlatSpec>
constexpr ox::Result<ox::UPtr<Preloader<PlatSpec>>>
Preloader<PlatSpec>::make(ox::ios_base::seekdir anchor, std::size_t sz) noexcept {
	auto p = ox::UPtr<Preloader>(new Preloader);
	if (const auto err = p->m_writer.seekp(0, anchor)) {
		return {std::move(p), err};
	}
	if (const auto err = p->m_writer.write(nullptr, sz)) {
		return {std::move(p), err};
	}
	if (const auto err = p->m_writer.seekp(p->m_writer.tellp() - sz)) {
		return {std::move(p), err};
	}
	return p;
}

template<typename PlatSpec>
template<typename U, bool force>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR, const ox::UnionView<U, force> val) noexcept {
	if (!unionCheckAndIt()) {
		return {};
	}
	OX_RETURN_ERROR(pad(val.get()));
	m_unionIdx.emplace_back(val.idx());
	const auto err = preload<PlatSpec, U>(this, val.get());
	m_unionIdx.pop_back();
	return err;
}

template<typename PlatSpec>
template<typename T>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR name, const T *val) noexcept {
	if (!unionCheckAndIt()) {
		return {};
	}
	OX_RETURN_ERROR(pad(val));
	if constexpr(ox::is_integral_v<T>) {
		return ox::serialize(m_writer, PlatSpec::correctEndianness(*val));
	} else if constexpr(ox::is_pointer_v<T>) {
		const PtrType a = startAlloc(sizeOf<PlatSpec>(val), alignOf<PlatSpec>(*val), m_writer.tellp()) + PlatSpec::RomStart;
		OX_RETURN_ERROR(field(name, *val));
		OX_RETURN_ERROR(endAlloc());
		return ox::serialize(m_writer, PlatSpec::correctEndianness(a));
	} else if constexpr(ox::isVector_v<T>) {
		return fieldVector(name, val);
	} else if constexpr(ox::is_same_v<T, ox::ModelValueVector>) {
		val->types();
		return fieldVector(name, val);
	} else if constexpr(ox::is_same_v<T, ox::ModelValueArray>) {
		return fieldArray(name, val);
	} else {
		m_unionIdx.emplace_back(-1);
		const auto out = preload<PlatSpec, T>(this, val);
		m_unionIdx.pop_back();
		return out;
	}
}

template<typename PlatSpec>
template<std::size_t SmallStringSize>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR, const ox::BasicString<SmallStringSize> *val) noexcept {
	if (!unionCheckAndIt()) {
		return {};
	}
	using VecMap = ox::VectorMemMap<PlatSpec>;
	const auto sz = val->bytes();
	VecMap vecVal{
		.smallVecSize = SmallStringSize,
		.size = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(sz)),
		.cap = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(sz)),
	};
	OX_RETURN_ERROR(pad(&vecVal));
	const auto restore = m_writer.tellp();
	std::size_t a = 0;
	if (sz && sz >= SmallStringSize) {
		OX_RETURN_ERROR(ox::allocate(m_writer, sz).moveTo(a));
	} else {
		a = restore;
	}
	vecVal.items = PlatSpec::correctEndianness(static_cast<PtrType>(a) + PlatSpec::RomStart);
	OX_RETURN_ERROR(m_writer.seekp(a));
	OX_RETURN_ERROR(m_writer.write(val->data(), sz));
	OX_RETURN_ERROR(m_writer.seekp(restore));
	OX_RETURN_ERROR(serialize(m_writer, vecVal));
	m_ptrs.emplace_back(restore + offsetof(VecMap, items), vecVal.items);
	return {};
}

template<typename PlatSpec>
template<typename T, std::size_t sz>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR name, const ox::Array<T, sz> *val) noexcept {
	if (!unionCheckAndIt()) {
		return {};
	}
	OX_RETURN_ERROR(pad(&(*val)[0]));
	// serialize the Array elements
	if constexpr(sz) {
		m_unionIdx.emplace_back(-1);
		for (std::size_t i = 0; i < val->size(); ++i) {
			OX_RETURN_ERROR(this->interface()->field(name, &(*val)[i]));
		}
		m_unionIdx.pop_back();
	}
	return {};
}

template<typename PlatSpec>
template<typename T>
constexpr ox::Error Preloader<PlatSpec>::field(StringViewCR, const T **val, std::size_t cnt) noexcept {
	if (!unionCheckAndIt()) {
		return {};
	}
	if (cnt) {
		OX_RETURN_ERROR(pad(*val));
		// serialize the array
		m_unionIdx.emplace_back(-1);
		for (std::size_t i = 0; i < cnt; ++i) {
			OX_RETURN_ERROR(this->interface()->field(nullptr, &val[i]));
		}
		m_unionIdx.pop_back();
	}
	return {};
}

template<typename PlatSpec>
constexpr ox::Result<std::size_t> Preloader<PlatSpec>::startAlloc(size_t sz, size_t align) noexcept {
	m_allocStack.emplace_back(static_cast<typename PlatSpec::PtrType>(m_writer.tellp()));
	OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
	auto const padding = calcPadding(align);
	OX_REQUIRE_M(a, ox::allocate(m_writer, sz + padding));
	a += padding;
	OX_RETURN_ERROR(m_writer.seekp(a));
	m_allocStart.push_back(a);
	return a;
}

template<typename PlatSpec>
constexpr ox::Result<std::size_t> Preloader<PlatSpec>::startAlloc(
		std::size_t sz, size_t align, std::size_t restore) noexcept {
	m_allocStack.emplace_back(restore, ox::ios_base::beg);
	OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
	auto const padding = calcPadding(align);
	OX_REQUIRE_M(a, ox::allocate(m_writer, sz + padding));
	a += padding;
	OX_RETURN_ERROR(m_writer.seekp(a));
	m_allocStart.push_back(a);
	return a;
}

template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::endAlloc() noexcept {
	if (m_allocStack.empty()) {
		return m_writer.seekp(0, ox::ios_base::end);
	}
	const auto &si = *m_allocStack.back().unwrap();
	OX_RETURN_ERROR(m_writer.seekp(static_cast<ox::ssize_t>(si.restore), si.seekdir));
	m_allocStack.pop_back();
	m_allocStart.pop_back();
	return {};
}

template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::offsetPtrs(std::size_t offset) noexcept {
	for (const auto &p : m_ptrs) {
		OX_RETURN_ERROR(m_writer.seekp(p.loc));
		const auto val = PlatSpec::template correctEndianness<typename PlatSpec::PtrType>(
				static_cast<typename PlatSpec::PtrType>(p.value + offset));
		OX_RETURN_ERROR(ox::serialize(m_writer, val));
	}
	OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
	return {};
}

template<typename PlatSpec>
template<typename T>
constexpr ox::Error Preloader<PlatSpec>::pad(const T *v) noexcept {
	const auto a = alignOf<PlatSpec>(*v);
	const auto excess = m_writer.tellp() % a;
	if (excess) {
		return m_writer.write(nullptr, a - excess);
	} else {
		return {};
	}
}

template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldVector(
		StringViewCR name, const ox::ModelValueVector *val) noexcept {
	// serialize the Vector
	ox::VectorMemMap<PlatSpec> vecVal{
			.size = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(val->size())),
			.cap = PlatSpec::correctEndianness(static_cast<typename PlatSpec::size_t>(val->size())),
	};
	return fieldVector(name, val, vecVal);
}

template<typename PlatSpec>
template<typename T, std::size_t SmallVectorSize, typename Allocator>
constexpr ox::Error Preloader<PlatSpec>::fieldVector(
		StringViewCR name, const ox::Vector<T, SmallVectorSize, Allocator> *val) noexcept {
	// serialize the Vector
	ox::VectorMemMap<PlatSpec> vecVal{
		.smallVecSize = SmallVectorSize * sizeOf<PlatSpec>(static_cast<T*>(nullptr)),
		.size = PlatSpec::correctEndianness(
				static_cast<typename PlatSpec::size_t>(val->size())),
		.cap = PlatSpec::correctEndianness(
				static_cast<typename PlatSpec::size_t>(val->size())),
	};
	return fieldVector(name, val, vecVal);
}

template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldVector(
		StringViewCR, const auto *val, ox::VectorMemMap<PlatSpec> vecVal) noexcept {
	OX_RETURN_ERROR(pad(&vecVal));
	const auto vecValPt = m_writer.tellp();
	// serialize the Vector elements
	if (val->size()) {
		const auto sz = sizeOf<PlatSpec>(&(*val)[0]) * val->size();
		const auto align = alignOf<PlatSpec>((*val)[0]);
		OX_RETURN_ERROR(m_writer.seekp(0, ox::ios_base::end));
		auto const padding = calcPadding(align);
		OX_REQUIRE_M(p, ox::allocate(m_writer, sz + padding));
		p += padding;
		OX_RETURN_ERROR(m_writer.seekp(p));
		m_unionIdx.emplace_back(-1);
		for (std::size_t i = 0; i < val->size(); ++i) {
			OX_RETURN_ERROR(this->interface()->field(nullptr, &val->operator[](i)));
		}
		m_unionIdx.pop_back();
		vecVal.items = PlatSpec::correctEndianness(
				static_cast<typename PlatSpec::size_t>(p + PlatSpec::RomStart));
		OX_RETURN_ERROR(m_writer.seekp(vecValPt));
	} else {
		vecVal.items = 0;
	}
	// serialize the Vector
	OX_RETURN_ERROR(serialize(m_writer, vecVal));
	m_ptrs.emplace_back(m_writer.tellp() - PtrSize, vecVal.items);
	return {};
}

template<typename PlatSpec>
constexpr ox::Error Preloader<PlatSpec>::fieldArray(StringViewCR, ox::ModelValueArray const*val) noexcept {
	OX_RETURN_ERROR(pad(&(*val)[0]));
	for (auto const&v : *val) {
		OX_RETURN_ERROR(this->interface()->field({}, &v));
	}
	return {};
}

template<typename PlatSpec>
constexpr bool Preloader<PlatSpec>::unionCheckAndIt() noexcept {
	auto &u = *m_unionIdx.back().unwrap();
	return u.checkAndIterate();
}

template<typename PlatSpec>
constexpr size_t Preloader<PlatSpec>::calcPadding(size_t align) const noexcept {
	auto const excess = m_writer.tellp() % align;
	return (align * (excess != 0)) - excess;
}

template<typename PlatSpec, typename T>
constexpr ox::Error preload(Preloader<PlatSpec> *pl, ox::CommonPtrWith<T> auto *obj) noexcept {
	OX_RETURN_ERROR(model(pl->interface(), obj));
	return pl->pad(obj);
}

extern template class Preloader<NativePlatSpec>;

}
