/*
    SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>

    SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/

#include "predicate.h"

#include <solid/device.h>
#include <solid/deviceinterface.h>
#include <QStringList>
#include <QMetaEnum>

namespace Solid
{
class Predicate::Private
{
public:

    Private() : isValid(false), type(PropertyCheck),
        compOperator(Predicate::Equals),
        operand1(nullptr), operand2(nullptr) {}

    bool isValid;
    Type type;

    DeviceInterface::Type ifaceType;
    QString property;
    QVariant value;
    Predicate::ComparisonOperator compOperator;

    Predicate *operand1;
    Predicate *operand2;
};
}

Solid::Predicate::Predicate()
    : d(new Private())
{
}

Solid::Predicate::Predicate(const Predicate &other)
    : d(new Private())
{
    *this = other;
}

Solid::Predicate::Predicate(const DeviceInterface::Type &ifaceType,
                            const QString &property, const QVariant &value,
                            ComparisonOperator compOperator)
    : d(new Private())
{
    d->isValid = true;
    d->ifaceType = ifaceType;
    d->property = property;
    d->value = value;
    d->compOperator = compOperator;
}

Solid::Predicate::Predicate(const QString &ifaceName,
                            const QString &property, const QVariant &value,
                            ComparisonOperator compOperator)
    : d(new Private())
{
    DeviceInterface::Type ifaceType = DeviceInterface::stringToType(ifaceName);

    if (((int)ifaceType) != -1) {
        d->isValid = true;
        d->ifaceType = ifaceType;
        d->property = property;
        d->value = value;
        d->compOperator = compOperator;
    }
}

Solid::Predicate::Predicate(const DeviceInterface::Type &ifaceType)
    : d(new Private())
{
    d->isValid = true;
    d->type = InterfaceCheck;
    d->ifaceType = ifaceType;
}

Solid::Predicate::Predicate(const QString &ifaceName)
    : d(new Private())
{
    DeviceInterface::Type ifaceType = DeviceInterface::stringToType(ifaceName);

    if (((int)ifaceType) != -1) {
        d->isValid = true;
        d->type = InterfaceCheck;
        d->ifaceType = ifaceType;
    }
}

Solid::Predicate::~Predicate()
{
    if (d->type != PropertyCheck && d->type != InterfaceCheck) {
        delete d->operand1;
        delete d->operand2;
    }

    delete d;
}

Solid::Predicate &Solid::Predicate::operator=(const Predicate &other)
{
    d->isValid = other.d->isValid;
    d->type = other.d->type;

    if (d->type != PropertyCheck && d->type != InterfaceCheck) {
        Predicate *operand1 = new Predicate(*(other.d->operand1));
        delete d->operand1;
        d->operand1 = operand1;
        Predicate *operand2 = new Predicate(*(other.d->operand2));
        delete d->operand2;
        d->operand2 = operand2;
    } else {
        d->ifaceType = other.d->ifaceType;
        d->property = other.d->property;
        d->value = other.d->value;
        d->compOperator = other.d->compOperator;
    }

    return *this;
}

Solid::Predicate Solid::Predicate::operator &(const Predicate &other)
{
    Predicate result;

    result.d->isValid = true;
    result.d->type = Conjunction;
    result.d->operand1 = new Predicate(*this);
    result.d->operand2 = new Predicate(other);

    return result;
}

Solid::Predicate &Solid::Predicate::operator &=(const Predicate &other)
{
    *this = *this & other;
    return *this;
}

Solid::Predicate Solid::Predicate::operator|(const Predicate &other)
{
    Predicate result;

    result.d->isValid = true;
    result.d->type = Disjunction;
    result.d->operand1 = new Predicate(*this);
    result.d->operand2 = new Predicate(other);

    return result;
}

Solid::Predicate &Solid::Predicate::operator |=(const Predicate &other)
{
    *this = *this | other;
    return *this;
}

bool Solid::Predicate::isValid() const
{
    return d->isValid;
}

bool Solid::Predicate::matches(const Device &device) const
{
    if (!d->isValid) {
        return false;
    }

    switch (d->type) {
    case Disjunction:
        return d->operand1->matches(device)
               || d->operand2->matches(device);
    case Conjunction:
        return d->operand1->matches(device)
               && d->operand2->matches(device);
    case PropertyCheck: {
        const DeviceInterface *iface = device.asDeviceInterface(d->ifaceType);

        if (iface != nullptr) {
            const int index = iface->metaObject()->indexOfProperty(d->property.toLatin1());
            QMetaProperty metaProp = iface->metaObject()->property(index);
            QVariant value = metaProp.isReadable() ? metaProp.read(iface) : QVariant();
            QVariant expected = d->value;

            if (metaProp.isEnumType() && expected.type() == QVariant::String) {
                QMetaEnum metaEnum = metaProp.enumerator();
                int value = metaEnum.keysToValue(d->value.toString().toLatin1());
                if (value >= 0) { // No value found for these keys, resetting expected to invalid
                    expected = value;
                } else {
                    expected = QVariant();
                }
            }

            if (d->compOperator == Mask) {
                bool v_ok;
                int v = value.toInt(&v_ok);
                bool e_ok;
                int e = expected.toInt(&e_ok);

                return (e_ok && v_ok && (v & e));
            } else {
                return (value == expected);
            }
        }
        break;
    }
    case InterfaceCheck:
        return device.isDeviceInterface(d->ifaceType);
    }

    return false;
}

QSet<Solid::DeviceInterface::Type> Solid::Predicate::usedTypes() const
{
    QSet<DeviceInterface::Type> res;

    if (d->isValid) {

        switch (d->type) {
        case Disjunction:
        case Conjunction:
            res += d->operand1->usedTypes();
            res += d->operand2->usedTypes();
            break;
        case PropertyCheck:
        case InterfaceCheck:
            res << d->ifaceType;
            break;
        }

    }

    return res;
}

QString Solid::Predicate::toString() const
{
    if (!d->isValid) {
        return "False";
    }

    if (d->type != PropertyCheck && d->type != InterfaceCheck) {
        QString op = " AND ";
        if (d->type == Disjunction) {
            op = " OR ";
        }

        return '[' + d->operand1->toString() + op + d->operand2->toString() + ']';
    } else {
        QString ifaceName = DeviceInterface::typeToString(d->ifaceType);

        if (ifaceName.isEmpty()) {
            ifaceName = "Unknown";
        }

        if (d->type == InterfaceCheck) {
            return "IS " + ifaceName;
        }

        QString value;

        switch (d->value.type()) {
        case QVariant::StringList: {
            value = '{';

            const QStringList list = d->value.toStringList();

            QStringList::ConstIterator it = list.begin();
            QStringList::ConstIterator end = list.end();

            for (; it != end; ++it) {
                value += '\'' + *it + '\'';

                if (it + 1 != end) {
                    value += ", ";
                }
            }

            value += '}';
            break;
        }
        case QVariant::Bool:
            value = (d->value.toBool() ? "true" : "false");
            break;
        case QVariant::Int:
        case QVariant::UInt:
        case QVariant::LongLong:
        case QVariant::ULongLong:
            value = d->value.toString();
            break;
        default:
            value = '\'' + d->value.toString() + '\'';
            break;
        }

        QString str_operator = "==";
        if (d->compOperator != Equals) {
            str_operator = " &";
        }

        return ifaceName + '.' + d->property + ' ' + str_operator + ' ' + value;
    }
}

Solid::Predicate::Type Solid::Predicate::type() const
{
    return d->type;
}

Solid::DeviceInterface::Type Solid::Predicate::interfaceType() const
{
    return d->ifaceType;
}

QString Solid::Predicate::propertyName() const
{
    return d->property;
}

QVariant Solid::Predicate::matchingValue() const
{
    return d->value;
}

Solid::Predicate::ComparisonOperator Solid::Predicate::comparisonOperator() const
{
    return d->compOperator;
}

Solid::Predicate Solid::Predicate::firstOperand() const
{
    if (d->operand1) {
        return *d->operand1;
    }
    return Predicate();
}

Solid::Predicate Solid::Predicate::secondOperand() const
{
    if (d->operand2) {
        return *d->operand2;
    }
    return Predicate();
}

