#include "DataValidator.h"

#include "Logging.h"
#include "Regex.h"

using namespace Framework;
using namespace Validator;

#pragma region ValidationResult

ValidationResult::ValidationResult()
    : ReferenceCounter()
{}

ValidationResult::~ValidationResult() {}

void ValidationResult::logInvalidInfo(std::source_location location) const
{
    Logging::error(location) << getInvalidInfo(0).getText();
}

#pragma region TypeMissmatch

Framework::Validator::TypeMissmatch::TypeMissmatch(Text path,
    AbstractElement* foundValue,
    XML::Element* expected,
    ValidationResult* reason)
    : ValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
    this->expected = expected;
    this->reason = reason;
}

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

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

Text TypeMissmatch::getInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    Text error = "";
    error.append() << ind.getText() << "Type missmatch at path '"
                   << path.getText() << "'. Value\n"
                   << ind.getText() << foundValue->toString().getText() << "\n"
                   << ind.getText() << "does not match expected type\n";
    if (reason)
    {
        error.append() << ind.getText() << "Reason for type mismatch:\n"
                       << reason->getInvalidInfo(indent + 4);
    }
    else
    {
        error.append() << ind.getText() << expected->toString().getText()
                       << "\n";
    }
    return error;
}

JSON::JSONValue* TypeMissmatch::getValidPart(
    RCArray<ValidationResult>* removedPartsValidationResults)
{
    if (reason)
    {
        RCArray<ValidationResult> temporaryInvalidParts;
        JSON::JSONValue* valid = reason->getValidPart(&temporaryInvalidParts);
        Text* p = reason->getPath().getTeilText(path.getLength());
        if (foundValue->getType() == AbstractType::ARRAY)
        {
            if (!valid
                && (!expected->hasAttribute("removeInvalidEntries")
                    || !expected->getAttributeValue("removeInvalidEntries")
                        .istGleich("true")))
            {
                if (removedPartsValidationResults)
                {
                    if (temporaryInvalidParts.getEintragAnzahl() == 1)
                    {
                        if (reason) reason->release();
                        reason = temporaryInvalidParts.get(0);
                    }
                    else
                    {
                        for (ValidationResult* res : temporaryInvalidParts)
                        {
                            if (res->isDifferent(this))
                            {
                                removedPartsValidationResults->add(
                                    dynamic_cast<ValidationResult*>(
                                        res->getThis()));
                            }
                        }
                    }
                    removedPartsValidationResults->add(
                        dynamic_cast<ValidationResult*>(getThis()));
                }
                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->asAbstractArray()->getLength() > i)
                {
                    if (removedPartsValidationResults)
                    {
                        for (ValidationResult* res : temporaryInvalidParts)
                        {
                            removedPartsValidationResults->add(
                                dynamic_cast<ValidationResult*>(
                                    res->getThis()));
                        }
                    }
                    JSON::JSONValue* foundValueJson
                        = dynamic_cast<JSON::JSONValue*>(foundValue);
                    if (foundValueJson)
                    {
                        JSON::JSONValue* tmp = foundValueJson->clone();
                        if (valid)
                            tmp->asArray()->setValue(
                                i, dynamic_cast<JSON::JSONValue*>(valid));
                        else
                            tmp->asArray()->removeValue(i);
                        ValidationResult* res = DataValidator(
                            dynamic_cast<XML::Element*>(expected->getThis()))
                                                    .validate(tmp);
                        res->addBasePath(path);
                        if (res->isValid())
                        {
                            res->release();
                            p->release();
                            return tmp;
                        }
                        else if (res->isDifferent(this) || !valid)
                        {
                            p->release();
                            JSON::JSONValue* result = res->getValidPart(
                                removedPartsValidationResults);
                            tmp->release();
                            res->release();
                            return result;
                        }
                        tmp->release();
                        res->release();
                    }
                }
            }
        }
        else if (foundValue->getType() == AbstractType::OBJECT)
        {
            if (!valid
                && (!expected->hasAttribute("removeInvalidEntries")
                    || !expected->getAttributeValue("removeInvalidEntries")
                        .istGleich("true")))
            {
                if (removedPartsValidationResults)
                {
                    if (temporaryInvalidParts.getEintragAnzahl() == 1)
                    {
                        if (reason) reason->release();
                        reason = temporaryInvalidParts.get(0);
                    }
                    else
                    {
                        for (ValidationResult* res : temporaryInvalidParts)
                        {
                            if (res->isDifferent(this))
                            {
                                removedPartsValidationResults->add(
                                    dynamic_cast<ValidationResult*>(
                                        res->getThis()));
                            }
                        }
                    }
                    removedPartsValidationResults->add(
                        dynamic_cast<ValidationResult*>(getThis()));
                }
                p->release();
                return 0;
            }
            if (p->hatAt(0, "."))
            {
                if (removedPartsValidationResults)
                {
                    for (ValidationResult* res : temporaryInvalidParts)
                    {
                        removedPartsValidationResults->add(
                            dynamic_cast<ValidationResult*>(res->getThis()));
                    }
                }
                Text* at = p->getTeilText(1);
                Text attr = *at;
                at->release();
                JSON::JSONValue* foundValueJson
                    = dynamic_cast<JSON::JSONValue*>(foundValue);
                if (foundValueJson)
                {
                    JSON::JSONValue* tmp = foundValueJson->clone();
                    tmp->asObject()->removeValue(attr);
                    if (valid)
                        tmp->asObject()->addValue(
                            attr, dynamic_cast<JSON::JSONValue*>(valid));
                    ValidationResult* res = DataValidator(
                        dynamic_cast<XML::Element*>(expected->getThis()))
                                                .validate(tmp);
                    res->addBasePath(path);
                    if (res->isValid())
                    {
                        res->release();
                        p->release();
                        return tmp;
                    }
                    else if (res->isDifferent(this) || !valid)
                    {
                        p->release();
                        JSON::JSONValue* result
                            = res->getValidPart(removedPartsValidationResults);
                        tmp->release();
                        res->release();
                        return result;
                    }
                    tmp->release();
                    res->release();
                }
            }
        }
        p->release();
        if (valid) valid->release();
    }
    if (removedPartsValidationResults)
    {
        removedPartsValidationResults->add(
            dynamic_cast<ValidationResult*>(getThis()));
    }
    return 0;
}

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

