#include "JSON.h"

#include "Datei.h"

using namespace Framework;
using namespace JSON;

#pragma region JSONValue

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

JSONValue::~JSONValue() {}

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

JSONType 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;
}

#pragma endregion Content

#pragma region JSONBool

JSONBool::JSONBool(bool b)
    : JSONValue(JSONType::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 Content

#pragma region JSONNumber

JSONNumber::JSONNumber(double num)
    : JSONValue(JSONType::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 Content

#pragma region JSONString

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

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

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

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

#pragma endregion Content

#pragma region JSONArray

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

JSONArray::JSONArray(Text string)
    : JSONValue(JSONType::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(JSONType::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);
}

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

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

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

Iterator<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
{
    return new JSONArray(toString());
}

#pragma endregion Content

#pragma region JSONObject

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

JSONObject::JSONObject(Text string)
    : JSONValue(JSONType::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(JSONType::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)
{
    for (int i = 0; i < fields->getEintragAnzahl(); i++)
    {
        if (fields->get(i).istGleich(field)) return 1;
    }
    return 0;
}

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

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

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

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

bool JSONObject::isValueOfType(Text field, JSONType 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 = "{";
    Iterator<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
{
    return new JSONObject(toString());
}

#pragma endregion Content

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 wsc = 0;
    int i = 0;
    bool esc = 0;
    bool strO = 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 ' ':
        case '\n':
        case '\t':
        case '\r':
            if (!strO) wsc++;
            esc = 0;
            break;
        default:
            esc = 0;
            break;
        }
    }
    Text ret;
    i = 0;
    esc = 0;
    strO = 0;
    for (; str[i]; i++)
    {
        switch (str[i])
        {
        case '\\':
            if (strO)
                esc = !esc;
            else
                esc = 0;
            ret.append(str[i]);
            break;
        case '"':
            if (!esc) strO = !strO;
            esc = 0;
            ret.append(str[i]);
            break;
        case ' ':
        case '\n':
        case '\t':
        case '\r':
            if (strO) ret.append(str[i]);
            esc = 0;
            break;
        default:
            ret.append(str[i]);
            esc = 0;
            break;
        }
    }
    return ret;
}

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 Content

using namespace Validator;

#pragma region JSONValidationResult

JSONValidationResult::JSONValidationResult()
    : ReferenceCounter()
{}

JSONValidationResult::~JSONValidationResult() {}

#pragma region JSONTypeMissmatch

JSONTypeMissmatch::JSONTypeMissmatch(Text path,
    JSONValue* foundValue,
    XML::Element* expected,
    JSONValidationResult* reason)
    : JSONValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
    this->expected = expected;
    this->reason = reason;
}

JSONTypeMissmatch::~JSONTypeMissmatch()
{
    foundValue->release();
    expected->release();
    if (reason) reason->release();
}

bool JSONTypeMissmatch::isValid() const
{
    return 0;
}

void JSONTypeMissmatch::printInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    std::cout << ind.getText() << "Type missmatch at path '" << path.getText()
              << "'. JSON Value\n"
              << ind.getText() << foundValue->toString().getText() << "\n"
              << ind.getText() << "does not match type\n"
              << ind.getText() << expected->toString().getText() << "\n";
    if (reason)
    {
        std::cout << ind.getText() << "Reason for type mismatch:\n";
        reason->printInvalidInfo(indent + 4);
    }
}

