/*
 * 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/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/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 {

class MetalClawWriter {

	private:
		FieldBitmap 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;
		uint8_t *m_buff = nullptr;

	public:
		constexpr MetalClawWriter(uint8_t *buff, std::size_t buffLen, int unionIdx = -1) noexcept;

		constexpr ~MetalClawWriter() noexcept;

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

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

		constexpr Error field(const char*, CommonPtrWith<bool> auto *val) noexcept;

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

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

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

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

		template<std::size_t L>
		constexpr Error field(const char*, const BString<L> *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 *name, const char *const*val, std::size_t buffLen) noexcept;

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

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

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

		constexpr Error fieldCString(const char *name, char **val, std::size_t buffLen) noexcept {
			return fieldCString(name, const_cast<const char**>(val), buffLen);
		}

		constexpr Error fieldCString(const char *name, char **val) noexcept {
			return fieldCString(name, const_cast<const char**>(val));
		}

		constexpr Error fieldCString(const char *name, char *val, std::size_t buffLen) noexcept {
			return fieldCString(name, const_cast<const char*>(val), buffLen);
		}

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

		template<typename U, bool force = false>
		constexpr Error field(const char*, UnionView<U, force> val) 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;

		[[nodiscard]]
		constexpr std::size_t size() const noexcept;

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

	private:
		constexpr Error appendInteger(Integer_c auto val) noexcept {
			bool fieldSet = false;
			if (val && (m_unionIdx == -1 || m_unionIdx == m_field)) {
				auto mi = mc::encodeInteger(val);
				if (mi.length < m_buffLen) {
					fieldSet = true;
					ox_memcpy(&m_buff[m_buffIt], mi.data, mi.length);
					m_buffIt += mi.length;
				} else {
					return OxError(MC_BUFFENDED);
				}
			}
			oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
			m_field++;
			return OxError(0);
		}

};

constexpr MetalClawWriter::MetalClawWriter(uint8_t *buff, std::size_t buffLen, int unionIdx) noexcept:
		m_fieldPresence(buff, buffLen),
		m_unionIdx(unionIdx),
		m_buffLen(buffLen),
		m_buff(buff) {
}

constexpr MetalClawWriter::~MetalClawWriter() noexcept {
	if (m_field != m_fields) {
		oxTrace("ox::mc::MetalClawWriter::error") << "MetalClawReader: incorrect fields number given";
	}
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<int8_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<int16_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<int32_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<int64_t> auto *val) noexcept {
	return appendInteger(*val);
}


constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<uint8_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<uint16_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<uint32_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<uint64_t> auto *val) noexcept {
	return appendInteger(*val);
}

constexpr Error MetalClawWriter::field(const char*, CommonPtrWith<bool> auto *val) noexcept {
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), *val));
	}
	++m_field;
	return OxError(0);
}

template<std::size_t SmallStringSize>
constexpr Error MetalClawWriter::field(const char*, const BasicString<SmallStringSize> *val) noexcept {
	bool fieldSet = false;
	if (val->len() && (m_unionIdx == -1 || m_unionIdx == m_field)) {
		// write the length
		const auto strLen = mc::encodeInteger(val->len());
		if (m_buffIt + strLen.length + static_cast<std::size_t>(val->len()) < m_buffLen) {
			memcpy(&m_buff[m_buffIt], strLen.data, strLen.length);
			m_buffIt += strLen.length;
			// write the string
			memcpy(&m_buff[m_buffIt], val->c_str(), static_cast<std::size_t>(val->len()));
			m_buffIt += static_cast<std::size_t>(val->len());
			fieldSet = true;
		} else {
			return OxError(MC_BUFFENDED);
		}
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	++m_field;
	return OxError(0);
}

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

template<std::size_t SmallStringSize>
constexpr Error MetalClawWriter::field(const char *name, BasicString<SmallStringSize> *val) noexcept {
	return field(name, const_cast<const BasicString<SmallStringSize>*>(val));
}

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

constexpr Error MetalClawWriter::fieldCString(const char*, const char *const*val, std::size_t) noexcept {
	bool fieldSet = false;
	if (m_unionIdx == -1 || m_unionIdx == m_field) {
		const auto strLen = *val ? ox_strlen(*val) : 0;
		// write the length
		const auto strLenBuff = mc::encodeInteger(strLen);
		if (m_buffIt + strLenBuff.length + static_cast<std::size_t>(strLen) < m_buffLen) {
			ox_memcpy(&m_buff[m_buffIt], strLenBuff.data, strLenBuff.length);
			m_buffIt += strLenBuff.length;
			// write the string
			ox_memcpy(&m_buff[m_buffIt], *val, static_cast<std::size_t>(strLen));
			m_buffIt += static_cast<std::size_t>(strLen);
			fieldSet = true;
		} else {
			return OxError(MC_BUFFENDED);
		}
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	++m_field;
	return OxError(0);
}

constexpr Error MetalClawWriter::fieldCString(const char *name, const char **val) noexcept {
	return fieldCString(name, val, {});
}

constexpr Error MetalClawWriter::fieldCString(const char *name, const char *const*val) noexcept {
	return fieldCString(name, val, {});
}

constexpr Error MetalClawWriter::fieldCString(const char*, const char *val, std::size_t strLen) noexcept {
	bool fieldSet = false;
	if (strLen && (m_unionIdx == -1 || m_unionIdx == m_field)) {
		// write the length
		const auto strLenBuff = mc::encodeInteger(strLen);
		if (m_buffIt + strLenBuff.length + static_cast<std::size_t>(strLen) < m_buffLen) {
			ox_memcpy(&m_buff[m_buffIt], strLenBuff.data, strLenBuff.length);
			m_buffIt += strLenBuff.length;
			// write the string
			ox_memcpy(&m_buff[m_buffIt], val, static_cast<std::size_t>(strLen));
			m_buffIt += static_cast<std::size_t>(strLen);
			fieldSet = true;
		} else {
			return OxError(MC_BUFFENDED);
		}
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	++m_field;
	return OxError(0);
}

template<typename T>
constexpr Error MetalClawWriter::field(const char*, T *val) noexcept {
	if constexpr(isVector_v<T>) {
		return field(nullptr, val->data(), val->size());
	} else {
		bool fieldSet = false;
		if (val && (m_unionIdx == -1 || m_unionIdx == m_field)) {
			MetalClawWriter writer(m_buff + m_buffIt, m_buffLen - m_buffIt);
			ModelHandlerInterface handler{&writer};
			oxReturnError(model(&handler, val));
			if (static_cast<std::size_t>(writer.m_fieldPresence.getMaxLen()) < writer.m_buffIt) {
				m_buffIt += writer.m_buffIt;
				fieldSet = true;
			}
		}
		oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
		++m_field;
		return OxError(0);
	}
}

template<typename U, bool force>
constexpr Error MetalClawWriter::field(const char*, UnionView<U, force> val) noexcept {
	bool fieldSet = false;
	if (val.get() && (m_unionIdx == -1 || m_unionIdx == m_field)) {
		MetalClawWriter writer(m_buff + m_buffIt, m_buffLen - m_buffIt, val.idx());
		ModelHandlerInterface handler{&writer};
		oxReturnError(model(&handler, val.get()));
		if (static_cast<std::size_t>(writer.m_fieldPresence.getMaxLen()) < writer.m_buffIt) {
			m_buffIt += writer.m_buffIt;
			fieldSet = true;
		}
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	m_field++;
	return OxError(0);
}

template<typename T>
constexpr Error MetalClawWriter::field(const char*, T *val, std::size_t len) noexcept {
	bool fieldSet = false;

	if (len && (m_unionIdx == -1 || m_unionIdx == m_field)) {
		// write the length
		const auto arrLen = mc::encodeInteger(len);
		if (m_buffIt + arrLen.length < m_buffLen) {
			ox_memcpy(&m_buff[m_buffIt], arrLen.data, arrLen.length);
			m_buffIt += arrLen.length;
		} else {
			return OxError(MC_BUFFENDED);
		}

		MetalClawWriter writer(m_buff + m_buffIt, m_buffLen - m_buffIt);
		ModelHandlerInterface handler{&writer};
		handler.setTypeInfo<T>("List", 0, len);

		// write the array
		for (std::size_t i = 0; i < len; i++) {
			oxReturnError(handler.field("", &val[i]));
		}

		m_buffIt += writer.m_buffIt;
		fieldSet = true;
	}

	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	m_field++;
	return OxError(0);
}

template<typename T>
constexpr Error MetalClawWriter::field(const char*, const HashMap<String, T> *val) noexcept {
	const auto &keys = val->keys();
	const auto len = keys.size();
	bool fieldSet = false;
	if (len && (m_unionIdx == -1 || m_unionIdx == m_field)) {
		// write the length
		const auto arrLen = mc::encodeInteger(len);
		if (m_buffIt + arrLen.length < m_buffLen) {
			ox_memcpy(&m_buff[m_buffIt], arrLen.data, arrLen.length);
			m_buffIt += arrLen.length;
		} else {
			return OxError(MC_BUFFENDED);
		}
		MetalClawWriter writer(m_buff + m_buffIt, m_buffLen - m_buffIt);
		ModelHandlerInterface handler{&writer};
		// double len for both key and value
		handler.setTypeInfo("Map", 0, len * 2);
		// write the array
		for (std::size_t i = 0; i < len; i++) {
			const auto &key = keys[i];
			const auto keyLen = ox_strlen(key);
			auto wkey = ox_malloca(keyLen + 1, char, 0);
			memcpy(wkey, key.c_str(), keyLen + 1);
			oxReturnError(handler.fieldCString("", wkey.get(), keyLen));
			oxRequireM(value, val->at(key));
			oxReturnError(handler.field("", value));
		}
		m_buffIt += writer.m_buffIt;
		fieldSet = true;
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	m_field++;
	return OxError(0);
}

template<typename T>
constexpr Error MetalClawWriter::field(const char *name, HashMap<String, T> *val) noexcept {
	return field(name, const_cast<const HashMap<String, T>*>(val));
}

template<typename T>
constexpr void MetalClawWriter::setTypeInfo(const char*, int, int fields) noexcept {
	m_fields = fields;
	m_fieldPresence.setFields(fields);
	m_buffIt = static_cast<std::size_t>(m_fieldPresence.getMaxLen());
	ox_memset(m_buff, 0, m_buffIt);
}

constexpr std::size_t MetalClawWriter::size() const noexcept {
	return m_buffIt;
}

Result<Buffer> writeMC(auto *val) noexcept {
	Buffer buff(10 * units::MB);
	MetalClawWriter writer(reinterpret_cast<uint8_t*>(buff.data()), buff.size());
	ModelHandlerInterface handler{&writer};
	oxReturnError(model(&handler, val));
	buff.resize(writer.size());
	return buff;
}

Error writeMC(char *buff, std::size_t buffLen, auto *val, std::size_t *sizeOut = nullptr) noexcept {
	MetalClawWriter writer(reinterpret_cast<uint8_t*>(buff), buffLen);
	ModelHandlerInterface handler(&writer);
	auto err = model(&handler, val);
	if (sizeOut) {
		*sizeOut = writer.size();
	}
	return err;
}

}
