/*
 * 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/typenamecatcher.hpp>
#include <ox/model/types.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/byteswap.hpp>
#include <ox/std/optional.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>
#include <ox/std/vector.hpp>

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

namespace ox {

template<Reader_c Reader>
class MetalClawReaderTemplate: public ModelHandlerBase<MetalClawReaderTemplate<Reader>, ox::OpType::Read> {

	private:
		FieldBitmapReader<Reader> m_fieldPresence;
		size_t m_fields{};
		size_t m_field{};
		Optional<int> const m_unionIdx{};
		Reader &m_reader;

	public:
		explicit constexpr MetalClawReaderTemplate(
				Reader &reader,
				Optional<int> const &unionIdx = {}) noexcept;

		constexpr ~MetalClawReaderTemplate() noexcept;

		constexpr Error field(CString, int8_t *val) noexcept;
		constexpr Error field(CString, int16_t *val) noexcept;
		constexpr Error field(CString, int32_t *val) noexcept;
		constexpr Error field(CString, int64_t *val) noexcept;

		constexpr Error field(CString, uint8_t *val) noexcept;
		constexpr Error field(CString, uint16_t *val) noexcept;
		constexpr Error field(CString, uint32_t *val) noexcept;
		constexpr Error field(CString, uint64_t *val) noexcept;

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

		// array handler
		constexpr Error field(CString, auto *val, size_t valLen) noexcept;

		// map handler
		template<typename T>
		constexpr Error field(CString, HashMap<String, T> *val) noexcept;

		// array handler, with callback to allow handling individual elements
		template<typename T, typename CB>
		constexpr Error field(CString, CB cb) noexcept;

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

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

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

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

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

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

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

		/**
		 * Reads an array length from the current location in the buffer.
		 * @param pass indicates that the parsing should iterate past the array length
		 */
		constexpr Result<ArrayLength> arrayLength(const char *name, bool pass = true) noexcept;

		/**
		 * Reads an string length from the current location in the buffer.
		 */
		constexpr Result<StringLength> stringLength(const char *name) noexcept;

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

		/**
		 * Returns a MetalClawReader to parse a child object.
		 */
		[[nodiscard]]
		constexpr MetalClawReaderTemplate<Reader> child(const char *name, Optional<int> unionIdx = {}) noexcept;

		/**
		 * Indicates whether or not the next field to be read is present.
		 */
		[[nodiscard]]
		constexpr bool fieldPresent(const char *name) const noexcept;

		/**
		 * Indicates whether or not the given field is present.
		 */
		[[nodiscard]]
		constexpr bool fieldPresent(int fieldNo) const noexcept;

		[[nodiscard]]
		constexpr int whichFieldPresent(const char *name, ModelUnion const&) const noexcept;

		constexpr void nextField() noexcept;

	private:
		template<typename I>
		constexpr Error readInteger(I &val) noexcept;

};

template<Reader_c Reader>
constexpr MetalClawReaderTemplate<Reader>::MetalClawReaderTemplate(
		Reader &reader,
		Optional<int> const &unionIdx) noexcept:
		m_fieldPresence(reader),
		m_unionIdx(unionIdx),
		m_reader(reader) {
}

template<Reader_c Reader>
constexpr MetalClawReaderTemplate<Reader>::~MetalClawReaderTemplate() noexcept {
	if (m_field != m_fields) {
		oxTrace("ox.mc.MetalClawReader.error") << "MetalClawReader: incorrect fields number given";
	}
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int8_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int16_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int32_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, int64_t *val) noexcept {
	return readInteger(*val);
}


template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint8_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint16_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint32_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, uint64_t *val) noexcept {
	return readInteger(*val);
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, bool *val) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		auto const result = m_fieldPresence.get(static_cast<size_t>(m_field));
		*val = result.value;
		OX_RETURN_ERROR(result);
	}
	++m_field;
	return {};
}