JSONValue* JSONTypeMissmatch::getValidPart() const
{
    if (reason)
    {
        JSONValue* valid = reason->getValidPart();
        Text* p = reason->getPath().getTeilText(path.getLength());
        if (foundValue->getType() == JSONType::ARRAY)
        {
            if (!valid
                && (!expected->hasAttribute("removeInvalidEntries")
                    || !expected->getAttributeValue("removeInvalidEntries")
                            .istGleich("true")))
            {
                p->release();
                return 0;
            }
            if (p->hatAt(0, "[") && p->hatAt(p->getLength() - 1, "]"))
            {
                Text* it = p->getTeilText(1, p->getLength() - 1);
                int i = (int)*it;
                it->release();
                if (i >= 0 && foundValue->asArray()->getLength() > i)
                {
                    JSONValue* tmp = foundValue->clone();
                    if (valid)
                        tmp->asArray()->setValue(i, valid);
                    else
                        tmp->asArray()->removeValue(i);
                    JSONValidationResult* res = JSONValidator(
                        dynamic_cast<XML::Element*>(expected->getThis()))
                                                    .validate(tmp);
                    if (res->isValid())
                    {
                        res->release();
                        p->release();
                        return tmp;
                    }
                    else if (res->isDifferent(this, path) || !valid)
                    {
                        p->release();
                        tmp->release();
                        JSONValue* result = res->getValidPart();
                        res->release();
                        return result;
                    }
                    tmp->release();
                    res->release();
                }
            }
        }
        else if (foundValue->getType() == JSONType::OBJECT)
        {
            if (!valid
                && (!expected->hasAttribute("removeInvalidEntries")
                    || !expected->getAttributeValue("removeInvalidEntries")
                            .istGleich("true")))
            {
                p->release();
                return 0;
            }
            if (p->hatAt(0, "."))
            {
                Text* at = p->getTeilText(1);
                Text attr = *at;
                at->release();
                JSONValue* tmp = foundValue->clone();
                tmp->asObject()->removeValue(attr);
                if (valid) tmp->asObject()->addValue(attr, valid);
                JSONValidationResult* res = JSONValidator(
                    dynamic_cast<XML::Element*>(expected->getThis()))
                                                .validate(tmp);
                if (res->isValid())
                {
                    res->release();
                    p->release();
                    return tmp;
                }
                else if (res->isDifferent(this, path) || !valid)
                {
                    p->release();
                    tmp->release();
                    JSONValue* result = res->getValidPart();
                    res->release();
                    return result;
                }
                tmp->release();
                res->release();
            }
        }
        p->release();
        if (valid) valid->release();
    }
    return 0;
}

Text JSONTypeMissmatch::getPath() const
{
    return path;
}

bool JSONTypeMissmatch::isDifferent(
    const JSONValidationResult* zResult, Text additionalPath) const
{
    const JSONTypeMissmatch* casted
        = dynamic_cast<const JSONTypeMissmatch*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(additionalPath + path)) return 1;
    return reason->isDifferent(casted->reason, path);
}

#pragma endregion Content

#pragma region JSONUnknownValue

JSONUnknownValue::JSONUnknownValue(Text path, JSONValue* foundValue)
    : JSONValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
}

JSONUnknownValue::~JSONUnknownValue()
{
    foundValue->release();
}

bool JSONUnknownValue::isValid() const
{
    return 0;
}

void JSONUnknownValue::printInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    std::cout << ind.getText() << "Unknown Value at '" << path.getText()
              << "'. Value found:\n"
              << ind.getText() << foundValue->toString().getText() << "\n";
}

JSONValue* JSONUnknownValue::getValidPart() const
{
    return 0;
}

Text JSONUnknownValue::getPath() const
{
    return path;
}

bool JSONUnknownValue::isDifferent(
    const JSONValidationResult* zResult, Text additionalPath) const
{
    const JSONUnknownValue* casted
        = dynamic_cast<const JSONUnknownValue*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(additionalPath + path)) return 1;
    return 0;
}

#pragma endregion Content

#pragma region JSONMissingValue

JSONMissingValue::JSONMissingValue(Text path, XML::Element* expected)
    : JSONValidationResult()
{
    this->path = path;
    this->expected = expected;
}

JSONMissingValue::~JSONMissingValue()
{
    expected->release();
}

bool JSONMissingValue::isValid() const
{
    return 0;
}

void JSONMissingValue::printInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    std::cout << ind.getText() << "Missing Value at '" << path.getText()
              << "'. Expected type:\n"
              << ind.getText() << expected->toString().getText() << "\n";
}

