#include <sstream>
#include "value.h"

namespace json {
    Value::Value() {
        _type = NullType;
        _data = "null";
    }

    Value::Value(bool_type const &value) {
        _type = BoolType;
        _data = value;
    }

    Value::Value(int const &value) {
        _type = IntType;
        _data = (int_type)value;
    }

    Value::Value(int_type const &value) {
        _type = IntType;
        _data = value;
    }

    Value::Value(decimal_type const &value) {
        _type = DecimalType;
        _data = value;
    }

    Value::Value(string_type const &value) {
        _type = StringType;
        _data = value;
    }

    Value::Value(const char *value) {
        _type = StringType;
        _data = std::string(value);
    }

    Value::Value(list_type const &value) {
        _type = ListType;
        _data = value;
    }

    Value::Value(dict_type const &value) {
        _type = DictType;
        _data = value;
    }

    void *Value::_value() {
        switch (_type) {
            case NullType:
                // null: value<string>, type_id<char>, variant has no <char> in it.
                return std::get_if<string_type>(&_data);
            case BoolType:
                return std::get_if<bool_type>(&_data);
            case IntType:
                return std::get_if<int_type>(&_data);
            case DecimalType:
                return std::get_if<decimal_type>(&_data);
            case StringType:
                return std::get_if<string_type>(&_data);
            case ListType:
                return std::get_if<list_type>(&_data);
            case DictType:
                return std::get_if<dict_type>(&_data);
            default:
                // will not be here
                throw std::logic_error("Got an unrecognized type_id");
        }
    }

    constexpr TYPE Value::type_id() const {
        return _type;
    }

    std::string Value::type_name() const {
        return _typeIdToName[_type];
    }

    constexpr bool Value::is_basic() const {
        return _type == NullType ||
               _type == BoolType ||
               _type == IntType ||
               _type == DecimalType ||
               _type == StringType;
    }

    std::string Value::str(bool const &format_need, int const &indent, int layer) {
        void *v = _value();
        std::ostringstream buff;

        switch (_type) {
            case NullType:
                buff << "null";
                break;
            case BoolType:
                if (GET_VALUE(bool_type, v)) buff << "true";
                else buff << "false";
                break;
            case IntType:
                buff << GET_VALUE(int_type, v);
                break;
            case DecimalType:
                buff << GET_VALUE(decimal_type, v);
                break;
            case StringType:
                buff << '"' << GET_VALUE(string_type, v) << '"';
                break;
            case ListType: {
                list_type &list = GET_VALUE(list_type, v);
                buff << '[';
                bool first = true;
                for (Value &item: list) {
                    if (first) {
                        first = false;
                        if (format_need) buff << item.str(format_need, indent, layer + 1);
                        else buff << item.str();
                    } else {
                        if (format_need) buff << ", " << item.str(format_need, indent, layer + 1);
                        else buff << ',' << item.str();
                    }
                }
                buff << ']';
                break;
            }
            case DictType: {
                dict_type &dict = GET_VALUE(dict_type, v);
                if (format_need) {
                    buff << "{\n";
                    for (auto it = dict.begin(); it != dict.end(); ++it) {
                        if (it != dict.begin()) buff << ",\n";
                        for (int i = 0; i < layer * indent; ++i) buff << ' ';
                        buff << '"' << it->first << "\": " << it->second.str(format_need, indent, layer + 1);
                    }
                    buff << '\n';
                    for (int i = 0; i < (layer - 1) * indent; ++i) buff << ' ';
                    buff << '}';
                } else {
                    buff << '{';
                    for (auto it = dict.begin(); it != dict.end(); it++) {
                        if (it != dict.begin()) buff << ',';
                        buff << '"' << it->first << "\":" << it->second.str();
                    }
                    buff << '}';
                }
                break;
            }
            default:
                // will not be here
                throw std::logic_error("Got an unrecognized type_id");
        }
        return buff.str();
    }

    void Value::push_back(Value const &val) {
        auto &list = value<list_type>();
        list.push_back(val);
    }

    void Value::pop_back() {
        auto &list = value<list_type>();
        list.pop_back();
    }

    void Value::erase(int const &index) {
        auto &list = value<list_type>();
        list.erase(list.begin() + index);
    }

    void Value::erase(std::string const &key) {
        auto &dict = value<dict_type>();
        if (dict.find(key) != dict.end()) dict.erase(key);
    }

    Value &Value::operator[](int const &index) {
        auto &list = value<list_type>();
        return list.at(index);
    }

    Value &Value::operator[](std::string const &key) {
        auto &dict = value<dict_type>();
        return dict[key];
    }
}