#include "JSON.h"

#include "Datei.h"

using namespace Framework;
using namespace JSON;

#pragma region JSONValue

JSONValue::JSONValue()
    : ReferenceCounter()
{
    this->type = AbstractType::NULL_;
}

JSONValue::~JSONValue() {}

JSONValue::JSONValue(AbstractType type)
    : ReferenceCounter()
{
    this->type = type;
}

AbstractType JSONValue::getType() const
{
    return type;
}

Text JSONValue::toString() const
{
    return Text("null");
}

JSONValue* JSONValue::clone() const
{
    return new JSONValue();
}

JSONBool* JSONValue::asBool() const
{
    return (JSONBool*)this;
}

JSONNumber* JSONValue::asNumber() const
{
    return (JSONNumber*)this;
}

JSONString* JSONValue::asString() const
{
    return (JSONString*)this;
}

JSONArray* JSONValue::asArray() const
{
    return (JSONArray*)this;
}

JSONObject* JSONValue::asObject() const
{
    return (JSONObject*)this;
}

const AbstractBool* Framework::JSON::JSONValue::asAbstractBool() const
{
    return dynamic_cast<const AbstractBool*>(this);
}

const AbstractNumber* Framework::JSON::JSONValue::asAbstractNumber() const
{
    return dynamic_cast<const AbstractNumber*>(this);
}

const AbstractString* Framework::JSON::JSONValue::asAbstractString() const
{
    return dynamic_cast<const AbstractString*>(this);
}

const AbstractArray* Framework::JSON::JSONValue::asAbstractArray() const
{
    return dynamic_cast<const AbstractArray*>(this);
}

const AbstractObject* Framework::JSON::JSONValue::asAbstractObject() const
{
    return dynamic_cast<const AbstractObject*>(this);
}

#pragma endregion Cotent

#pragma region JSONBool

JSONBool::JSONBool(bool b)
    : JSONValue(AbstractType::BOOLEAN)
{
    this->b = b;
}

bool JSONBool::getBool() const
{
    return b;
}

Text JSONBool::toString() const
{
    if (b)
        return Text("true");
    else
        return Text("false");
}

JSONValue* JSONBool::clone() const
{
    return new JSONBool(b);
}

#pragma endregion Cotent

#pragma region JSONNumber

JSONNumber::JSONNumber(double num)
    : JSONValue(AbstractType::NUMBER)
{
    number = num;
}

double JSONNumber::getNumber() const
{
    return number;
}

Text JSONNumber::toString() const
{
    return Text(number);
}

JSONValue* JSONNumber::clone() const
{
    return new JSONNumber(number);
}

#pragma endregion Cotent

#pragma region JSONString

JSONString::JSONString(Text string)
    : JSONValue(AbstractType::STRING)
{
    this->string = string;
    this->string.ersetzen("\\\"", "\"");
    this->string.ersetzen("\\n", "\n");
}

Text JSONString::getString() const
{
    return string;
}

Text JSONString::toString() const
{
    Text esc = string;
    esc.ersetzen("\"", "\\\"");
    esc.ersetzen("\n", "\\n");
    return Text(Text("\"") += esc.getText()) += "\"";
}

JSONValue* JSONString::clone() const
{
    Text esc = string;
    esc.ersetzen("\"", "\\\"");
    esc.ersetzen("\n", "\\n");
    return new JSONString(esc);
}

#pragma endregion Cotent

#pragma region JSONArray

JSONArray::JSONArray()
    : JSONValue(AbstractType::ARRAY)
{
    array = new RCArray<JSONValue>();
}

JSONArray::JSONArray(Text string)
    : JSONValue(AbstractType::ARRAY)
{
    array = new RCArray<JSONValue>();
    string = Parser::removeWhitespace(string);
    if (string.getText()[0] == '['
        && string.getText()[string.getLength() - 1] == ']')
    {
        string.remove(0, 1);
        string.remove(string.getLength() - 1, string.getLength());
        while (string.getLength())
        {
            int end = Parser::findObjectEndInArray(string);
            Text* objStr = string.getTeilText(0, end);
            string.remove(0, end + 1);
            array->add(Parser::getValue(*objStr));
            objStr->release();
        }
    }
}

JSONArray::JSONArray(const JSONArray& arr)
    : JSONValue(AbstractType::ARRAY)
{
    array = dynamic_cast<RCArray<JSONValue>*>(arr.array->getThis());
}

JSONArray::~JSONArray()
{
    array->release();
}

JSONArray& JSONArray::operator=(const JSONArray& arr)
{
    array->release();
    array = dynamic_cast<RCArray<JSONValue>*>(arr.array->getThis());
    return *this;
}

void JSONArray::addValue(JSONValue* value)
{
    array->add(value);
}

void JSONArray::setValue(int i, JSONValue* value)
{
    array->set(value, i);
}

void JSONArray::removeValue(int i)
{
    array->remove(i);
}

JSONValue* JSONArray::getValue(int i) const
{
    return array->get(i);
}

JSONValue* JSONArray::zValue(int i) const
{
    return array->z(i);
}