// array handler
template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::field(
	const char *name, auto *val, size_t const valLen) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			OX_REQUIRE(len, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
			// read the list
			if (valLen >= len) {
				auto reader = child({});
				auto &handler = *reader.interface();
				OX_RETURN_ERROR(handler.setTypeInfo("List", 0, {}, static_cast<size_t>(len)));
				for (size_t i = 0; i < len; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
					OX_RETURN_ERROR(handler.field({}, &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
				}
			} else {
				oxTracef("ox.mc.read.field(T)", "{}, length: {}", name, valLen);
				return ox::Error(McOutputBuffEnded);
			}
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
template<typename T>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, HashMap<String, T> *val) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			OX_REQUIRE(g, m_reader.tellg());
			size_t bytesRead = 0;
			OX_REQUIRE(len, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
			OX_RETURN_ERROR(m_reader.seekg(g));
			// read the list
			auto reader = child("");
			auto &handler = *reader.interface();
			OX_RETURN_ERROR(handler.setTypeInfo("List", 0, {}, static_cast<size_t>(len)));
			// this loop body needs to be in a lambda because of the potential alloca call
			constexpr auto loopBody = [](auto &handler, auto &val) {
				OX_REQUIRE(keyLen, handler.stringLength(nullptr));
				auto wkey = ox_malloca(keyLen + 1, char, 0);
				auto wkeyPtr = wkey.get();
				OX_RETURN_ERROR(handler.fieldCString("", &wkeyPtr, keyLen + 1));
				return handler.field("", &val[wkeyPtr]);
			};
			for (size_t i = 0; i < len; ++i) {
				OX_RETURN_ERROR(loopBody(handler, *val));
			}
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
template<typename T>
constexpr Error MetalClawReaderTemplate<Reader>::field(const char *name, T *val) noexcept {
	if constexpr(isVector_v<T>) {
		if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
			// set size of val if the field is present, don't worry about it if not
			if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
				OX_REQUIRE(len, arrayLength(name, false));
				OX_RETURN_ERROR(ox::resizeVector(*val, len));
				return field(name, val->data(), val->size());
			}
			OX_RETURN_ERROR(ox::resizeVector(*val, 0));
		}
		++m_field;
		return {};
	} else if constexpr(isArray_v<T>) {
		if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
			// set size of val if the field is present, don't worry about it if not
			if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
				OX_REQUIRE(len, arrayLength(name, false));
				if (len > val->size()) {
					return ox::Error(1, "Input array is too long");
				}
			}
			return field(name, val->data(), val->size());
		}
		++m_field;
		return {};
	} else {
		if ((!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) && val) {
			if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
				auto reader = child("");
				OX_RETURN_ERROR(model(reader.interface(), val));
			}
		}
		++m_field;
		return {};
	}
}

template<Reader_c Reader>
template<typename U, bool force>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, UnionView<U, force> val) noexcept {
	if ((!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) && val.get()) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			auto reader = child("", ox::Optional<int>(ox::in_place, val.idx()));
			OX_RETURN_ERROR(model(reader.interface(), val.get()));
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
template<size_t SmallStringSize>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, BasicString<SmallStringSize> *val) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
			const auto cap = size;
			*val = BasicString<SmallStringSize>(cap);
			auto data = val->data();
			// read the string
			OX_RETURN_ERROR(m_reader.read(data, size));
		} else {
			*val = "";
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
template<size_t L>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, IString<L> *val) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
			*val = IString<L>();
			OX_RETURN_ERROR(val->resize(size));
			auto const data = val->data();
			// read the string
			OX_RETURN_ERROR(m_reader.read(data, size));
		} else {
			*val = "";
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(
	CString, char *val, size_t const buffLen) noexcept {
	if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
		// read the length
		size_t bytesRead = 0;
		OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
		if (size > buffLen) {
			return ox::Error(McOutputBuffEnded);
		}
		// re-allocate in case too small
		auto data = val;
		// read the string
		OX_RETURN_ERROR(m_reader.read(data, size));
		data[size] = 0;
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(CString, char **val) noexcept {
	if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
		// read the length
		size_t bytesRead = 0;
		OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
		// re-allocate in case too small
		safeDelete(*val);
		*val = new char[size + 1];
		auto data = ox::Span{*val, size + 1};
		// read the string
		OX_RETURN_ERROR(m_reader.read(data.data(), size));
		data[size] = 0;
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(CString, char **val, size_t buffLen) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			OX_REQUIRE(size, mc::decodeInteger<StringLength>(m_reader, bytesRead));
			// re-allocate if too small
			if (buffLen < size + 1) {
				safeDelete(*val);
				*val = new char[size + 1];
				buffLen = size + 1;
			}
			auto data = ox::Span{*val, size + 1};
			// read the string
			OX_RETURN_ERROR(m_reader.read(data.data(), size));
			data[size] = 0;
		} else {
			auto data = *val;
			if (data) {
				data[0] = 0;
			}
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
constexpr Result<ArrayLength> MetalClawReaderTemplate<Reader>::arrayLength(CString, bool const pass) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			OX_REQUIRE(g, m_reader.tellg());
			OX_REQUIRE(out, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
			if (!pass) {
				OX_RETURN_ERROR(m_reader.seekg(g));
			}
			return out;
		}
	}
	return ox::Error(1);
}

template<Reader_c Reader>
constexpr Result<StringLength> MetalClawReaderTemplate<Reader>::stringLength(CString) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			auto len = mc::decodeInteger<StringLength>(m_reader, bytesRead);
			OX_RETURN_ERROR(m_reader.seekg(-static_cast<int64_t>(bytesRead), ox::ios_base::cur));
			return len;
		}
	}
	return 0;
}

