/*
 * 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/fieldcounter.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/std/bit.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/optional.hpp>
#include <ox/std/string.hpp>
#include <ox/std/types.hpp>
#include <ox/std/units.hpp>

#include "intops.hpp"
#include "err.hpp"
#include "presenceindicator.hpp"
#include "types.hpp"

namespace ox {

template<Writer_c Writer>
class MetalClawWriter: public ModelHandlerBase<MetalClawWriter<Writer>, OpType::Write> {

	private:
		Vector<char, 16> m_presenceMapBuff{};
		FieldBitmapWriter m_fieldPresence{m_presenceMapBuff};
		int m_field{};
		Optional<int> m_unionIdx;
		size_t m_writerBeginP{};
		Writer &m_writer;

	public:
		constexpr explicit MetalClawWriter(Writer &writer, Optional<int> const &unionIdx = {}) noexcept;

		constexpr ~MetalClawWriter() noexcept = default;

		constexpr Error field(CString, Integer_c auto const *val) noexcept;

		constexpr Error field(CString, bool const *val) noexcept;

		template<typename T>
		constexpr Error field(CString, T const *val, size_t len) noexcept;

		template<typename T>
		constexpr Error field(CString name, HashMap<String, T> const *val) noexcept;

		template<size_t SmallStringSize>
		constexpr Error field(CString, BasicString<SmallStringSize> const *val) noexcept;

		template<size_t L>
		constexpr Error field(CString, IString<L> const *val) noexcept;

		constexpr Error fieldCString(CString name, CString const*val, size_t buffLen) noexcept;

		constexpr Error fieldCString(CString name, CString *val) noexcept;

		constexpr Error fieldCString(CString name, CString const*val) noexcept;

		constexpr Error fieldCString(CString name, CString val, size_t strLen) noexcept;

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

		template<typename U, bool force = false>
		constexpr Error field(CString, UnionView<U, force> val) noexcept;

		template<typename T = std::nullptr_t>
		constexpr Error setTypeInfo(
				CString name = T::TypeName,
				int version = T::TypeVersion,
		        Vector<String> const& = {},
				size_t fields = ModelFieldCount_v<T>) noexcept;

		/**
		 * stringLength is not implemented in MetalClawWriter
		 */
		[[nodiscard]]
		constexpr auto stringLength(CString) noexcept {
			return 0;
		}

		/**
		 * stringLength is not implemented in MetalClawWriter
		 */
		[[nodiscard]]
		constexpr auto arrayLength(CString, bool = true) noexcept {
			return 0;
		}

		constexpr Error finalize() noexcept;

	private:
		constexpr Error appendInteger(Integer_c auto val) noexcept {
			bool fieldSet = false;
			if (val && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
				auto mi = mc::encodeInteger(val);
				OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(mi.data.data()), mi.length));
				fieldSet = true;
			}
			OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
			++m_field;
			return {};
		}

};

extern template class ModelHandlerInterface<MetalClawWriter<BufferWriter>>;
extern template class ModelHandlerInterface<MetalClawWriter<CharBuffWriter>>;

template<Writer_c Writer>
constexpr MetalClawWriter<Writer>::MetalClawWriter(Writer &writer, Optional<int> const &unionIdx) noexcept:
	m_unionIdx(unionIdx),
	m_writerBeginP(writer.tellp()),
	m_writer(writer) {
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, Integer_c auto const *val) noexcept {
	return appendInteger(*val);
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::field(CString, bool const *val) noexcept {
	if (!m_unionIdx.has_value() || *m_unionIdx == m_field) {
		OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), *val));
	}
	++m_field;
	return {};
}

template<Writer_c Writer>
template<size_t SmallStringSize>
constexpr Error MetalClawWriter<Writer>::field(CString, BasicString<SmallStringSize> const *val) noexcept {
	bool fieldSet = false;
	if (val->size() && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
		// write the length
		auto const strLen = mc::encodeInteger(val->size());
		OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(strLen.data.data()), strLen.length));
		// write the string
		OX_RETURN_ERROR(m_writer.write(val->c_str(), static_cast<size_t>(val->size())));
		fieldSet = true;
	}
	OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

template<Writer_c Writer>
template<size_t L>
constexpr Error MetalClawWriter<Writer>::field(CString name, IString<L> const *val) noexcept {
	return fieldCString(name, val->data(), val->size());
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString, CString const *val, size_t) noexcept {
	bool fieldSet = false;
	if (!m_unionIdx.has_value() || *m_unionIdx == m_field) {
		OX_ALLOW_UNSAFE_BUFFERS_BEGIN
		// this strlen is tolerated because sometimes 0 gets passed to
		// the size param, which is a lie
		// this code should be cleaned up at some point...
		auto const strLen = *val ? ox::strlen(*val) : 0;
		OX_ALLOW_UNSAFE_BUFFERS_END
		// write the length
		auto const strLenBuff = mc::encodeInteger(strLen);
		OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(strLenBuff.data.data()), strLenBuff.length));
		// write the string
		OX_RETURN_ERROR(m_writer.write(*val, static_cast<size_t>(strLen)));
		fieldSet = true;
	}
	OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString const name, CString *val) noexcept {
	return fieldCString(name, val, {});
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString const name, CString const *val) noexcept {
	return fieldCString(name, val, {});
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::fieldCString(CString, CString const val, size_t const strLen) noexcept {
	bool fieldSet = false;
	if (strLen && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
		// write the length
		auto const strLenBuff = mc::encodeInteger(strLen);
		OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(strLenBuff.data.data()), strLenBuff.length));
		// write the string
		OX_RETURN_ERROR(m_writer.write(val, static_cast<size_t>(strLen)));
		fieldSet = true;
	}
	OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(CString, T const *val) noexcept {
	if constexpr(isVector_v<T> || isArray_v<T>) {
		return field(nullptr, val->data(), val->size());
	} else {
		bool fieldSet = false;
		if (val && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
			auto const writeIdx = m_writer.tellp();
			MetalClawWriter writer(m_writer);
			OX_RETURN_ERROR(model(writer.interface(), val));
			OX_RETURN_ERROR(writer.finalize());
			fieldSet = writeIdx != m_writer.tellp();
		}
		OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
		++m_field;
		return {};
	}
}

