/*
 * Copyright 2015 - 2023 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 <json/json.h>

#include <ox/model/fieldcounter.hpp>
#include <ox/model/modelhandleradaptor.hpp>
#include <ox/model/optype.hpp>
#include <ox/model/types.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/hashmap.hpp>
#include <ox/std/string.hpp>
#include <ox/std/uuid.hpp>

namespace ox {

class OrganicClawWriter {

	friend Result<Buffer> writeOC(const auto &val) noexcept;

	protected:
		Json::Value m_json;
		Json::ArrayIndex m_fieldIt = 0;
		int m_unionIdx = -1;

	public:
		explicit OrganicClawWriter(int unionIdx = -1) noexcept;

		explicit OrganicClawWriter(Json::Value json, int unionIdx = -1) noexcept;

		Error field(const char *key, const int8_t *val) noexcept {
			if (*val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const int16_t *val) noexcept {
			if (*val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const int32_t *val) noexcept {
			if (*val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const int64_t *val) noexcept {
			if (*val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}


		Error field(const char *key, const uint8_t *val) noexcept {
			if (*val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const uint16_t *val) noexcept {
			if (targetValid() && *val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const uint32_t *val) noexcept {
			if (targetValid() && *val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const uint64_t *val) noexcept {
			if (targetValid() && *val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error field(const char *key, const bool *val) noexcept {
			if (targetValid() && *val) {
				value(key) = *val;
			}
			++m_fieldIt;
			return {};
		}

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

		template<typename T>
		Error field(const char *key, const HashMap<String, T> *val) noexcept {
			if (targetValid()) {
				const auto &keys = val->keys();
				OrganicClawWriter w;
				ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{&w};
				for (std::size_t i = 0; i < keys.size(); ++i) {
					const auto k = keys[i].c_str();
					if (k) [[likely]] {
						oxRequireM(value, val->at(k));
						oxReturnError(handler.field(k, value));
					}
				}
				value(key) = w.m_json;
			}
			++m_fieldIt;
			return {};
		}

		template<std::size_t L>
		Error field(const char *key, const BString<L> *val) noexcept {
			if (targetValid() && val->len()) {
				value(key) = val->c_str();
			}
			++m_fieldIt;
			return {};
		}

		template<std::size_t L>
		Error field(const char *key, const BasicString<L> *val) noexcept {
			if (targetValid() && val->len()) {
				value(key) = val->c_str();
			}
			++m_fieldIt;
			return OxError(0);
		}

		Error fieldCString(const char*, const char *const*val, int len) noexcept;

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

		Error field(const char *key, const UUID *uuid) noexcept;

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

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

		template<typename T>
		constexpr ox::Error setTypeInfo(
				const char* = T::TypeName,
				int = T::TypeVersion) noexcept {
			return {};
		}

		template<typename T>
		constexpr ox::Error setTypeInfo(
				const char*,
				int,
				const Vector<String>&,
				std::size_t) noexcept {
			return {};
		}

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

	private:
		[[nodiscard]]
		constexpr bool targetValid() const noexcept {
			return static_cast<int>(m_fieldIt) == m_unionIdx || m_unionIdx == -1;
		}

		[[nodiscard]]
		Json::Value &value(const char *key) noexcept;

};

template<typename T>
Error OrganicClawWriter::field(const char *key, const T *val, std::size_t len) noexcept {
	if (targetValid() && len) {
		OrganicClawWriter w((Json::Value(Json::arrayValue)));
		ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{&w};
		for (std::size_t i = 0; i < len; ++i) {
			oxReturnError(handler.field("", &val[i]));
		}
		value(key) = w.m_json;
	}
	++m_fieldIt;
	return OxError(0);
}

template<typename T>
Error OrganicClawWriter::field(const char *key, const T *val) noexcept {
	if constexpr(isVector_v<T> || isArray_v<T>) {
		return field(key, val->data(), val->size());
	} else if (val && targetValid()) {
		OrganicClawWriter w;
		ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{&w};
		oxReturnError(model(&handler, val));
		if (!w.m_json.isNull()) {
			value(key) = w.m_json;
		}
	}
	++m_fieldIt;
	return OxError(0);
}

template<typename U, bool force>
Error OrganicClawWriter::field(const char *key, UnionView<U, force> val) noexcept {
	if (targetValid()) {
		OrganicClawWriter w(val.idx());
		ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler{&w};
		oxReturnError(model(&handler, val.get()));
		if (!w.m_json.isNull()) {
			value(key) = w.m_json;
		}
	}
	++m_fieldIt;
	return OxError(0);
}

Result<Buffer> writeOC(const auto &val) noexcept {
	OrganicClawWriter writer;
	ModelHandlerInterface<OrganicClawWriter, OpType::Write> handler(&writer);
	oxReturnError(model(&handler, &val));
	Json::StreamWriterBuilder jsonBuilder;
	const auto str = Json::writeString(jsonBuilder, writer.m_json);
	Buffer buff(str.size() + 1);
	memcpy(buff.data(), str.c_str(), str.size() + 1);
	return buff;
}

}
