/*
 * 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/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<auto HandlerMaker>
class MetalClawReaderTemplate {

	private:
		FieldBitmapReader<const uint8_t*> m_fieldPresence;
		int m_fields = 0;
		int m_field = 0;
		int m_unionIdx = -1;
		std::size_t m_buffIt = 0;
		std::size_t m_buffLen = 0;
		const uint8_t *m_buff = nullptr;
		MetalClawReaderTemplate<HandlerMaker> *m_parent = nullptr;

	public:
		constexpr MetalClawReaderTemplate(const uint8_t *buff, std::size_t buffLen, int unionIdx = -1,
		                                  MetalClawReaderTemplate<HandlerMaker> *parent = nullptr) 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.
		 */
		[[nodiscard]]
		constexpr StringLength stringLength(const char *name) noexcept;

		template<typename T = std::nullptr_t>
		constexpr void setTypeInfo(const char *name = T::TypeName, int version = T::TypeVersion,
		                           int fields = ModelFieldCount_v<T>) noexcept;

		/**
		 * Returns a MetalClawReader to parse a child object.
		 */
		[[nodiscard]]
		constexpr MetalClawReaderTemplate<HandlerMaker> child(const char *name, int unionIdx = -1) 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;

		[[nodiscard]]
		static constexpr auto opType() noexcept {
			return OpType::Read;
		}

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

};

template<auto HandlerMaker>
constexpr MetalClawReaderTemplate<HandlerMaker>::MetalClawReaderTemplate(const uint8_t *buff, std::size_t buffLen,
                                                                         int unionIdx,
                                                                         MetalClawReaderTemplate *parent) noexcept:
		m_fieldPresence(buff, buffLen),
		m_unionIdx(unionIdx),
		m_buffLen(buffLen),
		m_buff(buff),
		m_parent(parent) {
}

template<auto HandlerMaker>
constexpr MetalClawReaderTemplate<HandlerMaker>::~MetalClawReaderTemplate() noexcept {
	if (m_parent) {
		m_parent->m_buffIt += m_buffIt;
	}
	if (m_field != m_fields) {
		oxTrace("ox::mc::MetalClawReader::error") << "MetalClawReader: incorrect fields number given";
	}
}

template<auto HandlerMaker>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char*, int8_t *val) noexcept {
	return readInteger(val);
}

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

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

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


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

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

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

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

template<auto HandlerMaker>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char*, bool *val) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		auto valErr = m_fieldPresence.get(static_cast<std::size_t>(m_field));
		*val = valErr.value;
		oxReturnError(valErr.error);
	}
	++m_field;
	return OxError(0);
}

// array handler
template<auto HandlerMaker>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char *name, auto *val, std::size_t valLen) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			if (m_buffIt >= m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			std::size_t bytesRead = 0;
			oxRequire(len, mc::decodeInteger<ArrayLength>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead));
			m_buffIt += bytesRead;
			// read the list
			if (valLen >= len) {
				auto reader = child("");
				auto handler = HandlerMaker(&reader);
				handler.setTypeInfo("List", 0, static_cast<int>(len));
				for (std::size_t i = 0; i < len; ++i) {
					oxReturnError(handler.field("", &val[i]));
				}
			} else {
				oxTrace("ox::mc::read::field(T)") << name << ", size:" << valLen;
				return OxError(MC_OUTBUFFENDED);
			}
		}
	}
	++m_field;
	return OxError(0);
}

template<auto HandlerMaker>
template<typename T>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char*, HashMap<String, T> *val) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			if (m_buffIt >= m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			std::size_t bytesRead = 0;
			oxRequire(len, mc::decodeInteger<ArrayLength>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead));
			m_buffIt += bytesRead;
			// read the list
			auto reader = child("");
			auto handler = HandlerMaker(&reader);
			handler.setTypeInfo("List", 0, static_cast<int>(len));
			for (std::size_t i = 0; i < len; ++i) {
				const auto keyLen = handler.stringLength(nullptr);
				auto wkey = ox_malloca(keyLen + 1, char, 0);
				auto wkeyPtr = wkey.get();
				oxReturnError(handler.fieldCString("", &wkeyPtr, static_cast<int>(keyLen + 1)));
				oxReturnError(handler.field("", &val->operator[](wkey.get())));
			}
		}
	}
	++m_field;
	return OxError(0);
}

template<auto HandlerMaker>
template<typename T>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char *name, T *val) noexcept {
	if constexpr(isVector_v<T>) {
		if (m_unionIdx == -1 || 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));
				val->resize(len);
			}
			return field(name, val->data(), val->size());
		}
		++m_field;
		return OxError(0);
	} else {
		if ((m_unionIdx == -1 || m_unionIdx == m_field) && val) {
			if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
				auto reader = child("");
				auto handler = HandlerMaker(&reader);
				oxReturnError(model(&handler, val));
			}
		}
		++m_field;
		return OxError(0);
	}
}

template<auto HandlerMaker>
template<typename U, bool force>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char*, UnionView<U, force> val) noexcept {
	if ((m_unionIdx == -1 || m_unionIdx == m_field) && val.get()) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			auto reader = child("", val.idx());
			auto handler = HandlerMaker(&reader);
			oxReturnError(model(&handler, val.get()));
		}
	}
	++m_field;
	return OxError(0);
}

