/*
 * 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 <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;
		std::size_t m_fields = 0;
		std::size_t m_field = 0;
		ox::Optional<int> m_unionIdx;
		Reader &m_reader;

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

		constexpr ~MetalClawReaderTemplate() noexcept;

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

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

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

		// array handler
		constexpr Error field(const char*, auto *val, std::size_t len) noexcept;

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

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

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

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

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

		template<std::size_t L>
		constexpr Error field(const char*, BString<L> *val) noexcept;

		constexpr Error fieldCString(const char*, char *val, std::size_t buffLen) noexcept;

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

		constexpr Error fieldCString(const char*, char **val, std::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>& = {},
				std::size_t fields = ModelFieldCount_v<T>) noexcept;

		/**
		 * Returns a MetalClawReader to parse a child object.
		 */
		[[nodiscard]]
		constexpr MetalClawReaderTemplate<Reader> child(const char *name, ox::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, const ModelUnion&) 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,
		ox::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(const char*, int8_t *val) noexcept {
	return readInteger(val);
}

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

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

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


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

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

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

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

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

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

template<Reader_c Reader>
template<typename T>
constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, HashMap<String, T> *val) noexcept {
	if (!m_unionIdx.has_value() || static_cast<std::size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			oxRequire(g, m_reader.tellg());
			std::size_t bytesRead = 0;
			oxRequire(len, mc::decodeInteger<ArrayLength>(m_reader, &bytesRead));
			oxReturnError(m_reader.seekg(g));
			// read the list
			auto reader = child("");
			auto &handler = *reader.interface();
			oxReturnError(handler.setTypeInfo("List", 0, {}, static_cast<std::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) {
				oxRequire(keyLen, handler.stringLength(nullptr));
				auto wkey = ox_malloca(keyLen + 1, char, 0);
				auto wkeyPtr = wkey.get();
				oxReturnError(handler.fieldCString("", &wkeyPtr, keyLen + 1));
				return handler.field("", &val[wkeyPtr]);
			};
			for (std::size_t i = 0; i < len; ++i) {
				oxReturnError(loopBody(handler, *val));
			}
		}
	}
	++m_field;
	return OxError(0);
}

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<std::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<std::size_t>(m_field))) {
				oxRequire(len, arrayLength(name, false));
				oxReturnError(ox::resizeVector(*val, len));
				return field(name, val->data(), val->size());
			}
			oxReturnError(ox::resizeVector(*val, 0));
		}
		++m_field;
		return {};
	} else if constexpr(isArray_v<T>) {
		if (!m_unionIdx.has_value() || static_cast<std::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<std::size_t>(m_field))) {
				oxRequire(len, arrayLength(name, false));
				if (len > val->size()) {
					return OxError(1, "Input array is too long");
				}
			}
			return field(name, val->data(), val->size());
		}
		++m_field;
		return {};
	} else {
		if ((!m_unionIdx.has_value() || static_cast<std::size_t>(*m_unionIdx) == m_field) && val) {
			if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
				auto reader = child("");
				oxReturnError(model(reader.interface(), val));
			}
		}
		++m_field;
		return {};
	}
}

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

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

template<Reader_c Reader>
template<std::size_t L>
constexpr Error MetalClawReaderTemplate<Reader>::field(const char *name, BString<L> *val) noexcept {
	return fieldCString(name, val->data(), val->cap());
}

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

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

template<Reader_c Reader>
constexpr Error MetalClawReaderTemplate<Reader>::fieldCString(const char*, char **val, std::size_t buffLen) noexcept {
	if (!m_unionIdx.has_value() || static_cast<std::size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			std::size_t bytesRead = 0;
			oxRequire(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 = *val;
			// read the string
			oxReturnError(m_reader.read(data, size));
			data[size] = 0;
		} else {
			auto data = *val;
			if (data) {
				data[0] = 0;
			}
		}
	}
	++m_field;
	return OxError(0);
}

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

template<Reader_c Reader>
constexpr Result<StringLength> MetalClawReaderTemplate<Reader>::stringLength(const char*) noexcept {
	if (!m_unionIdx.has_value() || static_cast<std::size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			std::size_t bytesRead = 0;
			auto len = mc::decodeInteger<StringLength>(m_reader, &bytesRead);
			oxReturnError(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<std::size_t>(*m_unionIdx) == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			std::size_t bytesRead = 0;
			auto const result = mc::decodeInteger<I>(m_reader, &bytesRead);
			oxReturnError(result);
			*val = result.value;
		} else {
			*val = 0;
		}
	}
	++m_field;
	return OxError(0);
}

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

template<Reader_c Reader>
template<typename T>
constexpr ox::Error MetalClawReaderTemplate<Reader>::setTypeInfo(
		const char*, int, const Vector<String>&, std::size_t 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(
		const char*,
		ox::Optional<int> unionIdx) noexcept {
	return MetalClawReaderTemplate<Reader>(m_reader, unionIdx);
}

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

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

template<Reader_c Reader>
[[nodiscard]]
constexpr int MetalClawReaderTemplate<Reader>::whichFieldPresent(const char*, const ModelUnion &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(const char *buff, std::size_t buffLen, T *val) noexcept {
	BufferReader br(buff, buffLen);
	MetalClawReader reader(br);
	ModelHandlerInterface<MetalClawReader, ox::OpType::Read> handler(&reader);
	return model(&handler, val);
}

template<typename T>
Result<T> readMC(const char *buff, std::size_t buffLen) noexcept {
	T val;
	oxReturnError(readMC(buff, buffLen, &val));
	return val;
}

template<typename T>
Result<T> readMC(const Buffer &buff) noexcept {
	return readMC<T>(buff.data(), buff.size());
}

extern template class ModelHandlerInterface<MetalClawReaderTemplate<BufferReader>>;

}
