/*
 * 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 "array.hpp"
#include "buffer.hpp"
#include "types.hpp"
#include "typetraits.hpp"
#include "writer.hpp"

namespace ox {

template<typename PlatSpec>
struct VectorMemMap {
	const std::size_t smallVecSize = 0; // not a map value
	uint8_t allocator = 0;
	typename PlatSpec::size_t size = 0;
	typename PlatSpec::size_t cap = 0;
	typename PlatSpec::PtrType items = 0;
};

template<typename PlatSpec>
[[nodiscard]]
constexpr auto sizeOf(const VectorMemMap<PlatSpec> *t) noexcept {
	constexpr auto padding = [](std::size_t size, std::size_t al) {
		return size % al;
	};
	std::size_t size = 0;
	if (t->smallVecSize) {
		size += t->smallVecSize;
		size += padding(size, PlatSpec::alignOf(t->allocator));
	}
	size += sizeof(t->allocator);
	size += padding(size, PlatSpec::alignOf(t->size));
	size += sizeof(t->size);
	size += padding(size, PlatSpec::alignOf(t->cap));
	size += sizeof(t->cap);
	size += padding(size, PlatSpec::alignOf(t->items));
	size += sizeof(t->items);
	return size;
}

template<typename PlatSpec, std::size_t SmallVecSize = 0>
[[nodiscard]]
constexpr auto alignOf(const VectorMemMap<PlatSpec>&) noexcept {
	const typename PlatSpec::size_t i = 0;
	return PlatSpec::alignOf(i);
}

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

template<typename PlatSpec>
constexpr ox::Error serialize(Writer_c auto *buff, const VectorMemMap<PlatSpec> &vm) noexcept {
	oxReturnError(buff->write(nullptr, vm.smallVecSize));
	oxReturnError(serialize(buff, PlatSpec::correctEndianness(vm.allocator)));
	oxReturnError(pad<PlatSpec>(buff, &vm.size));
	oxReturnError(serialize(buff, PlatSpec::correctEndianness(vm.size)));
	oxReturnError(serialize(buff, PlatSpec::correctEndianness(vm.cap)));
	oxReturnError(serialize(buff, PlatSpec::correctEndianness(vm.items)));
	return {};
}

template<typename T>
constexpr ox::Error serialize(Writer_c auto *buff, T val) noexcept requires(is_integer_v<T>) {
	ox::Array<char, sizeof(T)> tmp;
	for (auto i = 0u; i < sizeof(T); ++i) {
		tmp[i] = static_cast<char>((val >> i * 8) & 255);
	}
	return buff->write(tmp.data(), tmp.size());
};

template<typename T>
constexpr ox::Result<ox::Array<char, sizeof(T)>> serialize(const T &in) noexcept {
	ox::Array<char, sizeof(T)> out = {};
	CharBuffWriter w(out);
	oxReturnError(serialize(&w, in));
	return out;
};

}