/*
 * Copyright 2015 - 2025 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/std/def.hpp>

OX_ALLOW_UNSAFE_BUFFERS_BEGIN
#include <json/json.h>
OX_ALLOW_UNSAFE_BUFFERS_END

#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/hashmap.hpp>
#include <ox/std/memops.hpp>
#include <ox/std/memory.hpp>
#include <ox/std/string.hpp>
#include <ox/std/uuid.hpp>

namespace ox {

class OrganicClawReader {

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

	public:
		OrganicClawReader() noexcept = default;

		OrganicClawReader(uint8_t const *buff, size_t buffSize);

		OrganicClawReader(CString json, size_t buffSize);

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

		Error field(CString key, bool *val) noexcept;

		// array handler
		template<typename T>
		Error field(CString key, T *val, size_t len) noexcept;

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

		template<typename T>
		Error field(CString key, T *val) noexcept;

		template<typename U, bool force = false>
		Error field(CString key, UnionView<U, force> val) noexcept;

		template<size_t L>
		Error field(CString key, BasicString<L> *val) noexcept;

		template<size_t L>
		Error field(CString key, IString<L> *val) noexcept;

		Error fieldCString(CString key, char *val, size_t buffLen) noexcept;

		Error fieldCString(CString key, char **val) noexcept;

		Error fieldCString(CString key, char **val, size_t buffLen) noexcept;

		Error field(CString key, UUID *val) noexcept;

		/**
		 * Reads an array length from the current location in the buffer.
		 * @param key
		 * @param pass indicates that the parsing should iterate past the array length
		 */
		Result<size_t> arrayLength(CString key, bool pass = true) noexcept;

		/**
		 * Reads an string length from the current location in the buffer.
		 */
		[[nodiscard]]
		size_t stringLength(CString key) noexcept;

		template<typename T = void>
		constexpr Error setTypeInfo() noexcept {
			return {};
		}

		template<typename T = void>
		constexpr Error setTypeInfo(CString) noexcept {
			return {};
		}

		template<typename T = void>
		constexpr Error setTypeInfo(CString, int, const Vector<String>& = {}) noexcept {
			return {};
		}

		template<typename T = void>
		constexpr Error setTypeInfo(CString, int, const Vector<String>& = {}, size_t = {}) noexcept {
			return {};
		}

		/**
		 * Returns a OrganicClawReader to parse a child object.
		 */
		[[nodiscard]]
		OrganicClawReader child(CString key, int unionIdx = -1) noexcept;

		// compatibility stub
		constexpr void nextField() noexcept {}

		[[nodiscard]]
		bool fieldPresent(CString key) noexcept;

		[[nodiscard]]
		int whichFieldPresent(CString name, const ModelUnion &u) const noexcept;

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

	private:
		[[nodiscard]]
		Json::Value &value(CString key) noexcept;

		[[nodiscard]]
		bool targetValid() const noexcept;

};

template<typename T>
Error OrganicClawReader::field(CString key, T *val) noexcept {
	Error err{};
	try {
		if constexpr (is_integer_v<T>) {
			if (targetValid()) {
				auto const &jv = value(key);
				auto const rightType = sizeof(T) == 8 ?
					(is_signed_v<T> ? jv.isInt64() : jv.isUInt64()) :
					(is_signed_v<T> ? jv.isInt() : jv.isUInt());
				if (jv.empty()) {
					*val = 0;
				} else if (rightType) {
					if constexpr(is_signed_v<T>) {
						*val = static_cast<T>(jv.asInt64());
					} else {
						*val = static_cast<T>(jv.asUInt64());
					}
				} else {
					err = Error{1, "Type mismatch"};
				}
			}
		} else if constexpr (isVector_v<T>) {
			auto const &srcVal = value(key);
			auto const srcSize = srcVal.size();
			OX_RETURN_ERROR(resizeVector(*val, srcSize));
			err = field(key, val->data(), val->size());
		} else if constexpr (isArray_v<T>) {
			auto const &srcVal = value(key);
			auto const srcSize = srcVal.size();
			if (srcSize > val->size()) {
				err = Error{1, "Input array is too long"};
			} else {
				err = field(key, val->data(), val->size());
			}
		} else if (targetValid()) {
			auto const &jv = value(key);
			if (jv.empty() || jv.isObject()) {
				auto reader = child(key);
				ModelHandlerInterface handler(reader);
				err = model(&handler, val);
			} else {
				err = Error{1, "Type mismatch"};
			}
		}
	} catch (Json::LogicError const &e) {
		err = Error{1, "error reading JSON data"};
	}
	++m_fieldIt;
	return err;
}

