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

	private:
		ox::Vector<uint8_t, 16> m_presenceMapBuff{};
		FieldBitmap m_fieldPresence;
		int m_field = 0;
		ox::Optional<int> m_unionIdx;
		std::size_t m_writerBeginP{};
		Writer &m_writer;

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

		constexpr ~MetalClawWriter() noexcept = default;

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

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

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

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

		template<typename T>
		constexpr Error field(const char *name, const 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;

		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;

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

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

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

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

		ox::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);
				oxReturnError(m_writer.write(reinterpret_cast<const char*>(mi.data), mi.length));
				fieldSet = true;
			}
			oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
			++m_field;
			return OxError(0);
		}

};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::field(const char*, const T *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> writer(m_writer);
			ModelHandlerInterface<MetalClawWriter<Writer>> handler{&writer};
			oxReturnError(model(&handler, val));
			oxReturnError(writer.finalize());
			fieldSet = writeIdx != m_writer.tellp();
		}
		oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
		++m_field;
		return {};
	}
}

template<Writer_c Writer>
template<typename U, bool force>
constexpr Error MetalClawWriter<Writer>::field(const char*, 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> writer(m_writer, ox::Optional<int>(ox::in_place, val.idx()));
		ModelHandlerInterface handler{&writer};
		oxReturnError(model(&handler, val.get()));
		oxReturnError(writer.finalize());
		fieldSet = writeIdx != m_writer.tellp();
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	++m_field;
	return {};
}

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

template<Writer_c Writer>
template<typename T>
constexpr Error MetalClawWriter<Writer>::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.has_value() || *m_unionIdx == m_field)) {
		// write the length
		const auto arrLen = mc::encodeInteger(len);
		oxReturnError(m_writer.write(reinterpret_cast<const char*>(arrLen.data), arrLen.length));
		// write map
		MetalClawWriter<Writer> writer(m_writer);
		ModelHandlerInterface handler{&writer};
		// double len for both key and value
		oxReturnError(handler.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) -> ox::Error {
			const auto keyLen = key.len();
			auto wkey = ox_malloca(keyLen + 1, char, 0);
			memcpy(wkey.get(), key.c_str(), keyLen + 1);
			oxReturnError(handler.fieldCString("", wkey.get(), keyLen));
			oxRequireM(value, val.at(key));
			return handler.field("", value);
		};
		// write the array
		for (std::size_t i = 0; i < len; ++i) {
			auto const&key = keys[i];
			oxReturnError(loopBody(handler, key, *val));
		}
		oxReturnError(writer.finalize());
		fieldSet = true;
	}
	oxReturnError(m_fieldPresence.set(static_cast<std::size_t>(m_field), fieldSet));
	++m_field;
	return OxError(0);
}

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

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

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

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

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

}