template<Reader_c Reader>
template<typename I>
constexpr Error MetalClawReaderTemplate<Reader>::readInteger(I &val) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			size_t bytesRead = 0;
			auto const result = mc::decodeInteger<I>(m_reader, bytesRead);
			OX_RETURN_ERROR(result);
			val = result.value;
		} else {
			val = 0;
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
template<typename T, typename CB>
constexpr Error MetalClawReaderTemplate<Reader>::field(CString, CB cb) noexcept {
	if (!m_unionIdx.has_value() || static_cast<size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<size_t>(m_field))) {
			// read the length
			size_t bytesRead = 0;
			OX_REQUIRE(len, mc::decodeInteger<ArrayLength>(m_reader, bytesRead));
			// read the list
			auto reader = child("");
			auto &handler = *reader.interface();
			OX_RETURN_ERROR(handler.setTypeInfo("List", 0, {}, static_cast<size_t>(len)));
			for (size_t i = 0; i < len; ++i) {
				T val;
				OX_RETURN_ERROR(handler.field("", &val));
				OX_RETURN_ERROR(cb(i, &val));
			}
		}
	}
	++m_field;
	return {};
}

template<Reader_c Reader>
template<typename T>
constexpr ox::Error MetalClawReaderTemplate<Reader>::setTypeInfo(
		CString, int, const Vector<String>&, size_t const fields) noexcept {
	m_fields = fields;
	// Warning: narrow-conv
	return m_reader.seekg(
			static_cast<int>((fields / 8 + 1) - (fields % 8 == 0)),
			ox::ios_base::cur);
}

template<Reader_c Reader>
constexpr MetalClawReaderTemplate<Reader> MetalClawReaderTemplate<Reader>::child(
		CString,
		Optional<int> const unionIdx) noexcept {
	return MetalClawReaderTemplate<Reader>(m_reader, unionIdx);
}

template<Reader_c Reader>
constexpr bool MetalClawReaderTemplate<Reader>::fieldPresent(CString) const noexcept {
	return m_fieldPresence.get(static_cast<size_t>(m_field)).value;
}

template<Reader_c Reader>
constexpr bool MetalClawReaderTemplate<Reader>::fieldPresent(int const fieldNo) const noexcept {
	return m_fieldPresence.get(static_cast<size_t>(fieldNo)).value;
}

template<Reader_c Reader>
[[nodiscard]]
constexpr int MetalClawReaderTemplate<Reader>::whichFieldPresent(CString, ModelUnion const &u) const noexcept {
	FieldBitmapReader<Reader> p(m_reader);
	for (auto i = 0u; i < u.fieldCount(); ++i) {
		if (p.get(i)) {
			return static_cast<int>(i);
		}
	}
	return -1;
}

template<Reader_c Reader>
constexpr void MetalClawReaderTemplate<Reader>::nextField() noexcept {
	++m_field;
}

using MetalClawReader = MetalClawReaderTemplate<ox::BufferReader>;

template<typename T>
Error readMC(ox::BufferView const buff, T &val) noexcept {
	BufferReader br(buff);
	MetalClawReader reader(br);
	return model(reader.interface(), &val);
}

template<typename T>
Result<T> readMC(ox::BufferView buff) noexcept {
	Result<T> val;
	OX_RETURN_ERROR(readMC(buff, val.value));
	return val;
}

extern template class ModelHandlerInterface<MetalClawReaderTemplate<BufferReader>>;

}