bool TypeMissmatch::isDifferent(const ValidationResult* zResult) const
{
    const TypeMissmatch* casted = dynamic_cast<const TypeMissmatch*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(path)) return 1;
    return reason->isDifferent(casted->reason);
}

void TypeMissmatch::addBasePath(Text basePath)
{
    path = basePath + path;
    if (reason) reason->addBasePath(basePath);
}

#pragma endregion Cotent

#pragma region UnknownValue

UnknownValue::UnknownValue(Text path, AbstractElement* foundValue)
    : ValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
}

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

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

Text UnknownValue::getInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    Text error = "";
    error.append() << ind.getText() << "Unknown Value at '" << path.getText()
                   << "'. Value found:\n"
                   << ind.getText() << foundValue->toString().getText() << "\n";
    return error;
}

JSON::JSONValue* UnknownValue::getValidPart(
    RCArray<ValidationResult>* removedPartsValidationResults)
{
    if (removedPartsValidationResults)
    {
        removedPartsValidationResults->add(
            dynamic_cast<ValidationResult*>(getThis()));
    }
    return 0;
}

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

bool UnknownValue::isDifferent(const ValidationResult* zResult) const
{
    const UnknownValue* casted = dynamic_cast<const UnknownValue*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(path)) return 1;
    return 0;
}

void UnknownValue::addBasePath(Text basePath)
{
    path = basePath + path;
}

#pragma endregion Cotent

#pragma region MissingValue

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

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

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

Text MissingValue::getInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    Text error = "";
    error.append() << ind.getText() << "Missing Value at '" << path.getText()
                   << "'. Expected type:\n"
                   << ind.getText() << expected->toString().getText() << "\n";
    return error;
}

JSON::JSONValue* MissingValue::getValidPart(
    RCArray<ValidationResult>* removedPartsValidationResults)
{
    if (expected->hasAttribute("default"))
    {
        JSON::JSONValue* def
            = JSON::Parser::getValue(expected->getAttributeValue("default"));
        ValidationResult* res
            = DataValidator(dynamic_cast<XML::Element*>(expected->getThis()))
                  .validate(def);
        res->addBasePath(path);
        if (res->isValid())
        {
            res->release();
            return def;
        }
        else if (res->isDifferent(this))
        {
            def->release();
            JSON::JSONValue* result
                = res->getValidPart(removedPartsValidationResults);
            res->release();
            return result;
        }
        def->release();
    }
    if (removedPartsValidationResults)
    {
        removedPartsValidationResults->add(
            dynamic_cast<ValidationResult*>(getThis()));
    }
    return 0;
}

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

bool MissingValue::isDifferent(const ValidationResult* zResult) const
{
    const MissingValue* casted = dynamic_cast<const MissingValue*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(path)) return 1;
    return 0;
}

void MissingValue::addBasePath(Text basePath)
{
    path = basePath + path;
}

#pragma endregion Cotent

#pragma region MissingOneOf

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

MissingOneOf::~MissingOneOf() {}

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

Text MissingOneOf::getInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    Text error = "";
    error.append() << ind.getText() << "Missing Value at '" << path.getText()
                   << "'. The value should have one of the specified types:\n";
    ind += "    ";
    for (XML::Element* e : expected)
    {
        error.append() << ind.getText() << e->toString().getText() << "\n";
    }
    return error;
}

JSON::JSONValue* MissingOneOf::getValidPart(
    RCArray<ValidationResult>* removedPartsValidationResults)
{
    for (XML::Element* e : expected)
    {
        if (e->hasAttribute("default"))
        {
            JSON::JSONValue* defaultValue
                = JSON::Parser::getValue(e->getAttributeValue("default"));
            if (defaultValue)
            {
                JSON::JSONValue* valid
                    = DataValidator(dynamic_cast<XML::Element*>(e->getThis()))
                          .getValidParts(
                              defaultValue, removedPartsValidationResults);
                defaultValue->release();
                if (valid)
                {
                    return valid;
                }
            }
        }
    }
    if (removedPartsValidationResults)
    {
        removedPartsValidationResults->add(
            dynamic_cast<ValidationResult*>(getThis()));
    }
    // multiple possibilities are undecidable
    return 0;
}

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

