//-----------------------------------------------------------------------------
// Project     : VST SDK
// Flags       : clang-format SMTGSequencer
//
// Category    :
// Filename    : public.sdk/source/vst/moduleinfo/jsoncxx.h
// Created by  : Steinberg, 12/2021
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------

#pragma once

#include "json.h"
#include <cassert>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>
#include <variant>

#if defined(_MSC_VER) || __has_include(<charconv>)
#include <charconv>
#define SMTG_HAS_CHARCONV
#endif

//------------------------------------------------------------------------
namespace JSON {
namespace Detail {

//------------------------------------------------------------------------
template <typename JsonT>
struct Base
{
	explicit Base (JsonT* o) : object_ (o) {}
	explicit Base (const Base& o) : object_ (o.object_) {}

	Base& operator= (const Base& o) = default;

	operator JsonT* () const { return object_; }
	JsonT* jsonValue () const { return object_; }

protected:
	Base () : object_ (nullptr) {}

	JsonT* object_;
};

//------------------------------------------------------------------------
template <typename JsonElement>
struct Iterator
{
	explicit Iterator (JsonElement el) : el (el) {}

	bool operator== (const Iterator& other) const { return other.el == el; }
	bool operator!= (const Iterator& other) const { return other.el != el; }

	const JsonElement& operator* () const { return el; }
	const JsonElement& operator-> () const { return el; }

	Iterator& operator++ ()
	{
		if (el)
			el = el.next ();
		return *this;
	}

	Iterator operator++ (int)
	{
		auto it = Iterator (el);
		operator++ ();
		return it;
	}

private:
	JsonElement el;
};

//------------------------------------------------------------------------
} // Detail

struct Object;
struct Array;
struct String;
struct Number;
struct Boolean;

//------------------------------------------------------------------------
enum class Type
{
	Object,
	Array,
	String,
	Number,
	True,
	False,
	Null,
};

//------------------------------------------------------------------------
struct SourceLocation
{
	size_t offset;
	size_t line;
	size_t row;
};

//------------------------------------------------------------------------
struct Value : Detail::Base<json_value_s>
{
	using Detail::Base<json_value_s>::Base;
	using VariantT = std::variant<Object, Array, String, Number, Boolean, std::nullptr_t>;

	std::optional<Object> asObject () const;
	std::optional<Array> asArray () const;
	std::optional<String> asString () const;
	std::optional<Number> asNumber () const;
	std::optional<Boolean> asBoolean () const;
	std::optional<std::nullptr_t> asNull () const;

	VariantT asVariant () const;
	Type type () const;

	SourceLocation getSourceLocation () const;
};

//------------------------------------------------------------------------
struct Boolean
{
	Boolean (size_t type) : value (type == json_type_true) {}

	operator bool () const { return value; }

private:
	bool value;
};

//------------------------------------------------------------------------
struct String : Detail::Base<json_string_s>
{
	using Detail::Base<json_string_s>::Base;

	std::string_view text () const { return {jsonValue ()->string, jsonValue ()->string_size}; }

	SourceLocation getSourceLocation () const;
};

//------------------------------------------------------------------------
struct Number : Detail::Base<json_number_s>
{
	using Detail::Base<json_number_s>::Base;

	std::string_view text () const { return {jsonValue ()->number, jsonValue ()->number_size}; }

	std::optional<int64_t> getInteger () const;
	std::optional<double> getDouble () const;
};

//------------------------------------------------------------------------
struct ObjectElement : Detail::Base<json_object_element_s>
{
	using Detail::Base<json_object_element_s>::Base;

	String name () const { return String (jsonValue ()->name); }
	Value value () const { return Value (jsonValue ()->value); }

	ObjectElement next () const { return ObjectElement (jsonValue ()->next); }
};

//------------------------------------------------------------------------
struct Object : Detail::Base<json_object_s>
{
	using Detail::Base<json_object_s>::Base;
	using Iterator = Detail::Iterator<ObjectElement>;

	size_t size () const { return jsonValue ()->length; }

	Iterator begin () const { return Iterator (ObjectElement (jsonValue ()->start)); }
	Iterator end () const { return Iterator (ObjectElement (nullptr)); }
};

//------------------------------------------------------------------------
struct ArrayElement : Detail::Base<json_array_element_s>
{
	using Detail::Base<json_array_element_s>::Base;

	Value value () const { return Value (jsonValue ()->value); }

	ArrayElement next () const { return ArrayElement (jsonValue ()->next); }
};

//------------------------------------------------------------------------
struct Array : Detail::Base<json_array_s>
{
	using Detail::Base<json_array_s>::Base;
	using Iterator = Detail::Iterator<ArrayElement>;

	size_t size () const { return jsonValue ()->length; }

	Iterator begin () const { return Iterator (ArrayElement (jsonValue ()->start)); }
	Iterator end () const { return Iterator (ArrayElement (nullptr)); }
};

//------------------------------------------------------------------------
struct Document : Value
{
	static std::variant<Document, json_parse_result_s> parse (std::string_view data)
	{
		auto allocate = [] (void*, size_t allocSize) { return std::malloc (allocSize); };
		json_parse_result_s parse_result {};
		auto value = json_parse_ex (data.data (), data.size (),
		                            json_parse_flags_allow_json5 |
		                                json_parse_flags_allow_location_information,
		                            allocate, nullptr, &parse_result);
		if (value)
			return Document (value);
		return parse_result;
	}
	~Document () noexcept
	{
		if (object_)
			std::free (object_);
	}