JSONValue* JSONMissingValue::getValidPart() const
{
    if (expected->hasAttribute("default"))
    {
        JSONValue* def
            = Parser::getValue(expected->getAttributeValue("default"));
        JSONValidationResult* res
            = JSONValidator(dynamic_cast<XML::Element*>(expected->getThis()))
                  .validate(def);
        if (res->isValid())
        {
            res->release();
            return def;
        }
        else if (res->isDifferent(this, path))
        {
            def->release();
            JSONValue* result = res->getValidPart();
            res->release();
            return result;
        }
        def->release();
    }
    return 0;
}

Text JSONMissingValue::getPath() const
{
    return path;
}

bool JSONMissingValue::isDifferent(
    const JSONValidationResult* zResult, Text additionalPath) const
{
    const JSONMissingValue* casted
        = dynamic_cast<const JSONMissingValue*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(additionalPath + path)) return 1;
    return 0;
}

#pragma endregion Content

#pragma region JSONMissingOneOf

JSONMissingOneOf::JSONMissingOneOf(Text path, XML::Editor expected)
    : JSONValidationResult()
{
    this->path = path;
    for (XML::Element* e : expected)
        this->expected.add(dynamic_cast<XML::Element*>(e->getThis()));
}

JSONMissingOneOf::~JSONMissingOneOf() {}

bool JSONMissingOneOf::isValid() const
{
    return 0;
}

void JSONMissingOneOf::printInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    std::cout << ind.getText() << "Missing Value at '" << path.getText()
              << "'. The value should have one of the following types:\n";
    ind += "    ";
    for (XML::Element* e : expected)
    {
        std::cout << ind.getText() << e->toString().getText() << "\n";
    }
}

JSONValue* JSONMissingOneOf::getValidPart() const
{
    // multiple possibilities are undecidable
    return 0;
}

Text JSONMissingOneOf::getPath() const
{
    return path;
}

bool JSONMissingOneOf::isDifferent(
    const JSONValidationResult* zResult, Text additionalPath) const
{
    const JSONMissingOneOf* casted
        = dynamic_cast<const JSONMissingOneOf*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(additionalPath + path)) return 1;
    return 0;
}

#pragma endregion Content

#pragma region JSONNoTypeMatching

JSONNoTypeMatching::JSONNoTypeMatching(Text path,
    JSONValue* foundValue,
    RCArray<XML::Element>& expected,
    RCArray<JSONValidationResult>& reasons)
    : JSONValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
    this->expected = expected;
    this->reasons = reasons;
}

JSONNoTypeMatching::~JSONNoTypeMatching()
{
    foundValue->release();
}

bool JSONNoTypeMatching::isValid() const
{
    return 0;
}

void JSONNoTypeMatching::printInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    std::cout << ind.getText() << "Found Value at '" << path.getText()
              << "' did not match any of the given possible types:\n";
    Text ind2 = ind + "    ";
    for (XML::Element* element : expected)
    {
        std::cout << ind2.getText() << element->toString().getText() << "\n";
    }

    std::cout << ind.getText() << "Reasons:\n";
    for (JSONValidationResult* reason : reasons)
    {
        reason->printInvalidInfo(indent + 4);
    }
}

JSONValue* JSONNoTypeMatching::getValidPart() const
{
    // multiple possibilities are undecidable
    return 0;
}

Text JSONNoTypeMatching::getPath() const
{
    return path;
}

bool JSONNoTypeMatching::isDifferent(
    const JSONValidationResult* zResult, Text additionalPath) const
{
    const JSONNoTypeMatching* casted
        = dynamic_cast<const JSONNoTypeMatching*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(additionalPath + path)) return 1;
    for (int i = 0; i < reasons.getEintragAnzahl(); i++)
    {
        if (reasons.z(i)->isDifferent(casted->reasons.z(i), additionalPath))
            return 1;
    }
    return 0;
}

#pragma endregion Content

#pragma region JSONValidValue

JSONValidValue::JSONValidValue(Text path, JSONValue* value)
    : JSONValidationResult()
{
    this->path = path;
    this->value = value;
}

JSONValidValue::~JSONValidValue()
{
    value->release();
}

bool JSONValidValue::isValid() const
{
    return 1;
}

void JSONValidValue::printInvalidInfo(int indent) const {}