bool MissingOneOf::isDifferent(const ValidationResult* zResult) const
{
    const MissingOneOf* casted = dynamic_cast<const MissingOneOf*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(path)) return 1;
    return 0;
}

void MissingOneOf::addBasePath(Text basePath)
{
    path = basePath + path;
}

#pragma endregion Content

#pragma region NoTypeMatching

NoTypeMatching::NoTypeMatching(Text path,
    AbstractElement* foundValue,
    RCArray<XML::Element>& expected,
    RCArray<ValidationResult>& reasons)
    : ValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
    this->expected = expected;
    this->reasons = reasons;
}

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

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

Text NoTypeMatching::getInvalidInfo(int indent) const
{
    Text ind = "";
    ind.fillText(' ', indent);
    Text error = "";
    error.append() << ind.getText() << "Found Value at '" << path.getText()
                   << "' did not match any of the specified types\n";
    Text ind2 = ind + "    ";
    error.append() << ind.getText() << "Reasons:\n";
    for (ValidationResult* reason : reasons)
    {
        error += reason->getInvalidInfo(indent + 4);
    }
    return error;
}

JSON::JSONValue* NoTypeMatching::getValidPart(
    RCArray<ValidationResult>* removedPartsValidationResults)
{
    RCArray<ValidationResult> tempErrors;
    for (ValidationResult* reason : reasons)
    {
        JSON::JSONValue* result = reason->getValidPart(&tempErrors);
        if (result)
        {
            return result;
        }
    }
    if (removedPartsValidationResults)
    {
        removedPartsValidationResults->add(
            dynamic_cast<ValidationResult*>(getThis()));
        reasons.leeren();
        for (ValidationResult* error : tempErrors)
        {
            reasons.add(dynamic_cast<ValidationResult*>(error->getThis()));
        }
    }
    // multiple possibilities are undecidable
    return 0;
}

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

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

void NoTypeMatching::addBasePath(Text basePath)
{
    path = basePath + path;
    for (ValidationResult* reason : reasons)
    {
        reason->addBasePath(basePath);
    }
}

#pragma endregion Content

#pragma region ValidationPathNotFound

ValidationPathNotFound::ValidationPathNotFound(
    Text path, AbstractElement* foundValue, Text validationPath)
    : ValidationResult()
{
    this->path = path;
    this->foundValue = foundValue;
    this->validationPath = validationPath;
}

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

bool ValidationPathNotFound::isValid() const
{
    return false;
}

Text ValidationPathNotFound::getInvalidInfo(int indent) const
{
    Text result;
    result.append() << "Expected to validate path '" << validationPath.getText()
                    << "' but at path '" << path.getText() << "' the value "
                    << foundValue->toString().getText() << " was found.";
    return result;
}

JSON::JSONValue* ValidationPathNotFound::getValidPart(
    RCArray<ValidationResult>* zRemovedPartsValidationResults)
{
    return 0;
}

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

bool ValidationPathNotFound::isDifferent(const ValidationResult* zResult) const
{
    const ValidationPathNotFound* other
        = dynamic_cast<const ValidationPathNotFound*>(zResult);
    if (other == 0) return 1;
    if (!other->path.istGleich(path)) return 1;
    return 0;
}

void ValidationPathNotFound::addBasePath(Text basePath)
{
    path = basePath + path;
}

#pragma endregion Content

#pragma region ValidValue

ValidValue::ValidValue(Text path, AbstractElement* value)
    : ValidationResult()
{
    this->path = path;
    this->value = value;
}

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

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

Text ValidValue::getInvalidInfo(int indent) const
{
    return "";
}

JSON::JSONValue* ValidValue::getValidPart(
    RCArray<ValidationResult>* removedPartsValidationResults)
{
    JSON::JSONValue* json = dynamic_cast<JSON::JSONValue*>(value);
    return json ? json->clone() : 0;
}

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

bool ValidValue::isDifferent(const ValidationResult* zResult) const
{
    const ValidValue* casted = dynamic_cast<const ValidValue*>(zResult);
    if (casted == 0) return 1;
    if (!casted->getPath().istGleich(path)) return 1;
    return 0;
}

void ValidValue::addBasePath(Text basePath)
{
    path = basePath + path;
}

#pragma endregion Content

#pragma endregion Content

#pragma region DataValidator

DataValidator::DataValidator(XML::Element* constraints)
    : ReferenceCounter(),
      constraints(constraints),
      typeConstraints(new RCTrie<XML::Element>())
{
    for (XML::Element* e :
        constraints->select().selectAllElements().whereAttributeExists("id"))
    {
        Framework::Text id = e->getAttributeValue("id");
        typeConstraints->set(
            id, id.getLength(), dynamic_cast<XML::Element*>(e->getThis()));
    }
}

