/*
    KWin - the KDE window manager
    This file is part of the KDE project.

    SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>

    SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "clientmodel.h"
#include <config-kwin.h>
#include "abstract_client.h"
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
#include "screens.h"
#include "virtualdesktops.h"
#include "workspace.h"

namespace KWin::ScriptingModels::V2 {

static quint32 nextId() {
    static quint32 counter = 0;
    return ++counter;
}

ClientLevel::ClientLevel(ClientModel *model, AbstractLevel *parent)
    : AbstractLevel(model, parent)
{
#if KWIN_BUILD_ACTIVITIES
    if (Activities *activities = Activities::self()) {
        connect(activities, &Activities::currentChanged, this, &ClientLevel::reInit);
    }
#endif
    connect(VirtualDesktopManager::self(), &VirtualDesktopManager::currentChanged, this, &ClientLevel::reInit);
    connect(Workspace::self(), &Workspace::clientAdded, this, &ClientLevel::clientAdded);
    connect(Workspace::self(), &Workspace::clientRemoved, this, &ClientLevel::clientRemoved);
    connect(model, &ClientModel::exclusionsChanged, this, &ClientLevel::reInit);
}

ClientLevel::~ClientLevel()
{
}

void ClientLevel::clientAdded(AbstractClient *client)
{
    setupClientConnections(client);
    checkClient(client);
}

void ClientLevel::clientRemoved(AbstractClient *client)
{
    removeClient(client);
}

void ClientLevel::setupClientConnections(AbstractClient *client)
{
    auto check = [this, client] {
        checkClient(client);
    };
    connect(client, &AbstractClient::desktopChanged, this, check);
    connect(client, &AbstractClient::screenChanged, this, check);
    connect(client, &AbstractClient::activitiesChanged, this, check);
    connect(client, &AbstractClient::windowHidden, this, check);
    connect(client, &AbstractClient::windowShown, this, check);
}

void ClientLevel::checkClient(AbstractClient *client)
{
    const bool shouldInclude = !exclude(client) && shouldAdd(client);
    const bool contains = containsClient(client);

    if (shouldInclude && !contains) {
        addClient(client);
    } else if (!shouldInclude && contains) {
        removeClient(client);
    }
}

bool ClientLevel::exclude(AbstractClient *client) const
{
    ClientModel::Exclusions exclusions = model()->exclusions();
    if (exclusions == ClientModel::NoExclusion) {
        return false;
    }
    if (exclusions & ClientModel::DesktopWindowsExclusion) {
        if (client->isDesktop()) {
            return true;
        }
    }
    if (exclusions & ClientModel::DockWindowsExclusion) {
        if (client->isDock()) {
            return true;
        }
    }
    if (exclusions & ClientModel::UtilityWindowsExclusion) {
        if (client->isUtility()) {
            return true;
        }
    }
    if (exclusions & ClientModel::SpecialWindowsExclusion) {
        if (client->isSpecialWindow()) {
            return true;
        }
    }
    if (exclusions & ClientModel::SkipTaskbarExclusion) {
        if (client->skipTaskbar()) {
            return true;
        }
    }
    if (exclusions & ClientModel::SkipPagerExclusion) {
        if (client->skipPager()) {
            return true;
        }
    }
    if (exclusions & ClientModel::SwitchSwitcherExclusion) {
        if (client->skipSwitcher()) {
            return true;
        }
    }
    if (exclusions & ClientModel::OtherDesktopsExclusion) {
        if (!client->isOnCurrentDesktop()) {
            return true;
        }
    }
    if (exclusions & ClientModel::OtherActivitiesExclusion) {
        if (!client->isOnCurrentActivity()) {
            return true;
        }
    }
    if (exclusions & ClientModel::MinimizedExclusion) {
        if (client->isMinimized()) {
            return true;
        }
    }
    if (exclusions & ClientModel::NotAcceptingFocusExclusion) {
        if (!client->wantsInput()) {
            return true;
        }
    }
    return false;
}

bool ClientLevel::shouldAdd(AbstractClient *client) const
{
    if (restrictions() == ClientModel::NoRestriction) {
        return true;
    }
    if (restrictions() & ClientModel::ActivityRestriction) {
        if (!client->isOnActivity(activity())) {
            return false;
        }
    }
    if (restrictions() & ClientModel::VirtualDesktopRestriction) {
        if (!client->isOnDesktop(virtualDesktop())) {
            return false;
        }
    }
    if (restrictions() & ClientModel::ScreenRestriction) {
        if (client->screen() != int(screen())) {
            return false;
        }
    }
    return true;
}

void ClientLevel::addClient(AbstractClient *client)
{
    if (containsClient(client)) {
        return;
    }
    Q_EMIT beginInsert(m_clients.count(), m_clients.count(), id());
    m_clients.insert(nextId(), client);
    Q_EMIT endInsert();
}

void ClientLevel::removeClient(AbstractClient *client)
{
    int index = 0;
    auto it = m_clients.begin();
    for (; it != m_clients.end(); ++it, ++index) {
        if (it.value() == client) {
            break;
        }
    }
    if (it == m_clients.end()) {
        return;
    }
    Q_EMIT beginRemove(index, index, id());
    m_clients.erase(it);
    Q_EMIT endRemove();
}

void ClientLevel::init()
{
    const QList<AbstractClient *> &clients = workspace()->allClientList();
    for (auto it = clients.begin(); it != clients.end(); ++it) {
        AbstractClient *client = *it;
        setupClientConnections(client);
        if (!exclude(client) && shouldAdd(client)) {
            m_clients.insert(nextId(), client);
        }
    }
}

void ClientLevel::reInit()
{
    const QList<AbstractClient *> &clients = workspace()->allClientList();
    for (auto it = clients.begin(); it != clients.end(); ++it) {
        checkClient((*it));
    }
}

quint32 ClientLevel::idForRow(int row) const
{
    if (row >= m_clients.size()) {
        return 0;
    }
    auto it = m_clients.constBegin();
    for (int i=0; i<row; ++i) {
        ++it;
    }
    return it.key();
}

bool ClientLevel::containsId(quint32 id) const
{
    return m_clients.contains(id);
}

int ClientLevel::rowForId(quint32 id) const
{
    int row = 0;
    for (auto it = m_clients.constBegin();
            it != m_clients.constEnd();
            ++it, ++row) {
        if (it.key() == id) {
            return row;
        }
    }
    return -1;
}

AbstractClient *ClientLevel::clientForId(quint32 child) const
{
    auto it = m_clients.constFind(child);
    if (it == m_clients.constEnd()) {
        return nullptr;
    }
    return it.value();
}

bool ClientLevel::containsClient(AbstractClient *client) const
{
    for (auto it = m_clients.constBegin();
            it != m_clients.constEnd();
            ++it) {
        if (it.value() == client) {
            return true;
        }
    }
    return false;
}

const AbstractLevel *ClientLevel::levelForId(quint32 id) const
{
    if (id == AbstractLevel::id()) {
        return this;
    }
    return nullptr;
}

AbstractLevel *ClientLevel::parentForId(quint32 child) const
{
    if (child == id()) {
        return parentLevel();
    }
    if (m_clients.contains(child)) {
        return const_cast<ClientLevel*>(this);
    }
    return nullptr;
}

AbstractLevel *AbstractLevel::create(const QList< ClientModel::LevelRestriction > &restrictions, ClientModel::LevelRestrictions parentRestrictions, ClientModel *model, AbstractLevel *parent)
{
    if (restrictions.isEmpty() || restrictions.first() == ClientModel::NoRestriction) {
        ClientLevel *leaf = new ClientLevel(model, parent);
        leaf->setRestrictions(parentRestrictions);
        if (!parent) {
            leaf->setParent(model);
        }
        return leaf;
    }
    // create a level
    QList<ClientModel::LevelRestriction> childRestrictions(restrictions);
    ClientModel::LevelRestriction restriction = childRestrictions.takeFirst();
    ClientModel::LevelRestrictions childrenRestrictions = restriction | parentRestrictions;
    ForkLevel *currentLevel = new ForkLevel(childRestrictions, model, parent);
    currentLevel->setRestrictions(childrenRestrictions);
    currentLevel->setRestriction(restriction);
    if (!parent) {
        currentLevel->setParent(model);
    }
    switch (restriction) {
    case ClientModel::ActivityRestriction: {
#ifdef KWIN_BUILD_ACTIVITIES
        if (Activities::self()) {
            const QStringList &activities = Activities::self()->all();
            for (QStringList::const_iterator it = activities.begin(); it != activities.end(); ++it) {
                AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel);
                if (!childLevel) {
                    continue;
                }
                childLevel->setActivity(*it);
                currentLevel->addChild(childLevel);
            }
        }
        break;
#else
        return nullptr;
#endif
    }
    case ClientModel::ScreenRestriction:
        for (int i=0; i<screens()->count(); ++i) {
            AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel);
            if (!childLevel) {
                continue;
            }
            childLevel->setScreen(i);
            currentLevel->addChild(childLevel);
        }
        break;
    case ClientModel::VirtualDesktopRestriction:
        for (uint i=1; i<=VirtualDesktopManager::self()->count(); ++i) {
            AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel);
            if (!childLevel) {
                continue;
            }
            childLevel->setVirtualDesktop(i);
            currentLevel->addChild(childLevel);
        }
        break;
    default:
        // invalid
        return nullptr;
    }

    return currentLevel;
}

AbstractLevel::AbstractLevel(ClientModel *model, AbstractLevel *parent)
    : QObject(parent)
    , m_model(model)
    , m_parent(parent)
    , m_screen(0)
    , m_virtualDesktop(0)
    , m_activity()
    , m_restriction(ClientModel::ClientModel::NoRestriction)
    , m_restrictions(ClientModel::NoRestriction)
    , m_id(nextId())
{
}

AbstractLevel::~AbstractLevel()
{
}

void AbstractLevel::setRestriction(ClientModel::LevelRestriction restriction)
{
    m_restriction = restriction;
}

void AbstractLevel::setActivity(const QString &activity)
{
    m_activity = activity;
}

void AbstractLevel::setScreen(uint screen)
{
    m_screen = screen;
}

void AbstractLevel::setVirtualDesktop(uint virtualDesktop)
{
    m_virtualDesktop = virtualDesktop;
}

void AbstractLevel::setRestrictions(ClientModel::LevelRestrictions restrictions)
{
    m_restrictions = restrictions;
}

ForkLevel::ForkLevel(const QList<ClientModel::LevelRestriction> &childRestrictions, ClientModel *model, AbstractLevel *parent)
    : AbstractLevel(model, parent)
    , m_childRestrictions(childRestrictions)
{
    connect(VirtualDesktopManager::self(), &VirtualDesktopManager::countChanged, this, &ForkLevel::desktopCountChanged);
    connect(screens(), &Screens::countChanged, this, &ForkLevel::screenCountChanged);
#ifdef KWIN_BUILD_ACTIVITIES
    if (Activities *activities = Activities::self()) {
        connect(activities, &Activities::added, this, &ForkLevel::activityAdded);
        connect(activities, &Activities::removed, this, &ForkLevel::activityRemoved);
    }
#endif
}

ForkLevel::~ForkLevel()
{
}

void ForkLevel::desktopCountChanged(uint previousCount, uint newCount)
{
    if (restriction() != ClientModel::ClientModel::VirtualDesktopRestriction) {
        return;
    }
    if (previousCount != uint(count())) {
        return;
    }
    if (previousCount > newCount) {
        // desktops got removed
        Q_EMIT beginRemove(newCount, previousCount-1, id());
        while (uint(m_children.count()) > newCount) {
            delete m_children.takeLast();
        }
        Q_EMIT endRemove();
    } else {
        // desktops got added
        Q_EMIT beginInsert(previousCount, newCount-1, id());
        for (uint i=previousCount+1; i<=newCount; ++i) {
            AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this);
            if (!childLevel) {
                continue;
            }
            childLevel->setVirtualDesktop(i);
            childLevel->init();
            addChild(childLevel);
        }
        Q_EMIT endInsert();
    }
}

void ForkLevel::screenCountChanged(int previousCount, int newCount)
{
    if (restriction() != ClientModel::ClientModel::ClientModel::ScreenRestriction) {
        return;
    }
    if (newCount == previousCount || previousCount != count()) {
        return;
    }

    if (previousCount > newCount) {
        // screens got removed
        Q_EMIT beginRemove(newCount, previousCount-1, id());
        while (m_children.count() > newCount) {
            delete m_children.takeLast();
        }
        Q_EMIT endRemove();
    } else {
        // screens got added
        Q_EMIT beginInsert(previousCount, newCount-1, id());
        for (int i=previousCount; i<newCount; ++i) {
            AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this);
            if (!childLevel) {
                continue;
            }
            childLevel->setScreen(i);
            childLevel->init();
            addChild(childLevel);
        }
        Q_EMIT endInsert();
    }
}

void ForkLevel::activityAdded(const QString &activityId)
{
#ifdef KWIN_BUILD_ACTIVITIES
    if (restriction() != ClientModel::ClientModel::ActivityRestriction) {
        return;
    }
    // verify that our children do not contain this activity
    for (AbstractLevel *child : qAsConst(m_children)) {
        if (child->activity() == activityId) {
            return;
        }
    }
    Q_EMIT beginInsert(m_children.count(), m_children.count(), id());
    AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this);
    if (!childLevel) {
        Q_EMIT endInsert();
        return;
    }
    childLevel->setActivity(activityId);
    childLevel->init();
    addChild(childLevel);
    Q_EMIT endInsert();
#else
    Q_UNUSED(activityId)
#endif
}

void ForkLevel::activityRemoved(const QString &activityId)
{
#ifdef KWIN_BUILD_ACTIVITIES
    if (restriction() != ClientModel::ClientModel::ActivityRestriction) {
        return;
    }
    for (int i=0; i<m_children.length(); ++i) {
        if (m_children.at(i)->activity() == activityId) {
            Q_EMIT beginRemove(i, i, id());
            delete m_children.takeAt(i);
            Q_EMIT endRemove();
            break;
        }
    }
#else
    Q_UNUSED(activityId)
#endif
}

int ForkLevel::count() const
{
    return m_children.count();
}

void ForkLevel::addChild(AbstractLevel *child)
{
    m_children.append(child);
    connect(child, &AbstractLevel::beginInsert, this, &AbstractLevel::beginInsert);
    connect(child, &AbstractLevel::beginRemove, this, &AbstractLevel::beginRemove);
    connect(child, &AbstractLevel::endInsert, this, &AbstractLevel::endInsert);
    connect(child, &AbstractLevel::endRemove, this, &AbstractLevel::endRemove);
}

void ForkLevel::setActivity(const QString &activity)
{
    AbstractLevel::setActivity(activity);
    for (QList<AbstractLevel*>::iterator it = m_children.begin(); it != m_children.end(); ++it) {
        (*it)->setActivity(activity);
    }
}

void ForkLevel::setScreen(uint screen)
{
    AbstractLevel::setScreen(screen);
    for (QList<AbstractLevel*>::iterator it = m_children.begin(); it != m_children.end(); ++it) {
        (*it)->setScreen(screen);
    }
}

void ForkLevel::setVirtualDesktop(uint virtualDesktop)
{
    AbstractLevel::setVirtualDesktop(virtualDesktop);
    for (QList<AbstractLevel*>::iterator it = m_children.begin(); it != m_children.end(); ++it) {
        (*it)->setVirtualDesktop(virtualDesktop);
    }
}

void ForkLevel::init()
{
    for (QList<AbstractLevel*>::iterator it = m_children.begin(); it != m_children.end(); ++it) {
        (*it)->init();
    }
}

quint32 ForkLevel::idForRow(int row) const
{
    if (row >= m_children.length()) {
        return 0;
    }
    return m_children.at(row)->id();
}

const AbstractLevel *ForkLevel::levelForId(quint32 id) const
{
    if (id == AbstractLevel::id()) {
        return this;
    }
    for (QList<AbstractLevel*>::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) {
        if (const AbstractLevel *child = (*it)->levelForId(id)) {
            return child;
        }
    }
    // not found
    return nullptr;
}

AbstractLevel *ForkLevel::parentForId(quint32 child) const
{
    if (child == id()) {
        return parentLevel();
    }
    for (QList<AbstractLevel*>::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) {
        if (AbstractLevel *parent = (*it)->parentForId(child)) {
            return parent;
        }
    }
    // not found
    return nullptr;
}

int ForkLevel::rowForId(quint32 child) const
{
    if (id() == child) {
        return 0;
    }
    for (int i=0; i<m_children.count(); ++i) {
        if (m_children.at(i)->id() == child) {
            return i;
        }
    }
    // do recursion
    for (QList<AbstractLevel*>::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) {
        int row = (*it)->rowForId(child);
        if (row != -1) {
            return  row;
        }
    }
    // not found
    return -1;
}

AbstractClient *ForkLevel::clientForId(quint32 child) const
{
    for (QList<AbstractLevel*>::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) {
        if (AbstractClient *client = (*it)->clientForId(child)) {
            return client;
        }
    }
    // not found
    return nullptr;
}

ClientModel::ClientModel(QObject *parent)
    : QAbstractItemModel(parent)
    , m_root(nullptr)
    , m_exclusions(NoExclusion)
{
}

ClientModel::~ClientModel()
{
}

void ClientModel::setLevels(QList< ClientModel::LevelRestriction > restrictions)
{
    beginResetModel();
    if (m_root) {
        delete m_root;
    }
    m_root = AbstractLevel::create(restrictions, NoRestriction, this);
    connect(m_root, &AbstractLevel::beginInsert, this, &ClientModel::levelBeginInsert);
    connect(m_root, &AbstractLevel::beginRemove, this, &ClientModel::levelBeginRemove);
    connect(m_root, &AbstractLevel::endInsert, this, &ClientModel::levelEndInsert);
    connect(m_root, &AbstractLevel::endRemove, this, &ClientModel::levelEndRemove);
    m_root->init();
    endResetModel();
}

void ClientModel::setExclusions(ClientModel::Exclusions exclusions)
{
    if (exclusions == m_exclusions) {
        return;
    }
    m_exclusions = exclusions;
    Q_EMIT exclusionsChanged();
}

QVariant ClientModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid() || index.column() != 0) {
        return QVariant();
    }
    if (const AbstractLevel *level = getLevel(index)) {
        LevelRestriction restriction = level->restriction();
        if (restriction == ActivityRestriction && (role == Qt::DisplayRole || role == ActivityRole)) {
            return level->activity();
        } else if (restriction == VirtualDesktopRestriction && (role == Qt::DisplayRole || role == DesktopRole)) {
            return level->virtualDesktop();
        } else if (restriction ==ScreenRestriction && (role == Qt::DisplayRole || role == ScreenRole)) {
            return level->screen();
        } else {
            return QVariant();
        }
    }
    if (role == Qt::DisplayRole || role == ClientRole) {
        if (AbstractClient *client = m_root->clientForId(index.internalId())) {
            return QVariant::fromValue(client);
        }
    }
    return QVariant();
}

int ClientModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return 1;
}

int ClientModel::rowCount(const QModelIndex &parent) const
{
    if (!m_root) {
        return 0;
    }
    if (!parent.isValid()) {
        return m_root->count();
    }
    if (const AbstractLevel *level = getLevel(parent)) {
        if (level->id() != parent.internalId()) {
            // not a real level - no children
            return 0;
        }
        return level->count();
    }
    return 0;
}

QHash<int, QByteArray> ClientModel::roleNames() const
{
    return {
        { Qt::DisplayRole, QByteArrayLiteral("display") },
        { ClientRole, QByteArrayLiteral("client") },
        { ScreenRole, QByteArrayLiteral("screen") },
        { DesktopRole, QByteArrayLiteral("desktop") },
        { ActivityRole, QByteArrayLiteral("activity") },
    };
}

QModelIndex ClientModel::parent(const QModelIndex &child) const
{
    if (!child.isValid() || child.column() != 0) {
        return QModelIndex();
    }
    return parentForId(child.internalId());
}

QModelIndex ClientModel::parentForId(quint32 childId) const
{
    if (childId == m_root->id()) {
        // asking for parent of our toplevel
        return QModelIndex();
    }
    if (AbstractLevel *parentLevel = m_root->parentForId(childId)) {
        if (parentLevel == m_root) {
            return QModelIndex();
        }
        const int row = m_root->rowForId(parentLevel->id());
        if (row == -1) {
            // error
            return QModelIndex();
        }
        return createIndex(row, 0, parentLevel->id());
    }
    return QModelIndex();
}

QModelIndex ClientModel::index(int row, int column, const QModelIndex &parent) const
{
    if (column != 0 || row < 0 || !m_root) {
        return QModelIndex();
    }
    if (!parent.isValid()) {
        if (row >= rowCount()) {
            return QModelIndex();
        }
        return createIndex(row, 0, m_root->idForRow(row));
    }
    const AbstractLevel *parentLevel = getLevel(parent);
    if (!parentLevel) {
        return QModelIndex();
    }
    if (row >= parentLevel->count()) {
        return QModelIndex();
    }
    const quint32 id = parentLevel->idForRow(row);
    if (id == 0) {
        return QModelIndex();
    }
    return createIndex(row, column, id);
}

const AbstractLevel *ClientModel::getLevel(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return m_root;
    }
    return m_root->levelForId(index.internalId());
}

void ClientModel::levelBeginInsert(int rowStart, int rowEnd, quint32 id)
{
    const int row = m_root->rowForId(id);
    QModelIndex parent;
    if (row != -1) {
        parent = createIndex(row, 0, id);
    }
    beginInsertRows(parent, rowStart, rowEnd);
}

void ClientModel::levelBeginRemove(int rowStart, int rowEnd, quint32 id)
{
    const int row = m_root->rowForId(id);
    QModelIndex parent;
    if (row != -1) {
        parent = createIndex(row, 0, id);
    }
    beginRemoveRows(parent, rowStart, rowEnd);
}

void ClientModel::levelEndInsert()
{
    endInsertRows();
}

void ClientModel::levelEndRemove()
{
    endRemoveRows();
}

#define CLIENT_MODEL_WRAPPER(name, levels) \
name::name(QObject *parent) \
    : ClientModel(parent) \
{ \
    setLevels(levels); \
} \
name::~name() {}

CLIENT_MODEL_WRAPPER(SimpleClientModel, QList<LevelRestriction>())
CLIENT_MODEL_WRAPPER(ClientModelByScreen, QList<LevelRestriction>() << ScreenRestriction)
CLIENT_MODEL_WRAPPER(ClientModelByScreenAndDesktop, QList<LevelRestriction>() << ScreenRestriction << VirtualDesktopRestriction)
CLIENT_MODEL_WRAPPER(ClientModelByScreenAndActivity, QList<LevelRestriction>() << ScreenRestriction << ActivityRestriction)
#undef CLIENT_MODEL_WRAPPER

ClientFilterModel::ClientFilterModel(QObject *parent)
    : QSortFilterProxyModel(parent)
    , m_clientModel(nullptr)
{
}

ClientFilterModel::~ClientFilterModel()
{
}

void ClientFilterModel::setClientModel(ClientModel *clientModel)
{
    if (clientModel == m_clientModel) {
        return;
    }
    m_clientModel = clientModel;
    setSourceModel(m_clientModel);
    Q_EMIT clientModelChanged();
}

void ClientFilterModel::setFilter(const QString &filter)
{
    if (filter == m_filter) {
        return;
    }
    m_filter = filter;
    Q_EMIT filterChanged();
    invalidateFilter();
}

bool ClientFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
    if (!m_clientModel) {
        return false;
    }
    if (m_filter.isEmpty()) {
        return true;
    }
    QModelIndex index = m_clientModel->index(sourceRow, 0, sourceParent);
    if (!index.isValid()) {
        return false;
    }
    QVariant data = index.data();
    if (!data.isValid()) {
        // an invalid QVariant is valid data
        return true;
    }
    // TODO: introduce a type as a data role and properly check, this seems dangerous
    if (data.type() == QVariant::Int || data.type() == QVariant::UInt || data.type() == QVariant::String) {
        // we do not filter out screen, desktop and activity
        return true;
    }
    AbstractClient *client = qvariant_cast<KWin::AbstractClient *>(data);
    if (!client) {
        return false;
    }
    if (client->caption().contains(m_filter, Qt::CaseInsensitive)) {
        return true;
    }
    const QString windowRole(QString::fromUtf8(client->windowRole()));
    if (windowRole.contains(m_filter, Qt::CaseInsensitive)) {
        return true;
    }
    const QString resourceName(QString::fromUtf8(client->resourceName()));
    if (resourceName.contains(m_filter, Qt::CaseInsensitive)) {
        return true;
    }
    const QString resourceClass(QString::fromUtf8(client->resourceClass()));
    if (resourceClass.contains(m_filter, Qt::CaseInsensitive)) {
        return true;
    }
    return false;
}

} // namespace KWin