JSONValue* JSONValidValue::getValidPart() const
{
    return value->clone();
}

Text JSONValidValue::getPath() const
{
    return path;
}

bool JSONValidValue::isDifferent(
    const JSONValidationResult* zResult, Text additionalPath) const
{
    const JSONValidValue* casted = dynamic_cast<const JSONValidValue*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(additionalPath + path)) return 1;
    return 0;
}

#pragma endregion Content

#pragma endregion Content

#pragma region JSONValidator

JSONValidator::JSONValidator(XML::Element* constraints)
    : ReferenceCounter(),
      constraints(constraints)
{}

JSONValidator::~JSONValidator()
{
    constraints->release();
}

JSONValidationResult* JSONValidator::validate(JSONValue* zValue) const
{
    return validate(zValue, constraints, "");
}

bool JSONValidator::isValid(JSONValue* zValue) const
{
    JSONValidationResult* res = validate(zValue);
    if (res->isValid())
    {
        res->release();
        return 1;
    }
    res->release();
    return 0;
}

JSONValue* JSONValidator::getValidParts(JSONValue* zValue) const
{
    JSONValidationResult* res = validate(zValue);
    if (res->isValid())
    {
        res->release();
        return zValue->clone();
    }
    JSONValue* valid = res->getValidPart();
    res->release();
    return valid;
}

XML::Element* JSONValidator::zConstraints()
{
    return constraints;
}