DataValidator::DataValidator(
    XML::Element* constraints, RCTrie<XML::Element>* typeConstraints)
    : ReferenceCounter(),
      constraints(constraints),
      typeConstraints(typeConstraints)
{}

DataValidator::~DataValidator()
{
    constraints->release();
    typeConstraints->release();
}

ValidationResult* DataValidator::validate(AbstractElement* zValue) const
{
    return validate(ElementPathBuilder().build(), zValue);
}

ValidationResult* Framework::Validator::DataValidator::validate(
    ElementPath* path, AbstractElement* zValue) const
{
    ValidationResult* result = validate(path, zValue, constraints, "");
    path->release();
    return result;
}

bool DataValidator::isValid(AbstractElement* zValue) const
{
    ValidationResult* res = validate(zValue);
    if (res->isValid())
    {
        res->release();
        return 1;
    }
    res->release();
    return 0;
}

JSON::JSONValue* DataValidator::getValidParts(JSON::JSONValue* zValue,
    RCArray<ValidationResult>* removedPartsValidationResults) const
{
    ValidationResult* res = validate(zValue);
    if (res->isValid())
    {
        res->release();
        return zValue->clone();
    }
    JSON::JSONValue* valid = res->getValidPart(removedPartsValidationResults);
    res->release();
    return valid;
}

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

JSON::JSONObject* Framework::Validator::DataValidator::getJsonSchema() const
{
    JSON::JSONObject* zDefs = new JSON::JSONObject();
    JSON::JSONObject* result = getJsonSchema(constraints, zDefs);
    if (zDefs->getFieldCount() > 0)
    {
        result->addValue("$defs", zDefs);
    }
    else
    {
        zDefs->release();
    }
    result->addValue("$schema",
        new JSON::JSONString("https://json-schema.org/draft-04/schema#"));
    return result;
}

void Framework::Validator::DataValidator::updateValidator(
    Text id, DataValidator* validator)
{
    typeConstraints->set(id, id.getLength(), validator->constraints);
    validator->release();
}

