// SPDX-License-Identifier: GPL-3.0-or-later

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea.
*
* nymea is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea. If not, see <https://www.gnu.org/licenses/>.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "integrationpluginmock.h"
#include "httpdaemon.h"

#include "types/mediabrowseritem.h"
#include "integrations/thing.h"
#include "integrations/thingdiscoveryinfo.h"
#include "integrations/thingpairinginfo.h"
#include "integrations/thingsetupinfo.h"
#include "integrations/thingactioninfo.h"
#include "integrations/browseresult.h"
#include "integrations/browseritemresult.h"
#include "integrations/browseractioninfo.h"
#include "integrations/browseritemactioninfo.h"
#include "plugininfo.h"

#include "network/networkaccessmanager.h"

#include <QDebug>
#include <QColor>
#include <QStringList>
#include <QTimer>
#include <QUrlQuery>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>

IntegrationPluginMock::IntegrationPluginMock()
{
    generateBrowseItems();
}

IntegrationPluginMock::~IntegrationPluginMock()
{
    delete m_virtualFs;
}

void IntegrationPluginMock::discoverThings(ThingDiscoveryInfo *info)
{
    if (info->thingClassId() == mockThingClassId) {
        qCDebug(dcMock()) << "starting mock discovery:" << info->params();
        m_discoveredDeviceCount = info->params().paramValue(mockDiscoveryResultCountParamTypeId).toInt();
        QTimer::singleShot(1000, info, [this, info](){
            generateDiscoveredDevices(info);
        });
        return;
    }

    if (info->thingClassId() == pushButtonMockThingClassId) {
        qCDebug(dcMock()) << "starting mock push button discovery:" << info->params();
        m_discoveredDeviceCount = info->params().paramValue(pushButtonMockDiscoveryResultCountParamTypeId).toInt();
        QTimer::singleShot(1000, info, [this, info]() {
            generateDiscoveredPushButtonDevices(info);
        });
        return;
    }

    if (info->thingClassId() == displayPinMockThingClassId) {
        qCDebug(dcMock()) << "starting mock display pin discovery:" << info->params();
        m_discoveredDeviceCount = info->params().paramValue(displayPinMockDiscoveryResultCountParamTypeId).toInt();
        QTimer::singleShot(1000, info, [this, info]() {
            generateDiscoveredDisplayPinDevices(info);
        });
        return;
    }

    if (info->thingClassId() == parentMockThingClassId) {
        qCDebug(dcMock()) << "Starting discovery for mocked parent thing";
        QTimer::singleShot(1000, info, [info](){
            ThingDescriptor descriptor(parentMockThingClassId, "Mocked Thing Parent (Discovered)");
            info->addThingDescriptor(descriptor);
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }

    if (info->thingClassId() == childMockThingClassId) {
        QTimer::singleShot(1000, info, [this, info](){
            if (!myThings().filterByThingClassId(parentMockThingClassId).isEmpty()) {
                Thing *parent = myThings().filterByThingClassId(parentMockThingClassId).first();
                ThingDescriptor descriptor(childMockThingClassId, "Mocked Thing Child (Discovered)", QString(), parent->id());
                info->addThingDescriptor(descriptor);
            }
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }

    if (info->thingClassId() == userAndPassMockThingClassId) {
        QTimer::singleShot(1000, info, [this, info](){
            if (myThings().filterByThingClassId(userAndPassMockThingClassId).isEmpty()) {
                ThingDescriptor descriptor(userAndPassMockThingClassId, "Mocked Thing User & Password (Discovered)", QString());
                info->addThingDescriptor(descriptor);
            }
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }

    if (info->thingClassId() == networkDeviceMockThingClassId) {
        qCDebug(dcMock()) << "starting network device mock discovery:" << info->params();
        QTimer::singleShot(1000, info, [info](){
            QString resultType = info->params().paramValue(networkDeviceMockDiscoveryResultTypeParamTypeId).toString();
            ParamList params;
            if (resultType == "MAC address") {
                params.append(Param(networkDeviceMockThingMacAddressParamTypeId, "00:11:22:33:44:55"));
            } else if (resultType == "Host name") {
                params.append(Param(networkDeviceMockThingHostNameParamTypeId, "hostname.localhost"));
            } else if (resultType == "IP address") {
                params.append(Param(networkDeviceMockThingAddressParamTypeId, "127.0.0.1"));
            }

            ThingDescriptor descriptor(networkDeviceMockThingClassId, "Mocked Thing (networkdevice)", QString());
            descriptor.setParams(params);
            info->addThingDescriptor(descriptor);
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }

    qCWarning(dcMock()) << "Cannot discover for ThingClassId" << info->thingClassId();
    info->finish(Thing::ThingErrorThingNotFound);
}

void IntegrationPluginMock::setupThing(ThingSetupInfo *info)
{
    if (info->thing()->thingClassId() == mockThingClassId || info->thing()->thingClassId() == autoMockThingClassId) {
        if (m_daemons.contains(info->thing())) {
            // We already have a daemon, seems we're reconfiguring
            delete m_daemons.take(info->thing());
        }

        bool async = false;
        bool broken = false;
        if (info->thing()->thingClassId() == mockThingClassId) {
            async = info->thing()->paramValue(mockThingAsyncParamTypeId).toBool();
            broken = info->thing()->paramValue(mockThingBrokenParamTypeId).toBool();
        } else {
            async = info->thing()->paramValue(autoMockThingAsyncParamTypeId).toBool();
            broken = info->thing()->paramValue(autoMockThingBrokenParamTypeId).toBool();
        }
        qCDebug(dcMock()) << "SetupThing for" << info->thing()->name() << "Async:" << async << "Broken:" << broken;

        if (!async && broken) {
            qCWarning(dcMock()) << "This thing is intentionally broken.";
            info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("This mocked thing is intentionally broken."));
            return;
        }

        if (!broken) {
            HttpDaemon *daemon = new HttpDaemon(info->thing(), this);
            m_daemons.insert(info->thing(), daemon);

            if (!daemon->isListening()) {
                qCWarning(dcMock()) << "HTTP port opening failed:" << info->thing()->paramValue(mockThingHttpportParamTypeId).toInt();
                info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("Failed to open HTTP port. Port in use?"));
                return;
            }

            connect(daemon, &HttpDaemon::triggerEvent, this, &IntegrationPluginMock::triggerEvent);
            connect(daemon, &HttpDaemon::setState, this, &IntegrationPluginMock::setState);
            // Keep this queued or it might happen that the HttpDaemon is deleted before it is able to reply to the caller
            connect(daemon, &HttpDaemon::disappear, this, &IntegrationPluginMock::onDisappear, Qt::QueuedConnection);
            connect(daemon, &HttpDaemon::reconfigureAutodevice, this, &IntegrationPluginMock::onReconfigureAutoDevice, Qt::QueuedConnection);
        }


        if (async) {
            QTimer::singleShot(1000, info, [info](){
                qCDebug(dcMock()) << "Finishing thing setup for mocked thing" << info->thing()->name();
                if (info->thing()->paramValue(mockThingBrokenParamTypeId).toBool()) {
                    info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("This mocked thing is intentionally broken."));
                } else {
                    info->finish(Thing::ThingErrorNoError);
                }
            });

            connect(info, &ThingSetupInfo::aborted, this, [=](){
                qCDebug(dcMock()) << "Setup aborted. Destroying webserver" << info->thing()->name();
                delete m_daemons.take(info->thing());
            });

            return;
        }
        qCDebug(dcMock()) << "Setup complete" << info->thing()->name();
        info->finish(Thing::ThingErrorNoError);
        Thing *thing = info->thing();
        if (info->thing()->thingClassId() == mockThingClassId) {
            connect(info->thing(), &Thing::settingChanged, this, [thing](const ParamTypeId &settingTypeId, const QVariant &value) {
                if (settingTypeId == mockSettingsIntStateWithLimitsMinValueParamTypeId) {
                    thing->setStateMinValue(mockIntWithLimitsStateTypeId, value);
                }
                if (settingTypeId == mockSettingsIntStateWithLimitsMaxValueParamTypeId) {
                    thing->setStateMaxValue(mockIntWithLimitsStateTypeId, value);
                }
            });
        }

        return;
    }

    if (info->thing()->thingClassId() == pushButtonMockThingClassId) {
        qCDebug(dcMock()) << "Setup PushButton mock thing" << info->thing()->params();
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == displayPinMockThingClassId) {
        qCDebug(dcMock()) << "Setup DisplayPin mock thing" << info->thing()->params();
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == parentMockThingClassId) {
        qCDebug(dcMock()) << "Setup Parent mock thing" << info->thing()->params();
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == childMockThingClassId) {
        qCDebug(dcMock()) << "Setup Child mock thing" << info->thing()->params();
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == inputTypeMockThingClassId) {
        qCDebug(dcMock()) << "Setup InputType mock thing" << info->thing()->params();
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == userAndPassMockThingClassId) {
        qCDebug(dcMock()) << "Setup User and password mock thing";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == oAuthGoogleMockThingClassId) {
        qCDebug(dcMock()) << "Google OAuth setup complete";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == oAuthSonosMockThingClassId) {
        qCDebug(dcMock()) << "Sonos OAuth setup complete";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == genericIoMockThingClassId) {
        qCDebug(dcMock()) << "Generic IO mock setup complete";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == virtualIoLightMockThingClassId) {
        qCDebug(dcMock()) << "Virtual IO mock light setup complete";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == virtualIoTemperatureSensorMockThingClassId) {
        qCDebug(dcMock()) << "Virtual IO mock temperature sensor setup complete";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == networkDeviceMockThingClassId) {
        qCDebug(dcMock()) << "Network device mock setup complete";
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    qCWarning(dcMock()) << "Unhandled thing class" << info->thing()->thingClass();
    info->finish(Thing::ThingErrorThingClassNotFound);
}

void IntegrationPluginMock::postSetupThing(Thing *thing)
{
    qCDebug(dcMock()) << "Postsetup mock" << thing->name();
    if (thing->thingClassId() == parentMockThingClassId) {
        foreach (Thing *d, myThings()) {
            if (d->thingClassId() == childMockThingClassId && d->parentId() == thing->id()) {
                return;
            }
        }

        ThingDescriptor mockDescriptor(childMockThingClassId, "Mocked Thing Child (Auto created)", "Mocked Thing Child (Auto created)", thing->id());
        emit autoThingsAppeared(ThingDescriptors() << mockDescriptor);
    }
}

void IntegrationPluginMock::thingRemoved(Thing *thing)
{
    qCDebug(dcMock()) << "Thing removed" << thing->name();
    delete m_daemons.take(thing);
}

void IntegrationPluginMock::startMonitoringAutoThings()
{
    foreach (Thing *device, myThings()) {
        if (device->thingClassId() == autoMockThingClassId) {
            return; // We already have a Auto Mock device... do nothing.
        }
    }

    ThingDescriptor mockDescriptor(autoMockThingClassId, "Mocked Thing (Auto created)");

    ParamList params;
    std::srand(QDateTime::currentMSecsSinceEpoch());
    int port = 4242 + (std::rand() % 1000);
    Param param(autoMockThingHttpportParamTypeId, port);
    params.append(param);
    mockDescriptor.setParams(params);

    QList<ThingDescriptor> deviceDescriptorList;
    deviceDescriptorList.append(mockDescriptor);

    emit autoThingsAppeared(deviceDescriptorList);
}

void IntegrationPluginMock::startPairing(ThingPairingInfo *info)
{
    if (info->thingClassId() == pushButtonMockThingClassId) {
        qCDebug(dcMock()) << "Push button. Pressing the button in 3 seconds.";
        info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Wait 3 second before you continue, the push button will be pressed automatically."));
        m_pushbuttonPressed = false;
        QTimer::singleShot(3000, this, SLOT(onPushButtonPressed()));
        return;
    }

    if (info->thingClassId() == displayPinMockThingClassId) {
        qCDebug(dcMock()) << "Display pin!! The pin is 243681";
        info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter the secret which normaly will be displayed on the device. For this mocked thing the pin is 243681."));
        return;
    }

    if (info->thingClassId() == userAndPassMockThingClassId) {
        qCDebug(dcMock()) << "User and password. Login is \"user\" and \"password\".";
        info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("Please enter login credentials for the mocked thing (\"user\" and \"password\")."));
        return;
    }

    if (info->thingClassId() == oAuthSonosMockThingClassId) {
        QString clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
        QString clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11";

        QUrl url("https://api.sonos.com/login/v3/oauth");
        QUrlQuery queryParams;
        queryParams.addQueryItem("client_id", clientId);
        queryParams.addQueryItem("redirect_uri", "https://127.0.0.1:8888");
        queryParams.addQueryItem("response_type", "code");
        queryParams.addQueryItem("scope", "playback-control-all");
        queryParams.addQueryItem("state", "ya-ya");
        url.setQuery(queryParams);

        qCDebug(dcMock()) << "Sonos url:" << url;

        info->setOAuthUrl(url);
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thingClassId() == oAuthGoogleMockThingClassId) {
        QString clientId= "937667874529-pr6s5ciu6sfnnqmt2sppvb6rokbkjjta.apps.googleusercontent.com";
        QString clientSecret = "1ByBRmNqaK08VC54eEVcnGf1";

        QUrl url("https://accounts.google.com/o/oauth2/v2/auth");
        QUrlQuery queryParams;
        queryParams.addQueryItem("client_id", clientId);
        queryParams.addQueryItem("redirect_uri", "https://127.0.0.1:8888");
        queryParams.addQueryItem("response_type", "code");
        queryParams.addQueryItem("scope", "profile email");
        queryParams.addQueryItem("state", "ya-ya");
        url.setQuery(queryParams);

        info->setOAuthUrl(url);
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    info->finish(Thing::ThingErrorCreationMethodNotSupported);
}

void IntegrationPluginMock::confirmPairing(ThingPairingInfo *info, const QString &username, const QString &secret)
{
    qCDebug(dcMock()) << "Confirm pairing";

    if (info->thingClassId() == pushButtonMockThingClassId) {
        if (!m_pushbuttonPressed) {
            qCDebug(dcMock()) << "PushButton not pressed yet!";
            info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("The push button has not been pressed."));
            return;
        }

        QTimer::singleShot(1000, this, [info](){
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }

    if (info->thingClassId() == displayPinMockThingClassId) {
        if (secret != "243681") {
            qCWarning(dcMock()) << "Invalid pin:" << secret;
            info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Invalid PIN!"));
            return;
        }
        QTimer::singleShot(500, this, [info](){
            qCDebug(dcMock()) << "Pairing finished.";
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }

    if (info->thingClassId() == userAndPassMockThingClassId) {
        qCDebug(dcMock()) << "Credentials received:" << username << secret;
        if (username == "user" && secret == "password") {
            info->finish(Thing::ThingErrorNoError);
            return;
        } else {
            info->finish(Thing::ThingErrorAuthenticationFailure, QT_TR_NOOP("Wrong username or password"));
            return;
        }
    }


    if (info->thingClassId() == oAuthSonosMockThingClassId) {
        qCDebug(dcMock()) << "Secret is" << secret;
        QUrl url(secret);
        QUrlQuery query(url);
        qCDebug(dcMock()) << "Acess code is:" << query.queryItemValue("code");

        QString accessCode = query.queryItemValue("code");

        // Obtaining access token
        url = QUrl("https://api.sonos.com/login/v3/oauth/access");
        query.clear();
        query.addQueryItem("grant_type", "authorization_code");
        query.addQueryItem("code", accessCode);
        query.addQueryItem("redirect_uri", QByteArray("https://127.0.0.1:8888").toPercentEncoding());
        url.setQuery(query);

        QNetworkRequest request(url);
        request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded;charset=utf-8");

        QByteArray clientId = "b15cbf8c-a39c-47aa-bd93-635a96e9696c";
        QByteArray clientSecret = "c086ba71-e562-430b-a52f-867c6482fd11";

        QByteArray auth = QByteArray(clientId + ':' + clientSecret).toBase64(QByteArray::Base64Encoding | QByteArray::KeepTrailingEquals);
        request.setRawHeader("Authorization", QString("Basic %1").arg(QString(auth)).toUtf8());

        QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
        connect(reply, &QNetworkReply::finished, this, [this, reply, info](){
            reply->deleteLater();

            QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
            qCDebug(dcMock()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
            qCDebug(dcMock()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
            qCDebug(dcMock()) << "expires at" << QDateTime::currentDateTime().addSecs(jsonDoc.toVariant().toMap().value("expires_in").toInt()).toString();
            qCDebug(dcMock()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString();
            info->finish(Thing::ThingErrorNoError);
        });

        return;
    }


    if (info->thingClassId() == oAuthGoogleMockThingClassId) {
        qCDebug(dcMock()) << "Secret is" << secret;
        QUrl url(secret);
        QUrlQuery query(url);
        qCDebug(dcMock()) << "Acess code is:" << query.queryItemValue("code");

        QString accessCode = query.queryItemValue("code");

        // Obtaining access token
        QString clientId = "937667874529-pr6s5ciu6sfnnqmt2sppvb6rokbkjjta.apps.googleusercontent.com";
        QString clientSecret = "1ByBRmNqaK08VC54eEVcnGf1";

        url = QUrl("https://www.googleapis.com/oauth2/v4/token");
        query.clear();
        query.addQueryItem("code", accessCode);
        query.addQueryItem("client_id", clientId);
        query.addQueryItem("client_secret", clientSecret);
        query.addQueryItem("grant_type", "authorization_code");
        query.addQueryItem("redirect_uri", QByteArray("https://127.0.0.1:8888").toPercentEncoding());
//        query.addQueryItem("code_verifier", codeVerifier);
        url.setQuery(query);

        QNetworkRequest request(url);

        QNetworkReply *reply = hardwareManager()->networkManager()->post(request, QByteArray());
        connect(reply, &QNetworkReply::finished, this, [this, reply, info](){
            reply->deleteLater();

            QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll());
            qCDebug(dcMock()) << "Sonos accessToken reply:" << this << reply->error() << reply->errorString() << jsonDoc.toJson();
            qCDebug(dcMock()) << "Access token:" << jsonDoc.toVariant().toMap().value("access_token").toString();
            qCDebug(dcMock()) << "expires at" << QDateTime::currentDateTime().addSecs(jsonDoc.toVariant().toMap().value("expires_in").toInt()).toString();
            qCDebug(dcMock()) << "Refresh token:" << jsonDoc.toVariant().toMap().value("refresh_token").toString();
            qCDebug(dcMock()) << "ID token:" << jsonDoc.toVariant().toMap().value("id_token").toString();
            info->finish(Thing::ThingErrorNoError);
        });
        return;
    }


    qCWarning(dcMock()) << "Invalid ThingClassId -> no pairing possible with this thing";
    info->finish(Thing::ThingErrorThingClassNotFound);
}

void IntegrationPluginMock::browseThing(BrowseResult *result)
{
    qCDebug(dcMock()) << "Browse thing called" << result->thing();
    if (result->thing()->thingClassId() == mockThingClassId) {
        if (result->thing()->paramValue(mockThingAsyncParamTypeId).toBool()) {

            QTimer::singleShot(1000, result, [this, result]() {
                if (result->thing()->paramValue(mockThingBrokenParamTypeId).toBool()) {
                    result->finish(Thing::ThingErrorHardwareFailure);
                    return;
                }

                VirtualFsNode *node = m_virtualFs->findNode(result->itemId());
                if (!node) {
                    result->finish(Thing::ThingErrorItemNotFound);
                    return;
                }

                foreach (VirtualFsNode *child, node->childs) {
                    result->addItem(child->item);
                }

                result->finish(Thing::ThingErrorNoError);
            });

            return;
        }

        if (result->thing()->paramValue(mockThingBrokenParamTypeId).toBool()) {
            result->finish(Thing::ThingErrorHardwareFailure);
            return;
        }

        VirtualFsNode *node = m_virtualFs->findNode(result->itemId());
        if (!node) {
            result->finish(Thing::ThingErrorItemNotFound);
            return;
        }

        foreach (VirtualFsNode *child, node->childs) {
            result->addItem(child->item);
        }
        result->finish(Thing::ThingErrorNoError);
        return;
    }
    result->finish(Thing::ThingErrorInvalidParameter);
}

void IntegrationPluginMock::browserItem(BrowserItemResult *result)
{
    VirtualFsNode *node = m_virtualFs->findNode(result->itemId());
    if (!node) {
        result->finish(Thing::ThingErrorItemNotFound);
        return;
    }
    result->finish(node->item);
}

void IntegrationPluginMock::executeAction(ThingActionInfo *info)
{
    if (info->thing()->thingClassId() == mockThingClassId) {
        if (info->action().actionTypeId() == mockAsyncActionTypeId || info->action().actionTypeId() == mockAsyncFailingActionTypeId) {
            QTimer::singleShot(1000, info->thing(), [this, info](){
                if (info->action().actionTypeId() == mockAsyncActionTypeId) {
                    m_daemons.value(info->thing())->actionExecuted(info->action().actionTypeId());
                    info->finish(Thing::ThingErrorNoError);
                } else if (info->action().actionTypeId() == mockAsyncFailingActionTypeId) {
                    info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("This mock action is intentionally broken."));
                }

            });
            return;
        }

        if (info->action().actionTypeId() == mockIntWithLimitsActionTypeId) {
            info->thing()->setStateValue(mockIntWithLimitsStateTypeId, info->action().paramValue(mockIntWithLimitsActionIntWithLimitsParamTypeId));
            info->finish(Thing::ThingErrorNoError);
            return;
        }

        if (info->action().actionTypeId() == mockFailingActionTypeId) {
            info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("This mock action is intentionally broken."));
            return;
        }

        if (info->action().actionTypeId() == mockPowerActionTypeId) {
            qCDebug(dcMock()) << "Setting power to" << info->action().param(mockPowerActionPowerParamTypeId).value().toBool();
            info->thing()->setStateValue(mockPowerStateTypeId, info->action().param(mockPowerActionPowerParamTypeId).value().toBool());
        }

        if (info->action().actionTypeId() == mockBatteryLevelActionTypeId) {
            int newLevel = info->action().param(mockBatteryLevelActionBatteryLevelParamTypeId).value().toInt();
            qCDebug(dcMock()) << "Setting battery level to" << newLevel;
            info->thing()->setStateValue(mockBatteryLevelStateTypeId, newLevel);
            info->thing()->setStateValue(mockBatteryCriticalStateTypeId, newLevel < 10);
        }

        if (info->action().actionTypeId() == mockSignalStrengthActionTypeId) {
            int newStrength = info->action().param(mockSignalStrengthActionSignalStrengthParamTypeId).value().toInt();
            qCDebug(dcMock()) << "Setting signal strength to" << newStrength;
            info->thing()->setStateValue(mockSignalStrengthStateTypeId, newStrength);
            info->thing()->setStateValue(mockConnectedStateTypeId, newStrength != 0);
        }

        if (info->action().actionTypeId() == mockUpdateStatusActionTypeId) {
            QString newUpdateStatus = info->action().param(mockUpdateStatusActionUpdateStatusParamTypeId).value().toString();
            qCDebug(dcMock()) << "Setting update status to" << newUpdateStatus;
            info->thing()->setStateValue(mockUpdateStatusStateTypeId, newUpdateStatus);
            info->thing()->setStateValue(mockAvailableVersionStateTypeId, newUpdateStatus == "available" ? "2.0" : "1.0");
        }
        m_daemons.value(info->thing())->actionExecuted(info->action().actionTypeId());
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == autoMockThingClassId) {
        if (info->action().actionTypeId() == autoMockMockActionAsyncActionTypeId || info->action().actionTypeId() == autoMockMockActionAsyncBrokenActionTypeId) {
            QTimer::singleShot(1000, info->thing(), [this, info](){
                if (info->action().actionTypeId() == autoMockMockActionAsyncBrokenActionTypeId) {
                    info->finish(Thing::ThingErrorSetupFailed, QT_TR_NOOP("This mock action is intentionally broken."));
                } else {
                    m_daemons.value(info->thing())->actionExecuted(info->action().actionTypeId());
                    info->finish(Thing::ThingErrorNoError);
                }
            });
            return;
        }

        if (info->action().actionTypeId() == autoMockMockActionBrokenActionTypeId) {
            info->finish(Thing::ThingErrorSetupFailed);
            return;
        }

        m_daemons.value(info->thing())->actionExecuted(info->action().actionTypeId());
        info->finish(Thing::ThingErrorNoError);
    } else if (info->thing()->thingClassId() == pushButtonMockThingClassId) {
        if (info->action().actionTypeId() == pushButtonMockColorActionTypeId) {
            QString colorString = info->action().param(pushButtonMockColorActionColorParamTypeId).value().toString();
            QColor color(colorString);
            if (!color.isValid()) {
                qCWarning(dcMock()) << "Invalid color parameter";
                info->finish(Thing::ThingErrorInvalidParameter);
                return;
            }
            info->thing()->setStateValue(pushButtonMockColorStateTypeId, colorString);
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == pushButtonMockPercentageActionTypeId) {
            info->thing()->setStateValue(pushButtonMockPercentageStateTypeId, info->action().param(pushButtonMockPercentageActionPercentageParamTypeId).value().toInt());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == pushButtonMockAllowedValuesActionTypeId) {
            info->thing()->setStateValue(pushButtonMockAllowedValuesStateTypeId, info->action().param(pushButtonMockAllowedValuesActionAllowedValuesParamTypeId).value().toString());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == pushButtonMockDoubleActionTypeId) {
            info->thing()->setStateValue(pushButtonMockDoubleStateTypeId, info->action().param(pushButtonMockDoubleActionDoubleParamTypeId).value().toDouble());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == pushButtonMockBoolActionTypeId) {
            info->thing()->setStateValue(pushButtonMockBoolStateTypeId, info->action().param(pushButtonMockBoolActionBoolParamTypeId).value().toBool());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == pushButtonMockTimeoutActionTypeId) {
            // Not finishing action intentionally...
            return;
        }
        info->finish(Thing::ThingErrorActionTypeNotFound);
        return;
    } else if (info->thing()->thingClassId() == displayPinMockThingClassId) {
        if (info->action().actionTypeId() == displayPinMockColorActionTypeId) {
            QString colorString = info->action().param(displayPinMockColorActionColorParamTypeId).value().toString();
            QColor color(colorString);
            if (!color.isValid()) {
                qCWarning(dcMock()) << "Invalid color parameter";
                info->finish(Thing::ThingErrorInvalidParameter);
                return;
            }
            info->thing()->setStateValue(displayPinMockColorStateTypeId, colorString);
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == displayPinMockPercentageActionTypeId) {
            info->thing()->setStateValue(displayPinMockPercentageStateTypeId, info->action().param(displayPinMockPercentageActionPercentageParamTypeId).value().toInt());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == displayPinMockAllowedValuesActionTypeId) {
            info->thing()->setStateValue(displayPinMockAllowedValuesStateTypeId, info->action().param(displayPinMockAllowedValuesActionAllowedValuesParamTypeId).value().toString());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == displayPinMockDoubleActionTypeId) {
            info->thing()->setStateValue(displayPinMockDoubleStateTypeId, info->action().param(displayPinMockDoubleActionDoubleParamTypeId).value().toDouble());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == displayPinMockBoolActionTypeId) {
            info->thing()->setStateValue(displayPinMockBoolStateTypeId, info->action().param(displayPinMockBoolActionBoolParamTypeId).value().toBool());
            info->finish(Thing::ThingErrorNoError);
            return;
        } else if (info->action().actionTypeId() == displayPinMockTimeoutActionTypeId) {
            // Not finishing action intentionally...
            return;
        }
        info->finish(Thing::ThingErrorActionTypeNotFound);
        return;
    } else if (info->thing()->thingClassId() == parentMockThingClassId) {
        if (info->action().actionTypeId() == parentMockBoolValueActionTypeId) {
            info->thing()->setStateValue(parentMockBoolValueStateTypeId, info->action().param(parentMockBoolValueActionBoolValueParamTypeId).value().toBool());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
        info->finish(Thing::ThingErrorActionTypeNotFound);
        return;
    } else if (info->thing()->thingClassId() == childMockThingClassId) {
        if (info->action().actionTypeId() == childMockBoolValueActionTypeId) {
            info->thing()->setStateValue(childMockBoolValueStateTypeId, info->action().param(childMockBoolValueActionBoolValueParamTypeId).value().toBool());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
        info->finish(Thing::ThingErrorActionTypeNotFound);
        return;
    } else if (info->thing()->thingClassId() == inputTypeMockThingClassId) {
        if (info->action().actionTypeId() == inputTypeMockWritableBoolActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableBoolStateTypeId, info->action().param(inputTypeMockWritableBoolActionWritableBoolParamTypeId).value().toULongLong());
        } else if (info->action().actionTypeId() == inputTypeMockWritableIntActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableIntStateTypeId, info->action().param(inputTypeMockWritableIntActionWritableIntParamTypeId).value().toLongLong());
        } else if (info->action().actionTypeId() == inputTypeMockWritableIntMinMaxActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableIntMinMaxStateTypeId, info->action().param(inputTypeMockWritableIntMinMaxActionWritableIntMinMaxParamTypeId).value().toLongLong());
        } else if (info->action().actionTypeId() == inputTypeMockWritableUIntActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableUIntStateTypeId, info->action().param(inputTypeMockWritableUIntActionWritableUIntParamTypeId).value().toULongLong());
        } else if (info->action().actionTypeId() == inputTypeMockWritableUIntMinMaxActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableUIntMinMaxStateTypeId, info->action().param(inputTypeMockWritableUIntMinMaxActionWritableUIntMinMaxParamTypeId).value().toLongLong());
        } else if (info->action().actionTypeId() == inputTypeMockWritableDoubleActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableDoubleStateTypeId, info->action().param(inputTypeMockWritableDoubleActionWritableDoubleParamTypeId).value().toDouble());
        } else if (info->action().actionTypeId() == inputTypeMockWritableDoubleMinMaxActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableDoubleMinMaxStateTypeId, info->action().param(inputTypeMockWritableDoubleMinMaxActionWritableDoubleMinMaxParamTypeId).value().toDouble());
        } else if (info->action().actionTypeId() == inputTypeMockWritableStringActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableStringStateTypeId, info->action().param(inputTypeMockWritableStringActionWritableStringParamTypeId).value().toString());
        } else if (info->action().actionTypeId() == inputTypeMockWritableStringSelectionActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableStringSelectionStateTypeId, info->action().param(inputTypeMockWritableStringSelectionActionWritableStringSelectionParamTypeId).value().toString());
        } else if (info->action().actionTypeId() == inputTypeMockWritableColorActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableColorStateTypeId, info->action().param(inputTypeMockWritableColorActionWritableColorParamTypeId).value().toString());
        } else if (info->action().actionTypeId() == inputTypeMockWritableTimeActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableTimeStateTypeId, info->action().param(inputTypeMockWritableTimeActionWritableTimeParamTypeId).value().toTime());
        } else if (info->action().actionTypeId() == inputTypeMockWritableTimestampIntActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableTimestampIntStateTypeId, info->action().param(inputTypeMockWritableTimestampIntActionWritableTimestampIntParamTypeId).value().toLongLong());
        } else if (info->action().actionTypeId() == inputTypeMockWritableTimestampUIntActionTypeId) {
            info->thing()->setStateValue(inputTypeMockWritableTimestampUIntStateTypeId, info->action().param(inputTypeMockWritableTimestampUIntActionWritableTimestampUIntParamTypeId).value().toULongLong());
        } else if (info->action().actionTypeId() == inputTypeMockLocalizedListActionTypeId) {
            info->thing()->setStateValue(inputTypeMockLocalizedListStateTypeId, info->action().paramValue(inputTypeMockLocalizedListActionLocalizedListParamTypeId).toString());
        }
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->thing()->thingClassId() == virtualIoLightMockThingClassId) {
        if (info->action().actionTypeId() == virtualIoLightMockPowerActionTypeId) {
            qCDebug(dcMock()) << "ExecuteAction for virtual light power action with param" << info->action().param(virtualIoLightMockPowerActionPowerParamTypeId).value();
            info->thing()->setStateValue(virtualIoLightMockPowerStateTypeId, info->action().param(virtualIoLightMockPowerActionPowerParamTypeId).value());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
    }

    if (info->thing()->thingClassId() == genericIoMockThingClassId) {
        if (info->action().actionTypeId() == genericIoMockDigitalOutput1ActionTypeId) {
            qCDebug(dcMock()) << "Setting digital output 1 to" << info->action().param(genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId).value().toBool();
            info->thing()->setStateValue(genericIoMockDigitalOutput1StateTypeId, info->action().param(genericIoMockDigitalOutput1ActionDigitalOutput1ParamTypeId).value().toBool());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
        if (info->action().actionTypeId() == genericIoMockDigitalOutput2ActionTypeId) {
            info->thing()->setStateValue(genericIoMockDigitalOutput2StateTypeId, info->action().param(genericIoMockDigitalOutput2ActionDigitalOutput2ParamTypeId).value().toBool());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
        if (info->action().actionTypeId() == genericIoMockAnalogInput1ActionTypeId) {
            qCDebug(dcMock()) << "ExecuteAction for virtual io analog in 1 action with param" << info->action().param(genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId).value();
            info->thing()->setStateValue(genericIoMockAnalogInput1StateTypeId, info->action().param(genericIoMockAnalogInput1ActionAnalogInput1ParamTypeId).value());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
        if (info->action().actionTypeId() == genericIoMockAnalogOutput1ActionTypeId) {
            info->thing()->setStateValue(genericIoMockAnalogOutput1StateTypeId, info->action().param(genericIoMockAnalogOutput1ActionAnalogOutput1ParamTypeId).value());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
        if (info->action().actionTypeId() == genericIoMockAnalogOutput2ActionTypeId) {
            info->thing()->setStateValue(genericIoMockAnalogOutput2StateTypeId, info->action().param(genericIoMockAnalogOutput2ActionAnalogOutput2ParamTypeId).value());
            info->finish(Thing::ThingErrorNoError);
            return;
        }
    }

    if (info->thing()->thingClassId() == virtualIoTemperatureSensorMockThingClassId) {
        if (info->action().actionTypeId() == virtualIoTemperatureSensorMockInputActionTypeId) {
            double value = info->action().param(virtualIoTemperatureSensorMockInputActionInputParamTypeId).value().toDouble();
            info->thing()->setStateValue(virtualIoTemperatureSensorMockInputStateTypeId, value);
            double minTemp = info->thing()->setting(virtualIoTemperatureSensorMockSettingsMinTempParamTypeId).toDouble();
            double maxTemp = info->thing()->setting(virtualIoTemperatureSensorMockSettingsMaxTempParamTypeId).toDouble();
            double temp = minTemp + (maxTemp - minTemp) * value;
            info->thing()->setStateValue(virtualIoTemperatureSensorMockTemperatureStateTypeId, temp);
            info->finish(Thing::ThingErrorNoError);
            return;
        }
    }

    qCWarning(dcMock()) << "Unhandled executeAction call in mock plugin!";
}

void IntegrationPluginMock::executeBrowserItem(BrowserActionInfo *info)
{
    qCDebug(dcMock()) << "ExecuteBrowserItem called" << info->browserAction().itemId();
    bool broken = info->thing()->paramValue(mockThingBrokenParamTypeId).toBool();
    bool async = info->thing()->paramValue(mockThingAsyncParamTypeId).toBool();

    VirtualFsNode *node = m_virtualFs->findNode(info->browserAction().itemId());
    if (!node) {
        info->finish(Thing::ThingErrorItemNotFound);
        return;
    }

    if (!node->item.executable()) {
        info->finish(Thing::ThingErrorItemNotExecutable);
        return;
    }

    if (!async){
        if (broken) {
            info->finish(Thing::ThingErrorHardwareFailure);
            return;
        }
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    QTimer::singleShot(2000, info, [broken, info](){
        info->finish(broken ? Thing::ThingErrorHardwareFailure : Thing::ThingErrorNoError);
    });

}

void IntegrationPluginMock::executeBrowserItemAction(BrowserItemActionInfo *info)
{
    if (info->browserItemAction().actionTypeId() == mockAddToFavoritesBrowserItemActionTypeId) {

        VirtualFsNode *node = m_virtualFs->findNode(info->browserItemAction().itemId());
        if (!node) {
            info->finish(Thing::ThingErrorInvalidParameter);
            return;
        }

        VirtualFsNode *favoritesNode = m_virtualFs->findNode("favorites");
        if (favoritesNode->findNode(info->browserItemAction().itemId())) {
            info->finish(Thing::ThingErrorThingInUse);
            return;
        }
        BrowserItem newItem = node->item;
        newItem.setActionTypeIds({mockRemoveFromFavoritesBrowserItemActionTypeId});
        VirtualFsNode *newNode = new VirtualFsNode(newItem);
        favoritesNode->addChild(newNode);
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    if (info->browserItemAction().actionTypeId() == mockRemoveFromFavoritesBrowserItemActionTypeId) {
        VirtualFsNode *favoritesNode = m_virtualFs->findNode("favorites");
        VirtualFsNode *nodeToRemove = favoritesNode->findNode(info->browserItemAction().itemId());
        if (!nodeToRemove) {
            info->finish(Thing::ThingErrorItemNotFound);
            return;
        }
        int idx = favoritesNode->childs.indexOf(nodeToRemove);
        delete favoritesNode->childs.takeAt(idx);
        info->finish(Thing::ThingErrorNoError);
        return;
    }

    info->finish(Thing::ThingErrorActionTypeNotFound);
}

void IntegrationPluginMock::setState(const StateTypeId &stateTypeId, const QVariant &value)
{
    HttpDaemon *daemon = qobject_cast<HttpDaemon*>(sender());
    if (!daemon)
        return;

    Thing *device = m_daemons.key(daemon);
    device->setStateValue(stateTypeId, value);
}

void IntegrationPluginMock::triggerEvent(const EventTypeId &id, const ParamList &params)
{
    HttpDaemon *daemon = qobject_cast<HttpDaemon*>(sender());
    if (!daemon)
        return;

    Thing *device = m_daemons.key(daemon);

    qCDebug(dcMock) << "Emitting event " << id << params;
    device->emitEvent(id, params);
}

void IntegrationPluginMock::onDisappear()
{
    HttpDaemon *daemon = qobject_cast<HttpDaemon*>(sender());
    if (!daemon) {
        return;
    }
    Thing *device = m_daemons.key(daemon);
    qCDebug(dcMock) << "Emitting autoDeviceDisappeared for device" << device->id();
    emit autoThingDisappeared(device->id());
}

void IntegrationPluginMock::onReconfigureAutoDevice()
{
    HttpDaemon *daemon = qobject_cast<HttpDaemon *>(sender());
    if (!daemon)
        return;

    Thing *device = m_daemons.key(daemon);
    qCDebug(dcMock()) << "Reconfigure auto device for" << device << device->params();

    int currentPort = device->params().paramValue(autoMockThingHttpportParamTypeId).toInt();

    // Note: the reconfigure makes the http server listen on port + 1
    ParamList params;
    params.append(Param(autoMockThingHttpportParamTypeId, currentPort + 1));

    ThingDescriptor deviceDescriptor(autoMockThingClassId);
    deviceDescriptor.setTitle(device->name() + " (reconfigured)");
    deviceDescriptor.setDescription("This auto device was reconfigured");
    deviceDescriptor.setThingId(device->id());
    deviceDescriptor.setParams(params);

    emit autoThingsAppeared({deviceDescriptor});
}

void IntegrationPluginMock::generateDiscoveredDevices(ThingDiscoveryInfo *info)
{
    if (m_discoveredDeviceCount > 0) {
        ThingDescriptor d1(mockThingClassId, "Mock Device 1 (Discovered)", "55555");
        ParamList params;
        Param httpParam(mockThingHttpportParamTypeId, "55555");
        params.append(httpParam);
        d1.setParams(params);
        foreach (Thing *d, myThings()) {
            if (d->thingClassId() == mockThingClassId && d->paramValue(mockThingHttpportParamTypeId).toInt() == 55555) {
                d1.setThingId(d->id());
                break;
            }
        }
        info->addThingDescriptor(d1);
    }

    if (m_discoveredDeviceCount > 1) {
        ThingDescriptor d2(mockThingClassId, "Mock Device 2 (Discovered)", "55556");
        ParamList params;
        Param httpParam(mockThingHttpportParamTypeId, "55556");
        params.append(httpParam);
        d2.setParams(params);
        foreach (Thing *d, myThings()) {
            if (d->thingClassId() == mockThingClassId && d->paramValue(mockThingHttpportParamTypeId).toInt() == 55556) {
                d2.setThingId(d->id());
                break;
            }
        }
        info->addThingDescriptor(d2);
    }

    info->finish(Thing::ThingErrorNoError);
}

void IntegrationPluginMock::generateDiscoveredPushButtonDevices(ThingDiscoveryInfo *info)
{
    if (m_discoveredDeviceCount > 0) {
        ThingDescriptor d1(pushButtonMockThingClassId, "Mocked Thing (Push Button)", "1");
        info->addThingDescriptor(d1);
    }

    if (m_discoveredDeviceCount > 1) {
        ThingDescriptor d2(pushButtonMockThingClassId, "Mocked Thhing (Push Button)", "2");
        info->addThingDescriptor(d2);
    }
    info->finish(Thing::ThingErrorNoError, QT_TR_NOOP("This thing will simulate a push button press in 3 seconds."));
}

void IntegrationPluginMock::generateDiscoveredDisplayPinDevices(ThingDiscoveryInfo *info)
{
    if (m_discoveredDeviceCount > 0) {
        ThingDescriptor d1(displayPinMockThingClassId, "Mocked Thing (Display Pin)", "1");
        foreach (Thing *existingDev, myThings()) {
            if (existingDev->thingClassId() == displayPinMockThingClassId) {
                d1.setThingId(existingDev->id());
                break;
            }
        }
        info->addThingDescriptor(d1);
    }

    if (m_discoveredDeviceCount > 1) {
        ThingDescriptor d2(displayPinMockThingClassId, "Mocked Thing (Display Pin)", "2");
        int count = 0;
        foreach (Thing *existingDev, myThings()) {
            if (existingDev->thingClassId() == displayPinMockThingClassId && ++count > 1) {
                d2.setThingId(existingDev->id());
                break;
            }
        }
        info->addThingDescriptor(d2);
    }

    info->finish(Thing::ThingErrorNoError);
}

void IntegrationPluginMock::onPushButtonPressed()
{
    qCDebug(dcMock) << "PushButton pressed (automatically)";
    m_pushbuttonPressed = true;
}

void IntegrationPluginMock::onPluginConfigChanged()
{

}

void IntegrationPluginMock::generateBrowseItems()
{
    m_virtualFs = new VirtualFsNode(BrowserItem());

    BrowserItem item = BrowserItem("001", "Item 0", true);
    item.setDescription("I'm a folder");
    item.setIcon(BrowserItem::BrowserIconFolder);
    VirtualFsNode *folderNode = new VirtualFsNode(item);
    m_virtualFs->addChild(folderNode);

    item = BrowserItem("002", "Item 1", false, true);
    item.setDescription("I'm executable");
    item.setIcon(BrowserItem::BrowserIconApplication);
    item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
    m_virtualFs->addChild(new VirtualFsNode(item));

    item = BrowserItem("003", "Item 2", false, true);
    item.setDescription("I'm a file");
    item.setIcon(BrowserItem::BrowserIconFile);
    item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
    m_virtualFs->addChild(new VirtualFsNode(item));

    item = BrowserItem("004", "Item 3", false, true);
    item.setDescription("I have a nice thumbnail");
    item.setIcon(BrowserItem::BrowserIconFile);
    item.setThumbnail("https://github.com/nymea/nymea/raw/master/icons/nymea-logo-256x256.png");
    item.setActionTypeIds({mockAddToFavoritesBrowserItemActionTypeId});
    m_virtualFs->addChild(new VirtualFsNode(item));

    item = BrowserItem("005", "Item 4", false, false);
    item.setDescription("I'm disabled");
    item.setDisabled(true);
    item.setIcon(BrowserItem::BrowserIconFile);
    m_virtualFs->addChild(new VirtualFsNode(item));

    item = BrowserItem("favorites", "Favorites", true, false);
    item.setDescription("Yay! I'm the best!");
    item.setIcon(BrowserItem::BrowserIconFavorites);
    m_virtualFs->addChild(new VirtualFsNode(item));

    item = BrowserItem("sub-001", "Item Subdir 1", false, true);
    item.setDescription("I'm an item in a subdir");
    item.setIcon(BrowserItem::BrowserIconFile);
    folderNode->addChild(new VirtualFsNode(item));

    item = BrowserItem("sub-002", "Item Subdir 2", true, false);
    item.setDescription("I'm a folder in a subdir");
    item.setIcon(BrowserItem::BrowserIconFile);
    folderNode->addChild(new VirtualFsNode(item));

    item = BrowserItem("mediaservices", "Media services", true, false);
    item.setDescription("I list media icons");
    item.setIcon(BrowserItem::BrowserIconMusic);
    VirtualFsNode *mediaNode = new VirtualFsNode(item);
    m_virtualFs->addChild(mediaNode);

    MediaBrowserItem mediaItem = MediaBrowserItem("playlist", "Playlists", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconPlaylist);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("recent", "Recently played", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconRecentlyPlayed);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("library", "Library", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconLibrary);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("musiclibrary", "Music Library", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconMusicLibrary);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("videolibrary", "Video library", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconVideoLibrary);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("picturelibrary", "picture library", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconPictureLibrary);
    mediaNode->addChild(new VirtualFsNode(mediaItem));


    mediaItem = MediaBrowserItem("disk", "CD", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconDisk);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("usb", "USB", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconUSB);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("network", "Network", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconNetwork);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("aux", "AUX", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconAux);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("spotify", "Spotify", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconSpotify);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("amazon", "Amazon Music", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconAmazon);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("tunein", "TuneIn", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconTuneIn);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("siriusxm", "Sirius XM", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconSiriusXM);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("vTuner", "vTuner", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconVTuner);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("tidal", "Tidal", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconTidal);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("airable", "airable", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconAirable);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("deezer", "Deezer", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconDeezer);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("napster", "Napster", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconNapster);
    mediaNode->addChild(new VirtualFsNode(mediaItem));

    mediaItem = MediaBrowserItem("soundcloud", "SoundCloud", false, false);
    mediaItem.setMediaIcon(MediaBrowserItem::MediaBrowserIconSoundCloud);
    mediaNode->addChild(new VirtualFsNode(mediaItem));
}