	Document (Document&& doc) noexcept { *this = std::move (doc); }
	Document& operator= (Document&& doc) noexcept
	{
		std::swap (object_, doc.object_);
		return *this;
	}

private:
	using Value::Value;
};

//------------------------------------------------------------------------
inline std::optional<Object> Value::asObject () const
{
	if (type () != Type::Object)
		return {};
	return Object (json_value_as_object (jsonValue ()));
}

//------------------------------------------------------------------------
inline std::optional<Array> Value::asArray () const
{
	if (type () != Type::Array)
		return {};
	return Array (json_value_as_array (jsonValue ()));
}

//------------------------------------------------------------------------
inline std::optional<String> Value::asString () const
{
	if (type () != Type::String)
		return {};
	return String (json_value_as_string (jsonValue ()));
}

//------------------------------------------------------------------------
inline std::optional<Number> Value::asNumber () const
{
	if (type () != Type::Number)
		return {};
	return Number (json_value_as_number (jsonValue ()));
}

//------------------------------------------------------------------------
inline std::optional<Boolean> Value::asBoolean () const
{
	if (type () == Type::True || type () == Type::False)
		return Boolean (jsonValue ()->type);
	return {};
}

//------------------------------------------------------------------------
inline std::optional<std::nullptr_t> Value::asNull () const
{
	if (type () != Type::Null)
		return {};
	return nullptr;
}

//------------------------------------------------------------------------
inline Type Value::type () const
{
	switch (jsonValue ()->type)
	{
		case json_type_string: return Type::String;
		case json_type_number: return Type::Number;
		case json_type_object: return Type::Object;
		case json_type_array: return Type::Array;
		case json_type_true: return Type::True;
		case json_type_false: return Type::False;
		case json_type_null: return Type::Null;
	}
	assert (false);
	return Type::Null;
}

//------------------------------------------------------------------------
inline Value::VariantT Value::asVariant () const
{
	switch (type ())
	{
		case Type::String: return *asString ();
		case Type::Number: return *asNumber ();
		case Type::Object: return *asObject ();
		case Type::Array: return *asArray ();
		case Type::True: return *asBoolean ();
		case Type::False: return *asBoolean ();
		case Type::Null: return *asNull ();
	}
	assert (false);
	return nullptr;
}

//------------------------------------------------------------------------
inline SourceLocation Value::getSourceLocation () const
{
	auto exValue = reinterpret_cast<json_value_ex_s*> (jsonValue ());
	return {exValue->offset, exValue->line_no, exValue->row_no};
}

//------------------------------------------------------------------------
inline SourceLocation String::getSourceLocation () const
{
	auto exValue = reinterpret_cast<json_string_ex_s*> (jsonValue ());
	return {exValue->offset, exValue->line_no, exValue->row_no};
}

//------------------------------------------------------------------------
inline std::optional<int64_t> Number::getInteger () const
{
#if defined(SMTG_HAS_CHARCONV)
	int64_t result {0};
	auto res = std::from_chars (jsonValue ()->number,
	                            jsonValue ()->number + jsonValue ()->number_size, result);
	if (res.ec == std::errc ())
		return result;
	return {};
#else
	int64_t result {0};
	std::string str (jsonValue ()->number, jsonValue ()->number + jsonValue ()->number_size);
	if (std::sscanf (str.data (), "%lld", &result) != 1)
		return {};
	return result;
#endif
}

//------------------------------------------------------------------------
inline std::optional<double> Number::getDouble () const
{
#if 1 // clang still has no floting point from_chars version
	size_t ctrl {0};
	auto result = std::stod (std::string (jsonValue ()->number, jsonValue ()->number_size), &ctrl);
	if (ctrl > 0)
		return result;
#else
	double result {0.};
	auto res = std::from_chars (jsonValue ()->number,
	                            jsonValue ()->number + jsonValue ()->number_size, result);
	if (res.ec == std::errc ())
		return result;
#endif
	return {};
}

//------------------------------------------------------------------------
inline std::string_view errorToString (json_parse_error_e error)
{
	switch (error)
	{
		case json_parse_error_e::json_parse_error_none: return {};
		case json_parse_error_e::json_parse_error_expected_comma_or_closing_bracket:
			return "json_parse_error_expected_comma_or_closing_bracket";
		case json_parse_error_e::json_parse_error_expected_colon:
			return "json_parse_error_expected_colon";
		case json_parse_error_e::json_parse_error_expected_opening_quote:
			return "json_parse_error_expected_opening_quote";
		case json_parse_error_e::json_parse_error_invalid_string_escape_sequence:
			return "json_parse_error_invalid_string_escape_sequence";
		case json_parse_error_e::json_parse_error_invalid_number_format:
			return "json_parse_error_invalid_number_format";
		case json_parse_error_e::json_parse_error_invalid_value:
			return "json_parse_error_invalid_value";
		case json_parse_error_e::json_parse_error_premature_end_of_buffer:
			return "json_parse_error_premature_end_of_buffer";
		case json_parse_error_e::json_parse_error_invalid_string:
			return "json_parse_error_invalid_string";
		case json_parse_error_e::json_parse_error_allocator_failed:
			return "json_parse_error_allocator_failed";
		case json_parse_error_e::json_parse_error_unexpected_trailing_characters:
			return "json_parse_error_unexpected_trailing_characters";
		case json_parse_error_e::json_parse_error_unknown: return "json_parse_error_unknown";
	}
	return {};
}

//------------------------------------------------------------------------
} // JSON