ValidationResult* DataValidator::validate(ElementPath* pathToValidate,
    AbstractElement* zValue,
    XML::Element* zConstraints,
    Text path) const
{
    if (zConstraints->getName().istGleich("oneOf"))
    {
        return validateMultipleTypes(
            pathToValidate, zValue, zConstraints, path);
    }
    if (zConstraints->getName().istGleich("ref"))
    {
        Text id = zConstraints->getAttributeValue("ref");
        XML::Element* e = typeConstraints->z(id, id.getLength());
        if (e)
        {
            zConstraints = e;
        }
    }
    switch (zValue->getType())
    {
    case AbstractType::NULL_:
        if (pathToValidate->isValid())
        {
            return new ValidationPathNotFound(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                pathToValidate->toString());
        }
        if (!zConstraints->hasAttribute("nullable")
            || !zConstraints->getAttributeValue("nullable").istGleich("true"))
        {
            return new TypeMissmatch(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        break;
    case AbstractType::BOOLEAN:
        if (pathToValidate->isValid())
        {
            return new ValidationPathNotFound(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                pathToValidate->toString());
        }
        if (!zConstraints->getName().istGleich("bool"))
        {
            return new TypeMissmatch(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else if (zConstraints->hasAttribute("equals"))
        {
            if (!zConstraints->getAttributeValue("equals").istGleich("true")
                == !zValue->asAbstractBool()->getBool())
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
        }
        break;
    case AbstractType::NUMBER:
        if (pathToValidate->isValid())
        {
            return new ValidationPathNotFound(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                pathToValidate->toString());
        }
        if (!zConstraints->getName().istGleich("number"))
        {
            return new TypeMissmatch(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            double number = zValue->asAbstractNumber()->getNumber();
            if (zConstraints->hasAttribute("equals")
                && (double)zConstraints->getAttributeValue("equals") != number)
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("lessOrEqual")
                && number
                       > (double)zConstraints->getAttributeValue("lessOrEqual"))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("greaterOrEqual")
                && number < (double)zConstraints->getAttributeValue(
                       "greaterOrEqual"))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("less")
                && number >= (double)zConstraints->getAttributeValue("less"))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("greater")
                && number <= (double)zConstraints->getAttributeValue("greater"))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
        }
        break;
    case AbstractType::STRING:
        if (pathToValidate->isValid())
        {
            return new ValidationPathNotFound(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                pathToValidate->toString());
        }
        if (!zConstraints->getName().istGleich("string"))
        {
            return new TypeMissmatch(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            Text string = zValue->asAbstractString()->getString();
            if (zConstraints->hasAttribute("equals")
                && !zConstraints->getAttributeValue("equals").istGleich(string))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("contains")
                && !string.hat(
                    zConstraints->getAttributeValue("contains").getText()))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("startsWith")
                && string.positionVon(
                       zConstraints->getAttributeValue("startsWith").getText())
                       != 0)
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("endsWith")
                && !string.hatAt(
                    string.getLength()
                        - zConstraints->getAttributeValue("endsWith")
                            .getLength(),
                    zConstraints->getAttributeValue("endsWith").getText()))
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("oneOf"))
            {
                JSON::JSONArray* array = JSON::Parser::getValue(
                    zConstraints->getAttributeValue("oneOf"))
                                             ->asArray();
                bool ok = 0;
                for (JSON::JSONValue* v : *array)
                {
                    if (v->asString()->getString().istGleich(string))
                    {
                        ok = 1;
                        break;
                    }
                }
                array->release();
                if (!ok)
                {
                    return new TypeMissmatch(path,
                        dynamic_cast<AbstractElement*>(zValue->getThis()),
                        dynamic_cast<XML::Element*>(zConstraints->getThis()),
                        0);
                }
            }
        }
        break;
    case AbstractType::ARRAY:
        if (!zConstraints->getName().istGleich("array"))
        {
            return new TypeMissmatch(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            if (pathToValidate->isValid())
            {
                if (!pathToValidate->isArrayElement())
                {
                    return new ValidationPathNotFound(path,
                        dynamic_cast<AbstractElement*>(zValue->getThis()),
                        pathToValidate->toString());
                }
                else
                {
                    int index = pathToValidate->getArrayIndex();
                    const AbstractArray* array = zValue->asAbstractArray();
                    if (index >= array->getLength())
                    {
                        return new ValidationPathNotFound(path,
                            dynamic_cast<AbstractElement*>(zValue->getThis()),
                            pathToValidate->toString());
                    }
                    Text p = path;
                    p += "[";
                    p += index;
                    p += "]";
                    pathToValidate->next();
                    return validateMultipleTypes(pathToValidate,
                        array->zAbstractValue(index),
                        zConstraints,
                        p);
                }
            }
            const AbstractArray* array = zValue->asAbstractArray();
            if (zConstraints->hasAttribute("minSize")
                && (int)zConstraints->getAttributeValue("minSize")
                       > array->getLength())
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            if (zConstraints->hasAttribute("maxSize")
                && (int)zConstraints->getAttributeValue("maxSize")
                       < array->getLength())
            {
                return new TypeMissmatch(path,
                    dynamic_cast<AbstractElement*>(zValue->getThis()),
                    dynamic_cast<XML::Element*>(zConstraints->getThis()),
                    0);
            }
            for (int i = 0; i < array->getLength(); i++)
            {
                Text p = path;
                p += "[";
                p += i;
                p += "]";
                ValidationResult* res = validateMultipleTypes(
                    pathToValidate, array->zAbstractValue(i), zConstraints, p);
                if (!res->isValid())
                {
                    return new TypeMissmatch(path,
                        dynamic_cast<AbstractElement*>(zValue->getThis()),
                        dynamic_cast<XML::Element*>(zConstraints->getThis()),
                        res);
                }
            }
        }
        break;
    case AbstractType::OBJECT:
        if (!zConstraints->getName().istGleich("object"))
        {
            return new TypeMissmatch(path,
                dynamic_cast<AbstractElement*>(zValue->getThis()),
                dynamic_cast<XML::Element*>(zConstraints->getThis()),
                0);
        }
        else
        {
            if (pathToValidate->isValid())
            {
                if (!pathToValidate->isObjectAttribute())
                {
                    return new ValidationPathNotFound(path,
                        dynamic_cast<AbstractElement*>(zValue->getThis()),
                        pathToValidate->toString());
                }
                else
                {
                    Text key = pathToValidate->getObjectAttribute();
                    const AbstractObject* obj = zValue->asAbstractObject();
                    if (!obj->hasValue(key))
                    {
                        return new ValidationPathNotFound(path,
                            dynamic_cast<AbstractElement*>(zValue->getThis()),
                            pathToValidate->toString());
                    }
                    Text p = path;
                    p += ".";
                    p += key;
                    if (!zConstraints->selectChildsByAttribute("name", key)
                            .exists())
                    {
                        if (!zConstraints
                                ->getAttributeValue("allowAdditionalAttributes")
                                .istGleich("true"))
                        {
                            return new TypeMissmatch(path,
                                dynamic_cast<AbstractElement*>(
                                    zValue->getThis()),
                                dynamic_cast<XML::Element*>(
                                    zConstraints->getThis()),
                                new UnknownValue(p,
                                    dynamic_cast<AbstractElement*>(
                                        obj->zAbstractValue(key)->getThis())));
                        }
                    }
                    else
                    {
                        XML::Editor tmp = zConstraints->selectChildsByAttribute(
                            "name", key);
                        pathToValidate->next();
                        return validateMultipleTypes(pathToValidate,
                            obj->zAbstractValue(key),
                            tmp.begin().val(),
                            p);
                    }
                }
            }
            const AbstractObject* obj = zValue->asAbstractObject();
            for (int i = 0; i < obj->getFieldCount(); i++)
            {
                Text key = obj->getFieldKey(i);
                Text p = path;
                p += ".";
                p += key;
                if (!zConstraints->selectChildsByAttribute("name", key)
                        .exists())
                {
                    if (!zConstraints
                            ->getAttributeValue("allowAdditionalAttributes")
                            .istGleich("true"))
                    {
                        return new TypeMissmatch(path,
                            dynamic_cast<AbstractElement*>(zValue->getThis()),
                            dynamic_cast<XML::Element*>(
                                zConstraints->getThis()),
                            new UnknownValue(p,
                                dynamic_cast<AbstractElement*>(
                                    obj->zAbstractValue(i)->getThis())));
                    }
                }
                else
                {
                    XML::Editor tmp
                        = zConstraints->selectChildsByAttribute("name", key);
                    ValidationResult* res
                        = validateMultipleTypes(pathToValidate,
                            obj->zAbstractValue(i),
                            tmp.begin().val(),
                            p);
                    if (!res->isValid())
                    {
                        return new TypeMissmatch(path,
                            dynamic_cast<AbstractElement*>(zValue->getThis()),
                            dynamic_cast<XML::Element*>(
                                zConstraints->getThis()),
                            res);
                    }
                    res->release();
                }
            }
            for (XML::Element* constraint : zConstraints->selectChildren())
            {
                if (!obj->hasValue(constraint->getAttributeValue("name")))
                {
                    XML::Editor tmp = constraint->selectChildren();
                    bool optional = true;
                    std::function<void(XML::Element*)> checkOptional;
                    checkOptional = [&optional, &checkOptional](
                                        XML::Element* zElement) {
                        if (zElement->getName().istGleich("oneOf")
                            && (!zElement->hasAttribute("optional")
                                || !zElement->getAttributeValue("optional")
                                    .istGleich("true")))
                        {
                            XML::Editor tmp = zElement->selectChildren();
                            tmp.forEach(checkOptional);
                        }
                        else
                        {
                            optional &= zElement->hasAttribute("optional")
                                     && zElement->getAttributeValue("optional")
                                            .istGleich("true");
                        }
                    };
                    tmp.forEach(checkOptional);
                    if (!optional)
                    {
                        Text p = path;
                        p += ".";
                        p += constraint->getAttributeValue("name");
                        if (constraint->getChildCount() != 1)
                            return new TypeMissmatch(path,
                                dynamic_cast<AbstractElement*>(
                                    zValue->getThis()),
                                dynamic_cast<XML::Element*>(
                                    zConstraints->getThis()),
                                new MissingOneOf(p, tmp));
                        return new TypeMissmatch(path,
                            dynamic_cast<AbstractElement*>(zValue->getThis()),
                            dynamic_cast<XML::Element*>(
                                zConstraints->getThis()),
                            new MissingValue(p,
                                dynamic_cast<XML::Element*>(
                                    tmp.begin()->getThis())));
                    }
                }
            }
        }
        break;
    }
    return new ValidValue(
        path, dynamic_cast<AbstractElement*>(zValue->getThis()));
}

