#include "json.h"
#include <iostream>
#include <algorithm>
#include <cmath>

namespace json {

JSON::operator std::string() const {
    if (type != String)
        throw JSONError("Not a JSON string");
    else
        return (*val.str);
}

JSON::operator double() const {
    if (type != Num)
        throw JSONError("Not a JSON number");
    else
        return val.num;
}

JSON::operator bool() const {
    if (type != Bool && type != Null)
        throw JSONError("Not a JSON bool");
    else if (type == Null)
        return false;
    else
        return val.b;
}

JSON& JSON::operator[] (int i) const {
    if (type == Array)
        return (*val.arr)[i];
    else
        throw JSONError("Not a JSON array");
}

JSON& JSON::operator[] (std::string s) const {
    if (type != Object)
        throw JSONError("Not a JSON object");
    else
        return (*val.obj)[s];
}

JSON& JSON::operator[] (const char* s) const {
    if (type != Object)
        throw JSONError("Not a JSON object");
    else
        return (*val.obj)[s];
}


JSON& JSON::operator[] (const JSON& idx) const {
    if (idx.type == String) {
        return (*this)[static_cast<std::string>(idx)];
    } else if (idx.type == Num) {
        return (*this)[static_cast<int>(std::round(static_cast<double>(idx)))];
    } else {
        throw JSONError("Not a subscriptable JSON value");
    }
}

std::string JSON::to_string() const {
    return static_cast<std::string>(*this);
}

double JSON::to_double() const {
    return static_cast<double>(*this);
}

bool JSON::to_bool() const {
    return static_cast<bool>(*this);
}

bool JSON::isNull() const {
    return type == Null;
}

std::size_t JSON::size() const {
    if (type == Object) {
        return val.obj->size();
    } else if (type == Array) {
        return val.arr->size();
    } else {
        throw JSONError("Not a JSON value with a size");
    }
}

void JSON::push_back(const JSON& other) {
    if (type == Array) {
        val.arr->push_back(other);
    } else {
        throw JSONError("Not a JSON array");
    }
}

void JSON::push_front(const JSON& other) {
    if (type == Array) {
        val.arr->push_front(other);
    } else {
        throw JSONError("Not a JSON array");
    }
}

Type JSON::getType() const {
    return type;
}

JSON JSON::num(double n) {
    Value v;
    v.num = n;
    return JSON(Num, v);
}

JSON JSON::string(const std::string& s) {
    Value v;
    v.str = new std::string(s);
    return JSON(String, v);
}

JSON JSON::object() {
    return JSON(Object);
}

JSON JSON::array() {
    return JSON(Array);
}

JSON JSON::boolean(bool b) {
    Value v;
    v.b = b;
    return JSON(Bool, v);
}

JSON JSON::null() {
    return JSON(Null);
}

JSON::JSON(Type t) : type(t)
{
    if (t == Array)
        val.arr = new std::deque<JSON>();
    else if (t == Object)
        val.obj = new std::map<std::string, JSON>();
    else if (t == String)
        val.str = new std::string();
}

JSON::JSON(Type t, Value v) : type(t), val(v)
{}

JSON::JSON() : type(Null)
{}

JSON::JSON(const JSON& other) : type(other.type), val(other.val) {
    if (type == String)
        val.str = new std::string(*other.val.str);
    else if (type == Object)
        val.obj = new std::map<std::string, JSON>(*other.val.obj);
    else if (type == Array)
        val.arr = new std::deque<JSON>(*other.val.arr);
}

JSON::JSON(JSON&& other) : type(other.type), val(other.val) {
    if (type == String)
        other.val.str = nullptr;
    else if (type == Object)
        other.val.obj = nullptr;
    else if (type == Array)
        other.val.arr = nullptr;
}

JSON& JSON::operator=(const JSON& other) {
    if (type == String)
        delete val.str;
    else if (type == Object)
        delete val.obj;
    else if (type == Array)
        delete val.arr;

    type = other.type;
    if (type == String)
        val.str = new std::string(*other.val.str);
    else if (type == Object)
        val.obj = new std::map<std::string, JSON>(*other.val.obj);
    else if (type == Array)
        val.arr = new std::deque<JSON>(*other.val.arr);
    else
        val = other.val;
    return *this;
}

JSON& JSON::operator=(JSON&& other) {
    if (type == String)
        delete val.str;
    else if (type == Object)
        delete val.obj;
    else if (type == Array)
        delete val.arr;

    type = other.type;
    val = other.val;
    if (type == String)
        other.val.str = nullptr;
    else if (type == Object)
        other.val.obj = nullptr;
    else if (type == Array)
        other.val.arr = nullptr;
    return *this;
}

JSON::~JSON() {
    if (type == String)
        delete val.str;
    else if (type == Object)
        delete val.obj;
    else if (type == Array)
        delete val.arr;
}

std::ostream& operator<<(std::ostream& os, const JSON& j) {
    switch (j.type) {
        case Num:
            os << j.val.num;
            break;
        case String:
            os << '"' << *j.val.str << '"';
            break;
        case Object:
            os << '{';
            {
                bool first = true;
                for (const auto& p : *j.val.obj) {
                    if (!first)
                        os << ", ";
                    first = false;
                    os << JSON::string(p.first)
                        << ": " << p.second;
                }
            }
            os << '}';
            break;
        case Array:
            os << '[';
            {
                bool first = true;
                for (const auto& e : *j.val.arr) {
                    if (!first)
                        os << ", ";
                    first = false;
                    os << e;
                }
            }
            os << ']';
            break;
        case Bool:
            if (j.val.b) {
                os << "true";
            } else {
                os << "false";
            }
            break;
        case Null:
            os << "null";
            break;
    }
    return os;
}

}