AbstractElement* JSONArray::zAbstractValue(int i) const
{
    return zValue(i);
}

int JSONArray::getLength() const
{
    return array->getEintragAnzahl();
}

bool JSONArray::isValueOfType(int i, AbstractType type) const
{
    return i >= 0 && i < array->getEintragAnzahl()
        && array->z(i)->getType() == type;
}

ArrayIterator<JSONValue*> JSONArray::begin() const
{
    return array->begin();
}

ArrayIterator<JSONValue*> JSONArray::end() const
{
    return array->end();
}

Text JSONArray::toString() const
{
    Text str = "[";
    for (auto i = array->begin(); i; i++)
    {
        str += i->toString();
        if (i.hasNext()) str += ",";
    }
    str += "]";
    return str;
}

JSONValue* JSONArray::clone() const
{
    JSONArray* arr = new JSONArray();
    for (JSONValue* value : *this)
    {
        arr->addValue(value->clone());
    }
    return arr;
}

#pragma endregion Cotent

#pragma region JSONObject

JSONObject::JSONObject()
    : JSONValue(AbstractType::OBJECT)
{
    fields = new Array<Text>();
    values = new RCArray<JSONValue>();
}

JSONObject::JSONObject(Text string)
    : JSONValue(AbstractType::OBJECT)
{
    fields = new Array<Text>();
    values = new RCArray<JSONValue>();
    string = Parser::removeWhitespace(string);
    if (string.getText()[0] == '{'
        && string.getText()[string.getLength() - 1] == '}')
    {
        string.remove(0, 1);
        string.remove(string.getLength() - 1, string.getLength());
        while (string.getLength())
        {
            int endField = Parser::findFieldEndInObject(string);
            Text* fieldName = string.getTeilText(0, endField);
            string.remove(0, endField + 1);
            fieldName->remove(0, 1);
            fieldName->remove(
                fieldName->getLength() - 1, fieldName->getLength());
            int endValue = Parser::findValueEndInObject(string);
            Text* value = string.getTeilText(0, endValue);
            string.remove(0, endValue + 1);
            fields->add(Text(fieldName->getText()));
            values->add(Parser::getValue(*value));
            fieldName->release();
            value->release();
        }
    }
}

JSONObject::JSONObject(const JSONObject& obj)
    : JSONValue(AbstractType::OBJECT)
{
    fields = dynamic_cast<Array<Text>*>(obj.fields->getThis());
    values = dynamic_cast<RCArray<JSONValue>*>(obj.values->getThis());
}

JSONObject::~JSONObject()
{
    fields->release();
    values->release();
}

JSONObject& JSONObject::operator=(const JSONObject& obj)
{
    fields->release();
    values->release();
    fields = dynamic_cast<Array<Text>*>(obj.fields->getThis());
    values = dynamic_cast<RCArray<JSONValue>*>(obj.values->getThis());
    return *this;
}

bool JSONObject::addValue(Text field, JSONValue* value)
{
    if (hasValue(field)) return 0;
    fields->add(field);
    values->add(value);
    return 1;
}

bool JSONObject::removeValue(Text field)
{
    for (int i = 0; i < fields->getEintragAnzahl(); i++)
    {
        if (fields->get(i).istGleich(field))
        {
            fields->remove(i);
            values->remove(i);
            return 1;
        }
    }
    return 0;
}

bool JSONObject::hasValue(Text field) const
{
    return zValue(field);
}

JSONValue* JSONObject::getValue(Text field) const
{
    for (int i = 0; i < fields->getEintragAnzahl(); i++)
    {
        if (fields->get(i).istGleich(field)) return values->get(i);
    }
    return 0;
}

JSONValue* JSONObject::zValue(Text field) const
{
    for (int i = 0; i < fields->getEintragAnzahl(); i++)
    {
        if (fields->get(i).istGleich(field)) return values->z(i);
    }
    return 0;
}

AbstractElement* JSONObject::zAbstractValue(Text field) const
{
    return zValue(field);
}

ArrayIterator<Text> JSONObject::getFields()
{
    return fields->begin();
}

ArrayIterator<JSONValue*> JSONObject::getValues()
{
    return values->begin();
}

Text Framework::JSON::JSONObject::getFieldKey(int i) const
{
    return fields->get(i);
}

AbstractElement* Framework::JSON::JSONObject::zAbstractValue(int i) const
{
    return values->z(i);
}

int JSONObject::getFieldCount() const
{
    return fields->getEintragAnzahl();
}

bool JSONObject::isValueOfType(Text field, AbstractType type) const
{
    for (int i = 0; i < fields->getEintragAnzahl(); i++)
    {
        if (fields->get(i).istGleich(field))
            return values->z(i)->getType() == type;
    }
    return 0;
}

Text JSONObject::toString() const
{
    Text str = "{";
    ArrayIterator<Text> k = fields->begin();
    for (auto v = values->begin(); k && v; k++, v++)
    {
        str += "\"";
        str += k._.getText();
        str += "\":";
        str += v->toString().getText();
        if (v.hasNext()) str += ",";
    }
    str += "}";
    return str;
}