ValidationResult* DataValidator::validateMultipleTypes(
    ElementPath* pathToValidate,
    AbstractElement* zChildValue,
    XML::Element* zPossibleChildConstraints,
    Text childPath) const
{
    if (zPossibleChildConstraints->getChildCount() == 1
        && !zPossibleChildConstraints->hasAttribute("typeSpecifiedBy"))
    {
        XML::Editor children = zPossibleChildConstraints->selectChildren();
        return validate(pathToValidate,
            zChildValue,
            children.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() == AbstractType::OBJECT)
        {
            const AbstractObject* obj = zChildValue->asAbstractObject();
            if (obj->hasValue(typeAttr))
            {
                AbstractElement* typeV = obj->zAbstractValue(typeAttr);
                ElementPath* tmp = ElementPathBuilder().build();
                for (XML::Element* constraint :
                    zPossibleChildConstraints->selectChildsByName("object")
                        .whereChildWithAttributeExists("name", typeAttr))
                {
                    XML::Editor nameChildren
                        = constraint->selectChildsByAttribute("name", typeAttr);
                    XML::Element* typeAttrContraints
                        = nameChildren.begin().val();
                    ValidationResult* res = validateMultipleTypes(tmp,
                        typeV,
                        typeAttrContraints,
                        childPath + "." + typeAttr);
                    if (res->isValid())
                        possibleConstraints.add(
                            dynamic_cast<XML::Element*>(constraint->getThis()));
                    res->release();
                }
                tmp->release();
            }
        }
    }
    if (hasTypeAttr && possibleConstraints.getEintragAnzahl() == 1)
        return validate(pathToValidate,
            zChildValue,
            possibleConstraints.begin().val(),
            childPath); // if the type is clear
    else if (hasTypeAttr && possibleConstraints.getEintragAnzahl() > 0)
    { // more then one type is possible
        RCArray<ValidationResult> invalidResults;
        int index = pathToValidate->getCurrentPathElementIndex();
        for (XML::Element* constraint : possibleConstraints)
        {
            pathToValidate->setCurrentPathElementIndex(index);
            ValidationResult* res
                = validate(pathToValidate, zChildValue, constraint, childPath);
            invalidResults.add(res);
            if (res->isValid())
                return new ValidValue(childPath,
                    dynamic_cast<AbstractElement*>(zChildValue->getThis()));
        }
        return new NoTypeMatching(childPath,
            dynamic_cast<AbstractElement*>(zChildValue->getThis()),
            possibleConstraints,
            invalidResults);
    }
    // try all types
    possibleConstraints.leeren();
    RCArray<ValidationResult> invalidResults;
    int index = pathToValidate->getCurrentPathElementIndex();
    for (XML::Element* constraint : zPossibleChildConstraints->selectChildren())
    {
        pathToValidate->setCurrentPathElementIndex(index);
        ValidationResult* res
            = validate(pathToValidate, zChildValue, constraint, childPath);
        invalidResults.add(res);
        if (res->isValid())
            return new ValidValue(childPath,
                dynamic_cast<AbstractElement*>(zChildValue->getThis()));
        possibleConstraints.add(
            dynamic_cast<XML::Element*>(constraint->getThis()));
    }
    pathToValidate->setCurrentPathElementIndex(index);
    if (pathToValidate->isValid())
    {
        return new ValidationPathNotFound(childPath,
            dynamic_cast<AbstractElement*>(zChildValue->getThis()),
            pathToValidate->toString());
    }
    return new NoTypeMatching(childPath,
        dynamic_cast<AbstractElement*>(zChildValue->getThis()),
        possibleConstraints,
        invalidResults);
}