template<auto HandlerMaker>
template<std::size_t SmallStringSize>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char*, BasicString<SmallStringSize> *val) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			if (m_buffIt >= m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			std::size_t bytesRead = 0;
			oxRequire(size, mc::decodeInteger<StringLength>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead));
			m_buffIt += bytesRead;
			const auto cap = size;
			*val = BasicString<SmallStringSize>(cap);
			auto data = val->data();
			// read the string
			if (static_cast<StringLength>(cap) < size) {
				return OxError(MC_OUTBUFFENDED);
			}
			if (m_buffIt + size > m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			memcpy(data, &m_buff[m_buffIt], size);
			data[size] = 0;
			m_buffIt += size;
		} else {
			*val = "";
		}
	}
	++m_field;
	return OxError(0);
}

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

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

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

template<auto HandlerMaker>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::fieldCString(const char*, char **val, std::size_t buffLen) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			if (m_buffIt >= m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			std::size_t bytesRead = 0;
			auto [size, err] = mc::decodeInteger<StringLength>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead);
			m_buffIt += bytesRead;
			oxReturnError(err);
			// 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
			if (m_buffIt + size <= m_buffLen) {
				ox_memcpy(data, &m_buff[m_buffIt], size);
				data[size] = 0;
				m_buffIt += size;
			} else {
				return OxError(MC_BUFFENDED);
			}
		} else {
			auto data = *val;
			if (data) {
				data[0] = 0;
			}
		}
	}
	++m_field;
	return OxError(0);
}

template<auto HandlerMaker>
constexpr Result<ArrayLength> MetalClawReaderTemplate<HandlerMaker>::arrayLength(const char*, bool pass) noexcept {
	if ((m_unionIdx == -1 || m_unionIdx == m_field)) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			if (m_buffIt >= m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			std::size_t bytesRead = 0;
			auto out = mc::decodeInteger<ArrayLength>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead).value;
			if (pass) {
				m_buffIt += bytesRead;
			}
			return out;
		}
	}
	return OxError(1);
}

template<auto HandlerMaker>
template<typename I>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::readInteger(I *val) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			std::size_t bytesRead = 0;
			if (m_buffIt >= m_buffLen) {
				oxTrace("ox::MetalClaw::readInteger") << "Buffer ended";
				return OxError(MC_BUFFENDED);
			}
			auto valErr = mc::decodeInteger<I>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead);
			m_buffIt += bytesRead;
			oxReturnError(valErr.error);
			*val = valErr.value;
		} else {
			*val = 0;
		}
	}
	++m_field;
	return OxError(0);
}

template<auto HandlerMaker>
template<typename T, typename CB>
constexpr Error MetalClawReaderTemplate<HandlerMaker>::field(const char*, CB cb) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
			// read the length
			if (m_buffIt >= m_buffLen) {
				return OxError(MC_BUFFENDED);
			}
			std::size_t bytesRead = 0;
			oxRequire(len, mc::decodeInteger<ArrayLength>(&m_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead));
			m_buffIt += bytesRead;

			// read the list
			auto reader = child("");
			auto handler = HandlerMaker(&reader);
			handler.setTypeInfo("List", 0, static_cast<int>(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<auto HandlerMaker>
constexpr StringLength MetalClawReaderTemplate<HandlerMaker>::stringLength(const char*) noexcept {
	if ((m_unionIdx == -1 || 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_buff[m_buffIt], m_buffLen - m_buffIt, &bytesRead);
			return len.value;
		}
	}
	return 0;
}

template<auto HandlerMaker>
template<typename T>
constexpr void MetalClawReaderTemplate<HandlerMaker>::setTypeInfo(const char*, int, int fields) noexcept {
	m_fields = fields;
	m_buffIt = static_cast<std::size_t>((fields / 8 + 1) - (fields % 8 == 0));
	m_fieldPresence.setFields(fields);
	m_fieldPresence.setMaxLen(static_cast<int>(m_buffIt));
}

template<auto HandlerMaker>
constexpr MetalClawReaderTemplate<HandlerMaker> MetalClawReaderTemplate<HandlerMaker>::child(const char*, int unionIdx) noexcept {
	return MetalClawReaderTemplate<HandlerMaker>(m_buff + m_buffIt, m_buffLen - m_buffIt, unionIdx, this);
}

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

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

template<auto HandlerMaker>
[[nodiscard]]
constexpr int MetalClawReaderTemplate<HandlerMaker>::whichFieldPresent(const char*, const ModelUnion &u) const noexcept {
	FieldBitmapReader<const uint8_t*> p(m_buff + m_buffIt, m_buffLen - m_buffIt);
	p.setFields(u.fieldCount());
	for (auto i = 0u; i < u.fieldCount(); ++i) {
		if (p.get(i)) {
			return static_cast<int>(i);
		}
	}
	return -1;
}

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

using MetalClawReader = MetalClawReaderTemplate<[](auto r) {
	return ModelHandlerInterface{r};
}>;

template<typename T>
Error readMC(const char *buff, std::size_t buffLen, T *val) noexcept {
	MetalClawReader reader(reinterpret_cast<const uint8_t*>(buff), buffLen);
	ModelHandlerInterface 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());
}

}