template<Writer_c Writer>
template<typename U, bool force>
constexpr Error MetalClawWriter<Writer>::field(CString, UnionView<U, force> val) noexcept {
	bool fieldSet = false;
	if (val.get() && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
		auto const writeIdx = m_writer.tellp();
		MetalClawWriter writer(m_writer, Optional<int>(in_place, val.idx()));
		OX_RETURN_ERROR(model(writer.interface(), val.get()));
		OX_RETURN_ERROR(writer.finalize());
		fieldSet = writeIdx != m_writer.tellp();
	}
	OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(CString, T const *val, size_t const len) noexcept {
	bool fieldSet = false;
	if (len && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
		// write the length
		auto const arrLen = mc::encodeInteger(len);
		OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(arrLen.data.data()), arrLen.length));
		auto const writeIdx = m_writer.tellp();
		MetalClawWriter writer(m_writer);
		OX_RETURN_ERROR(writer.interface()->template setTypeInfo<T>("List", 0, {}, static_cast<size_t>(len)));
		// write the array
		for (size_t i{}; i < len; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
			OX_RETURN_ERROR(writer.interface()->field("", &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
		}
		OX_RETURN_ERROR(writer.finalize());
		fieldSet = writeIdx != m_writer.tellp();
	}
	OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(CString, HashMap<String, T> const *val) noexcept {
	auto const &keys = val->keys();
	auto const len = keys.size();
	bool fieldSet = false;
	if (len && (!m_unionIdx.has_value() || *m_unionIdx == m_field)) {
		// write the length
		auto const arrLen = mc::encodeInteger(len);
		OX_RETURN_ERROR(m_writer.write(reinterpret_cast<CString>(arrLen.data.data()), arrLen.length));
		// write map
		MetalClawWriter writer(m_writer);
		// double len for both key and value
		OX_RETURN_ERROR(writer.interface()->setTypeInfo("Map", 0, {}, len * 2));
		// this loop body needs to be in a lambda because of the potential alloca call
		constexpr auto loopBody = [](auto &handler, auto const &key, auto const &val) -> Error {
			auto const keyLen = key.size();
			auto wkey = ox_malloca(keyLen + 1, char, 0);
			memcpy(wkey.get(), key.c_str(), keyLen + 1);
			OX_RETURN_ERROR(handler.fieldCString("", wkey.get(), keyLen));
			OX_REQUIRE_M(value, val.at(key));
			return handler.field("", value);
		};
		// write the array
		for (size_t i{}; i < len; ++i) {
			auto const &key = keys[i];
			OX_RETURN_ERROR(loopBody(*writer.interface(), key, *val));
		}
		OX_RETURN_ERROR(writer.finalize());
		fieldSet = true;
	}
	OX_RETURN_ERROR(m_fieldPresence.set(static_cast<size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::setTypeInfo(
		CString,
		int,
		Vector<String> const&,
		size_t const fields) noexcept {
	auto const fieldPresenceLen = (fields - 1) / 8 + 1;
	OX_RETURN_ERROR(m_writer.write(nullptr, fieldPresenceLen));
	m_presenceMapBuff.resize(fieldPresenceLen);
	m_fieldPresence.setBuffer(m_presenceMapBuff);
	return m_fieldPresence.setFields(static_cast<int>(fields));
}

template<Writer_c Writer>
constexpr Error MetalClawWriter<Writer>::finalize() noexcept {
	auto const end = m_writer.tellp();
	OX_RETURN_ERROR(m_writer.seekp(m_writerBeginP));
	OX_RETURN_ERROR(m_writer.write(
		m_presenceMapBuff.data(),
		m_presenceMapBuff.size()));
	OX_RETURN_ERROR(m_writer.seekp(end));
	return {};
}

Result<Buffer> writeMC(Writer_c auto &writer, auto const &val) noexcept {
	MetalClawWriter mcWriter(writer);
	OX_RETURN_ERROR(model(mcWriter.interface(), &val));
	OX_RETURN_ERROR(mcWriter.finalize());
	return {};
}

Result<Buffer> writeMC(auto const &val, size_t const buffReserveSz = 2 * units::KB) noexcept {
	Buffer buff(buffReserveSz);
	BufferWriter bw(&buff, 0);
	OX_RETURN_ERROR(writeMC(bw, val));
	buff.resize(bw.tellp());
	return buff;
}

Error writeMC(char *buff, size_t const buffLen, auto const &val, size_t *sizeOut = nullptr) noexcept {
	CharBuffWriter bw{{buff, buffLen}};
	OX_RETURN_ERROR(writeMC(bw, val));
	if (sizeOut) {
		*sizeOut = bw.tellp();
	}
	return {};
}

}