JSONValue* JSONObject::clone() const
{
    JSONObject* obj = new JSONObject();
    auto field = fields->begin();
    auto value = values->begin();
    for (; field && value; field++, value++)
    {
        obj->addValue(field.val(), value->clone());
    }
    return obj;
}

#pragma endregion Cotent

JSONValue* JSON::loadJSONFromFile(Text path)
{
    Datei d;
    d.setDatei(path);
    if (!d.open(Datei::Style::lesen))
    {
        return new JSONValue();
    }
    int size = (int)d.getSize();
    char* buffer = new char[size + 1];
    buffer[size] = 0;
    d.lese(buffer, size);
    d.close();
    JSONValue* result = Parser::getValue(buffer);
    delete[] buffer;
    return result;
}

#pragma region Parser

int Parser::findObjectEndInArray(const char* str)
{
    return findValueEndInObject(str);
}

Text Parser::removeWhitespace(const char* str)
{
    int i = 0;
    bool esc = 0;
    bool strO = 0;
    int l = 0;
    for (; str[i]; i++)
    {
        switch (str[i])
        {
        case '\\':
            if (strO)
                esc = !esc;
            else
                esc = 0;
            l++;
            break;
        case '"':
            if (!esc) strO = !strO;
            esc = 0;
            l++;
            break;
        case ' ':
        case '\n':
        case '\t':
        case '\r':
            if (strO) l++;
            esc = 0;
            break;
        default:
            esc = 0;
            l++;
            break;
        }
    }
    char* result = new char[l + 1];
    i = 0;
    esc = 0;
    strO = 0;
    l = 0;
    for (; str[i]; i++)
    {
        switch (str[i])
        {
        case '\\':
            if (strO)
                esc = !esc;
            else
                esc = 0;
            result[l++] = str[i];
            break;
        case '"':
            if (!esc) strO = !strO;
            esc = 0;
            result[l++] = str[i];
            break;
        case ' ':
        case '\n':
        case '\t':
        case '\r':
            if (strO) result[l++] = str[i];
            esc = 0;
            break;
        default:
            result[l++] = str[i];
            esc = 0;
            break;
        }
    }
    result[l] = 0;
    return Text::fromArray(result, l);
}

JSONValue* Parser::getValue(const char* str)
{
    Text string = Parser::removeWhitespace(str);
    if (string.istGleich("true")) return new JSONBool(1);
    if (string.istGleich("false")) return new JSONBool(0);
    if (string.getText()[0] == '"')
    {
        string.remove(0, 1);
        string.remove(string.getLength() - 1, string.getLength());
        return new JSONString(string);
    }
    if (string.getText()[0] == '[') return new JSONArray(string);
    if (string.getText()[0] == '{') return new JSONObject(string);
    if (Text((int)string).istGleich(string.getText()))
        return new JSONNumber((double)string);
    if (string.anzahlVon('.') == 1)
    {
        bool isNumber = 1;
        for (const char* c = (*string.getText() == '-') ? string.getText() + 1
                                                        : string.getText();
            *c;
            c++)
            isNumber &= (*c >= '0' && *c <= '9') || *c == '.';
        if (isNumber) return new JSONNumber((double)string);
    }
    return new JSONValue();
}

int Parser::findFieldEndInObject(const char* str)
{
    int i = 0;
    bool esc = 0;
    bool strO = 0;
    int objOc = 0;
    int arrayOc = 0;
    for (; str[i]; i++)
    {
        switch (str[i])
        {
        case '\\':
            if (strO)
                esc = !esc;
            else
                esc = 0;
            break;
        case '"':
            if (!esc) strO = !strO;
            esc = 0;
            break;
        case '[':
            if (!strO) arrayOc++;
            esc = 0;
            break;
        case ']':
            if (!strO) arrayOc--;
            esc = 0;
            break;
        case '{':
            if (!strO) objOc++;
            esc = 0;
            break;
        case '}':
            if (!strO) objOc--;
            esc = 0;
            break;
        case ':':
            if (!strO && objOc == 0 && arrayOc == 0) return i;
            esc = 0;
            break;
        default:
            esc = 0;
            break;
        }
    }
    return i;
}

int Parser::findValueEndInObject(const char* str)
{
    int i = 0;
    bool esc = 0;
    bool strO = 0;
    int objOc = 0;
    int arrayOc = 0;
    for (; str[i]; i++)
    {
        switch (str[i])
        {
        case '\\':
            if (strO)
                esc = !esc;
            else
                esc = 0;
            break;
        case '"':
            if (!esc) strO = !strO;
            esc = 0;
            break;
        case '[':
            if (!strO) arrayOc++;
            esc = 0;
            break;
        case ']':
            if (!strO) arrayOc--;
            esc = 0;
            break;
        case '{':
            if (!strO) objOc++;
            esc = 0;
            break;
        case '}':
            if (!strO) objOc--;
            esc = 0;
            break;
        case ',':
            if (!strO && objOc == 0 && arrayOc == 0) return i;
            esc = 0;
            break;
        default:
            esc = 0;
            break;
        }
    }
    return i;
}

#pragma endregion Cotent