template<typename U, bool force>
Error OrganicClawReader::field(CString const key, UnionView<U, force> val) noexcept {
	Error err{};
	if (targetValid()) {
		auto const &jv = value(key);
		if (jv.empty() || jv.isObject()) {
			auto reader = child(key, val.idx());
			ModelHandlerInterface handler(reader);
			err = model(&handler, val.get());
		} else {
			err = Error{1, "Type mismatch"};
		}
	}
	++m_fieldIt;
	return err;
}

template<size_t L>
Error OrganicClawReader::field(CString const key, BasicString<L> *val) noexcept {
	Error err{};
	if (targetValid()) {
		auto const &jv = value(key);
		if (jv.empty()) {
			*val = BasicString<L>{};
		} else if (jv.isString()) {
			*val = jv.asString().c_str();
		} else {
			err = Error{1, "Type mismatch"};
		}
	}
	++m_fieldIt;
	return err;
}

template<size_t L>
Error OrganicClawReader::field(CString const key, IString<L> *val) noexcept {
	Error err{};
	if (targetValid()) {
		auto const &jv = value(key);
		if (jv.empty()) {
			*val = IString<L>{};
		} else if (jv.isString()) {
			*val = jv.asString().c_str();
		} else {
			err = Error{1, "Type mismatch"};
		}
	}
	++m_fieldIt;
	return err;
}

// array handler
template<typename T>
Error OrganicClawReader::field(CString const key, T *val, size_t valLen) noexcept {
	auto const &srcVal = value(key);
	if (!srcVal.isNull() && !srcVal.isArray()) {
		return Error{1, "Type mismatch"};
	}
	auto srcSize = srcVal.size();
	if (srcSize > valLen) {
		return Error{1};
	}
	OrganicClawReader r(srcVal);
	ModelHandlerInterface handler{r};
	for (decltype(srcSize) i = 0; i < srcSize; ++i) {
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
		OX_RETURN_ERROR(handler.field("", &val[i]));
OX_ALLOW_UNSAFE_BUFFERS_END
	}
	return {};
}

template<typename T>
Error OrganicClawReader::field(CString const key, HashMap<String, T> *val) noexcept {
	auto const &srcVal = value(key);
	if (!srcVal.isObject()) {
		return Error{1, "Type mismatch"};
	}
	auto keys = srcVal.getMemberNames();
	auto srcSize = srcVal.size();
	OrganicClawReader r(srcVal);
	ModelHandlerInterface handler{r};
	for (decltype(srcSize) i = 0; i < srcSize; ++i) {
		auto const k = keys[i].c_str();
		OX_RETURN_ERROR(handler.field(k, &val->operator[](k)));
	}
	return {};
}

Error readOC(BufferView const buff, auto &val) noexcept {
	// OrganicClawReader constructor can throw, but readOC should return its errors.
	try {
		Json::Value doc;
		Json::CharReaderBuilder parserBuilder;
		auto const parser = UPtr<Json::CharReader>(parserBuilder.newCharReader());
OX_ALLOW_UNSAFE_BUFFERS_BEGIN
		if (!parser->parse(buff.data(), buff.data() + buff.size(), &doc, nullptr)) {
OX_ALLOW_UNSAFE_BUFFERS_END
			return Error{1, "Could not parse JSON"};
		}
		OrganicClawReader reader(buff.data(), buff.size());
		ModelHandlerInterface handler(reader);
		return model(&handler, &val);
	} catch (Error const &err) {
		return err;
	} catch (...) {
		return Error{1, "Unknown Error"};
	}
}

template<typename T>
Result<T> readOC(BufferView buff) noexcept {
	Result<T> val;
	OX_RETURN_ERROR(readOC(buff, val.value));
	return val;
}

template<typename T>
Result<T> readOC(StringViewCR json) noexcept {
	return readOC<T>(BufferView{json.data(), json.size()});
}

}