JSON::JSONObject* Framework::Validator::DataValidator::getJsonSchema(
    XML::Element* zConstraint, JSON::JSONObject* zDefs) const
{
    JSON::JSONObject* result = new JSON::JSONObject();
    if (zConstraint->hasAttribute("id"))
    {
        Text id = zConstraint->getAttributeValue("id");
        id.ersetzen(" ", "_");
        if (zDefs->hasValue(id))
        {
            Text jsonRef = Text("#/$defs/") + id;
            result->addValue("$ref", new JSON::JSONString(jsonRef));
            return result;
        }
        zDefs->addValue(id, new JSON::JSONValue()); // avoid endless recursion
                                                    // for cyclic datastructures
    }
    if (zConstraint->getName().istGleich("bool"))
    {
        if (zConstraint->hasAttribute("equals"))
        {
            JSON::JSONArray* array = new JSON::JSONArray();
            array->addValue(new JSON::JSONBool(
                zConstraint->getAttributeValue("equals").istGleich("true")));
            result->addValue("enum", array);
        }
        else
        {
            result->addValue("type", new JSON::JSONString("boolean"));
        }
    }
    else if (zConstraint->getName().istGleich("number"))
    {
        if (zConstraint->hasAttribute("equals"))
        {
            JSON::JSONArray* array = new JSON::JSONArray();
            array->addValue(new JSON::JSONNumber(
                (double)zConstraint->getAttributeValue("equals")));
            result->addValue("enum", array);
        }
        else
        {
            result->addValue("type", new JSON::JSONString("number"));
            if (zConstraint->hasAttribute("lessOrEqual"))
            {
                result->addValue("maximum",
                    new JSON::JSONNumber(
                        (double)zConstraint->getAttributeValue("lessOrEqual")));
            }
            if (zConstraint->hasAttribute("greaterOrEqual"))
            {
                result->addValue("minimum",
                    new JSON::JSONNumber((double)zConstraint->getAttributeValue(
                        "greaterOrEqual")));
            }
            if (zConstraint->hasAttribute("less"))
            {
                result->addValue("maximum",
                    new JSON::JSONNumber(
                        (double)zConstraint->getAttributeValue("less")));
                result->addValue("exclusiveMaximum", new JSON::JSONBool(true));
            }
            if (zConstraint->hasAttribute("greater"))
            {
                result->addValue("minimum",
                    new JSON::JSONNumber(
                        (double)zConstraint->getAttributeValue("greater")));
                result->addValue("exclusiveMinimum", new JSON::JSONBool(true));
            }
        }
    }
    else if (zConstraint->getName().istGleich("string"))
    {
        if (zConstraint->hasAttribute("equals"))
        {
            JSON::JSONArray* array = new JSON::JSONArray();
            array->addValue(
                new JSON::JSONString(zConstraint->getAttributeValue("equals")));
            result->addValue("enum", array);
        }
        else if (zConstraint->hasAttribute("oneOf"))
        {
            JSON::JSONArray* array = JSON::Parser::getValue(
                zConstraint->getAttributeValue("oneOf"))
                                         ->asArray();
            result->addValue("enum", array);
        }
        else
        {
            result->addValue("type", new JSON::JSONString("string"));
            if (zConstraint->hasAttribute("contains"))
            {
                result->addValue("pattern",
                    new JSON::JSONString(
                        Text(".*")
                        + Regex::quote(
                            zConstraint->getAttributeValue("contains"))
                        + ".*"));
            }
            if (zConstraint->hasAttribute("startsWith"))
            {
                result->addValue("pattern",
                    new JSON::JSONString(
                        Text("^")
                        + Regex::quote(
                            zConstraint->getAttributeValue("startsWith"))
                        + ".*"));
            }
            if (zConstraint->hasAttribute("endsWith"))
            {
                result->addValue("pattern",
                    new JSON::JSONString(
                        Text(".*")
                        + Regex::quote(
                            zConstraint->getAttributeValue("endsWith"))
                        + "$"));
            }
        }
    }
    else if (zConstraint->getName().istGleich("ref"))
    {
        Text id = zConstraint->getAttributeValue("ref");
        XML::Element* e = typeConstraints->z(id, id.getLength());
        id.ersetzen(" ", "_");
        if (!zDefs->hasValue(id))
        {
            JSON::JSONObject* def = getJsonSchema(e, zDefs);
            if (!def->hasValue(id))
            {
                zDefs->addValue(id, def);
            }
            else
            {
                // def was already added by a child
                def->release();
            }
        }
        Text jsonRef = Text("#/$defs/") + id;
        result->addValue("$ref", new JSON::JSONString(jsonRef));
    }
    else if (zConstraint->getName().istGleich("object"))
    {
        result->addValue("type", new JSON::JSONString("object"));
        result->addValue("additionalProperties",
            new JSON::JSONBool(
                zConstraint->getAttributeValue("allowAdditionalAttributes")
                    .istGleich("true")));
        JSON::JSONObject* properties = new JSON::JSONObject();
        JSON::JSONArray* required = new JSON::JSONArray();
        for (XML::Element* e : zConstraint->selectChildren())
        {
            JSON::JSONObject* prop = new JSON::JSONObject();
            JSON::JSONArray* oneOf = new JSON::JSONArray();
            bool isRequired = true;
            for (XML::Element* child : e->selectChildren())
            {
                oneOf->addValue(getJsonSchema(child, zDefs));
                isRequired
                    &= !child->getAttributeValue("optional").istGleich("true")
                    && !child->hasAttribute("default");
            }
            prop->addValue("oneOf", oneOf);
            properties->addValue(e->getAttributeValue("name"), prop);
            if (isRequired)
            {
                required->addValue(
                    new JSON::JSONString(e->getAttributeValue("name")));
            }
        }
        result->addValue("properties", properties);
        result->addValue("required", required);
    }
    else if (zConstraint->getName().istGleich("array"))
    {
        result->addValue("type", new JSON::JSONString("array"));
        JSON::JSONObject* items = new JSON::JSONObject();
        JSON::JSONArray* oneOf = new JSON::JSONArray();
        for (XML::Element* e : zConstraint->selectChildren())
        {
            oneOf->addValue(getJsonSchema(e, zDefs));
        }
        if (zConstraint->hasAttribute("minSize"))
        {
            result->addValue("minItems",
                new JSON::JSONNumber(
                    (double)zConstraint->getAttributeValue("minSize")));
        }
        if (zConstraint->hasAttribute("maxSize"))
        {
            result->addValue("maxItems",
                new JSON::JSONNumber(
                    (double)zConstraint->getAttributeValue("maxSize")));
        }
        items->addValue("oneOf", oneOf);
        result->addValue("items", items);
    }
    else if (zConstraint->getName().istGleich("oneOf"))
    {
        JSON::JSONArray* oneOf = new JSON::JSONArray();
        for (XML::Element* e : zConstraint->selectChildren())
        {
            oneOf->addValue(getJsonSchema(e, zDefs));
        }
        result->addValue("oneOf", oneOf);
    }
    if (zConstraint->hasAttribute("nullable")
        && zConstraint->getAttributeValue("nullable").istGleich("true"))
    {
        if (!result->hasValue("type"))
        {
            if (result->hasValue("enum"))
            {
                result->zValue("enum")->asArray()->addValue(
                    new JSON::JSONValue());
            }
            else
            {
                JSON::JSONObject* oneOf = new JSON::JSONObject();
                JSON::JSONArray* array = new JSON::JSONArray();
                array->addValue(result);
                JSON::JSONObject* nullType = new JSON::JSONObject();
                nullType->addValue("type", new JSON::JSONString("null"));
                array->addValue(nullType);
                oneOf->addValue("oneOf", array);
                result = oneOf;
            }
        }
        else
        {
            if (result->zValue("type")->getType() == AbstractType::ARRAY)
            {
                result->zValue("type")->asArray()->addValue(
                    new JSON::JSONString("null"));
            }
            else
            {
                JSON::JSONArray* array = new JSON::JSONArray();
                array->addValue(result->getValue("type"));
                array->addValue(new JSON::JSONString("null"));
                result->removeValue("type");
                result->addValue("type", array);
            }
        }
    }
    if (zConstraint->hasAttribute("id"))
    {
        Text id = zConstraint->getAttributeValue("id");
        id.ersetzen(" ", "_");
        zDefs->removeValue(id);
        zDefs->addValue(id, result);
        result = new JSON::JSONObject();
        Text jsonRef = Text("#/$defs/") + id;
        result->addValue("$ref", new JSON::JSONString(jsonRef));
    }
    return result;
}

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

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

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

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

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

DataValidator* DataValidator::buildForReference(Text id)
{
    return new DataValidator(
        new XML::Element(Text("<ref ref=\"") + id + Text("\"/>")));
}

OneOfValidationBuilder<DataValidator>* DataValidator::buildForOneOf()
{
    return new OneOfValidationBuilder<DataValidator>(
        [](XML::Element& e) { return new DataValidator(e.dublicate()); });
}