#ifndef HM_JSON_VALUE_H
#define HM_JSON_VALUE_H

#define IS_TYPE(type_a, type_b) std::is_same<type_a, type_b>::value
#define GET_VALUE(type, pointer) *((type *) pointer)
#define DIFFERENT_TYPE_ERR(type_name) std::logic_error("Error: expected type is <" + type_name + ">")

#include <string>
#include <vector>
#include <map>
#include <variant>
#include <stdexcept>

namespace json {
    class Value;

    enum TYPE {
        NullType, BoolType, IntType, DecimalType, StringType, ListType, DictType
    };
    std::string const _typeIdToName[] = {"Null", "Bool", "Int", "Decimal", "String", "List", "Dict"};  // NOLINT

    using null_type = char;
    using bool_type = bool;
    using int_type = long;
    using decimal_type = double;
    using string_type = std::string;
    using list_type = std::vector<Value>;
    using dict_type = std::map<std::string, Value>;

    class Value {
    public:
        Value();

        explicit Value(bool_type const &value);

        explicit Value(int const &value);

        explicit Value(int_type const &value);

        explicit Value(decimal_type const &value);

        explicit Value(string_type const &value);

        explicit Value(const char *value);

        explicit Value(list_type const &value);

        explicit Value(dict_type const &value);

        template<class T>
        [[nodiscard]] T &value();

        [[nodiscard]] constexpr TYPE type_id() const;

        [[nodiscard]] std::string type_name() const;

        [[nodiscard]] constexpr bool is_basic() const;

        [[nodiscard]] std::string str(bool const &format_need = false, int const &indent = 2, int layer = 1);

        void push_back(Value const &value);

        void pop_back();

        void erase(int const &index);

        void erase(std::string const &key);

        Value &operator[](int const &index);

        Value &operator[](std::string const &key);

    private:
        // null: string
        using data_type = std::variant<bool_type, int_type, decimal_type, string_type, list_type, dict_type>;
        TYPE _type;
        data_type _data;

        void *_value();
    };

    template<class T>
    T &Value::value() {
        if constexpr (IS_TYPE(T, null_type)) {
            if (_type != NullType) throw DIFFERENT_TYPE_ERR(_typeIdToName[NullType]);
        } else if constexpr (IS_TYPE(T, bool_type)) {
            if (_type != BoolType) throw DIFFERENT_TYPE_ERR(_typeIdToName[BoolType]);
        } else if constexpr (IS_TYPE(T, int_type)) {
            if (_type != IntType) throw DIFFERENT_TYPE_ERR(_typeIdToName[IntType]);
        } else if constexpr (IS_TYPE(T, decimal_type)) {
            if (_type != DecimalType) throw DIFFERENT_TYPE_ERR(_typeIdToName[DecimalType]);
        } else if constexpr (IS_TYPE(T, string_type)) {
            if (_type != StringType) throw DIFFERENT_TYPE_ERR(_typeIdToName[StringType]);
        } else if constexpr (IS_TYPE(T, list_type)) {
            if (_type != ListType) throw DIFFERENT_TYPE_ERR(_typeIdToName[ListType]);
        } else if constexpr (IS_TYPE(T, dict_type)) {
            if (_type != DictType) throw DIFFERENT_TYPE_ERR(_typeIdToName[DictType]);
        }

        return *((T *) _value());
    }
}

#endif //HM_JSON_VALUE_H