JSONValidationResult* JSONValidator::validate(
    JSONValue* zValue, XML::Element* zConstraints, Text path) const
{
    switch (zValue->getType())
    {
    case JSONType::NULL_:
        if (!zConstraints->hasAttribute("nullable")
            || !zConstraints->getAttributeValue("nullable").istGleich("true"))
        {
            return new JSONTypeMissmatch(path,
                dynamic_cast<JSONValue*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        break;
    case JSONType::BOOLEAN:
        if (!zConstraints->getName().istGleich("bool"))
        {
            return new JSONTypeMissmatch(path,
                dynamic_cast<JSONValue*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else if (zConstraints->hasAttribute("equals"))
        {
            if (!zConstraints->getAttributeValue("equals").istGleich("true")
                == !zValue->asBool()->getBool())
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
        }
        break;
    case JSONType::NUMBER:
        if (!zConstraints->getName().istGleich("number"))
        {
            return new JSONTypeMissmatch(path,
                dynamic_cast<JSONValue*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            if (zConstraints->hasAttribute("equals")
                && (double)zConstraints->getAttributeValue("equals")
                       != zValue->asNumber()->getNumber())
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("lessOrEqual")
                && zValue->asNumber()->getNumber()
                       > (double)zConstraints->getAttributeValue("lessOrEqual"))
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("greaterOrEqual")
                && zValue->asNumber()->getNumber()
                       < (double)zConstraints->getAttributeValue(
                           "greaterOrEqual"))
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("less")
                && zValue->asNumber()->getNumber()
                       >= (double)zConstraints->getAttributeValue("less"))
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("greater")
                && zValue->asNumber()->getNumber()
                       <= (double)zConstraints->getAttributeValue("greater"))
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
        }
        break;
    case JSONType::STRING:
        if (!zConstraints->getName().istGleich("string"))
        {
            return new JSONTypeMissmatch(path,
                dynamic_cast<JSONValue*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            if (zConstraints->hasAttribute("equals")
                && !zConstraints->getAttributeValue("equals").istGleich(
                    zValue->asString()->getString()))
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("contains")
                && zValue->asString()->getString().hat(
                    zConstraints->getAttributeValue("contains").getText()))
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("startsWith")
                && zValue->asString()->getString().positionVon(
                       zConstraints->getAttributeValue("startsWith").getText())
                       == 0)
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("endsWith")
                && zValue->asString()->getString().positionVon(
                       zConstraints->getAttributeValue("endsWith").getText())
                       == zValue->asString()->getString().getLength()
                              - zConstraints->getAttributeValue("endsWith")
                                    .getLength())
            {
                return new JSONTypeMissmatch(path,
                    dynamic_cast<JSONValue*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("oneOf"))
            {
                JSONArray* array
                    = Parser::getValue(zConstraints->getAttributeValue("oneOf"))
                          ->asArray();
                bool ok = 0;
                for (JSONValue* v : *array)
                {
                    if (v->asString()->getString().istGleich(
                            zValue->asString()->getString()))
                    {
                        ok = 1;
                        break;
                    }
                }
                array->release();
                if (!ok)
                {
                    return new JSONTypeMissmatch(path,
                        dynamic_cast<JSONValue*>(zValue->getThis()),
                        dynamic_cast<XML::Element*>(zConstraints->getThis()),
                        0);
                }
            }
        }
        break;
    case JSONType::ARRAY:
        if (!zConstraints->getName().istGleich("array"))
        {
            return new JSONTypeMissmatch(path,
                dynamic_cast<JSONValue*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            int index = 0;
            for (JSON::JSONValue* value : *zValue->asArray())
            {
                Text p = path;
                p += "[";
                p += index;
                p += "]";
                JSONValidationResult* res
                    = validateMultipleTypes(value, zConstraints, p);
                if (!res->isValid())
                {
                    return new JSONTypeMissmatch(path,
                        dynamic_cast<JSONValue*>(zValue->getThis()),
                        dynamic_cast<XML::Element*>(zConstraints->getThis()),
                        res);
                }
                res->release();
                index++;
            }
        }
        break;
    case JSONType::OBJECT:
        if (!zConstraints->getName().istGleich("object"))
        {
            return new JSONTypeMissmatch(path,
                dynamic_cast<JSONValue*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            JSON::JSONObject* obj = zValue->asObject();
            for (auto i = obj->getFields(); i; i++)
            {
                Text p = path;
                p += ".";
                p += i.val();
                if (!zConstraints->selectChildsByAttribute("name", i.val())
                         .exists())
                {
                    return new JSONTypeMissmatch(path,
                        dynamic_cast<JSONValue*>(zValue->getThis()),
                        dynamic_cast<XML::Element*>(zConstraints->getThis()),
                        new JSONUnknownValue(
                            p, zValue->asObject()->getValue(i.val())));
                }
                else
                {
                    JSONValidationResult* res = validateMultipleTypes(
                        zValue->asObject()->zValue(i.val()),
                        zConstraints->selectChildsByAttribute("name", i.val())
                            .begin()
                            .val(),
                        p);
                    if (!res->isValid())
                    {
                        return new JSONTypeMissmatch(path,
                            dynamic_cast<JSONValue*>(zValue->getThis()),
                            dynamic_cast<XML::Element*>(
                                zConstraints->getThis()),
                            res);
                    }
                    res->release();
                }
            }
            for (XML::Element* constraint : zConstraints->selectChildren())
            {
                if (!zValue->asObject()->hasValue(
                        constraint->getAttributeValue("name")))
                {
                    Text p = path;
                    p += ".";
                    p += constraint->getAttributeValue("name");
                    if (constraint->getChildCount() != 1)
                        return new JSONTypeMissmatch(path,
                            dynamic_cast<JSONValue*>(zValue->getThis()),
                            dynamic_cast<XML::Element*>(
                                zConstraints->getThis()),
                            new JSONMissingOneOf(
                                p, constraint->selectChildren()));
                    return new JSONTypeMissmatch(path,
                        dynamic_cast<JSONValue*>(zValue->getThis()),
                        dynamic_cast<XML::Element*>(zConstraints->getThis()),
                        new JSONMissingValue(p,
                            dynamic_cast<XML::Element*>(
                                constraint->selectChildren()
                                    .begin()
                                    .val()
                                    ->getThis())));
                }
            }
        }
        break;
    }
    return new JSONValidValue(
        path, dynamic_cast<JSONValue*>(zValue->getThis()));
}

JSONValidationResult* JSONValidator::validateMultipleTypes(
    JSONValue* zChildValue,
    XML::Element* zPossibleChildConstraints,
    Text childPath) const
{
    if (zPossibleChildConstraints->getChildCount() == 1)
        return validate(zChildValue,
            zPossibleChildConstraints->selectChildren().begin().val(),
            childPath); // only one type is possible
    bool hasTypeAttr = 0;
    RCArray<XML::Element> possibleConstraints;
    if (zPossibleChildConstraints->hasAttribute("typeSpecifiedBy"))
    { // try to find the correct constraints based on the type attribute
        hasTypeAttr = 1;
        Text typeAttr
            = zPossibleChildConstraints->getAttributeValue("typeSpecifiedBy");
        if (zChildValue->getType() == JSONType::OBJECT)
        {
            if (zChildValue->asObject()->hasValue(typeAttr))
            {
                JSONValue* typeV = zChildValue->asObject()->zValue(typeAttr);
                for (XML::Element* constraint :
                    zPossibleChildConstraints->selectChildsByName("object")
                        .whereChildWithAttributeExists("name", typeAttr))
                {
                    XML::Element* typeAttrContraints
                        = constraint->selectChildsByAttribute("name", typeAttr)
                              .begin()
                              .val();
                    JSONValidationResult* res = validateMultipleTypes(
                        typeV, typeAttrContraints, childPath + "." + typeAttr);
                    if (res->isValid())
                        possibleConstraints.add(
                            dynamic_cast<XML::Element*>(constraint->getThis()));
                    res->release();
                }
            }
        }
    }
    if (hasTypeAttr && possibleConstraints.getEintragAnzahl() == 1)
        return validate(zChildValue,
            possibleConstraints.begin().val(),
            childPath); // if the type is clear
    else if (hasTypeAttr && possibleConstraints.getEintragAnzahl() > 0)
    { // more then one type is possible
        RCArray<JSONValidationResult> invalidResults;
        for (XML::Element* constraint : possibleConstraints)
        {
            JSONValidationResult* res
                = validate(zChildValue, constraint, childPath);
            invalidResults.add(res);
            if (res->isValid())
                return new JSONValidValue(childPath,
                    dynamic_cast<JSONValue*>(zChildValue->getThis()));
        }
        return new JSONNoTypeMatching(childPath,
            dynamic_cast<JSONValue*>(zChildValue->getThis()),
            possibleConstraints,
            invalidResults);
    }
    // try all types
    possibleConstraints.leeren();
    RCArray<JSONValidationResult> invalidResults;
    for (XML::Element* constraint : zPossibleChildConstraints->selectChildren())
    {
        JSONValidationResult* res
            = validate(zChildValue, constraint, childPath);
        invalidResults.add(res);
        if (res->isValid())
            return new JSONValidValue(
                childPath, dynamic_cast<JSONValue*>(zChildValue->getThis()));
        possibleConstraints.add(
            dynamic_cast<XML::Element*>(constraint->getThis()));
    }
    return new JSONNoTypeMatching(childPath,
        dynamic_cast<JSONValue*>(zChildValue->getThis()),
        possibleConstraints,
        invalidResults);
}

StringValidationBuilder<JSONValidator>* JSONValidator::buildForString()
{
    return new StringValidationBuilder<JSONValidator>(
        [](XML::Element& e) { return new JSONValidator(e.dublicate()); });
}

NumberValidationBuilder<JSONValidator>* JSONValidator::buildForNumber()
{
    return new NumberValidationBuilder<JSONValidator>(
        [](XML::Element& e) { return new JSONValidator(e.dublicate()); });
}

BoolValidationBuilder<JSONValidator>* JSONValidator::buildForBool()
{
    return new BoolValidationBuilder<JSONValidator>(
        [](XML::Element& e) { return new JSONValidator(e.dublicate()); });
}

ObjectValidationBuilder<JSONValidator>* JSONValidator::buildForObject()
{
    return new ObjectValidationBuilder<JSONValidator>(
        [](XML::Element& e) { return new JSONValidator(e.dublicate()); });
}

ArrayValidationBuilder<JSONValidator>* JSONValidator::buildForArray()
{
    return new ArrayValidationBuilder<JSONValidator>(
        [](XML::Element& e) { return new JSONValidator(e.dublicate()); });
}

#pragma endregion Content