/*COPYRIGHT_HEADER

This file is a part of nm-tray.

Copyright (c)
    2015~now Palo Kisa <palo.kisa@gmail.com>

nm-tray 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 2
of the License, or (at your option) any later version.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

COPYRIGHT_HEADER*/
#include "nmmodel.h"
#include "nmmodel_p.h"
#include "icons.h"
#include "log.h"

#include <NetworkManagerQt/GenericTypes>
#include <NetworkManagerQt/VpnConnection>
#include <NetworkManagerQt/WirelessDevice>
#include <NetworkManagerQt/AdslDevice>
#include <NetworkManagerQt/WiredDevice>
#include <NetworkManagerQt/WimaxDevice>
#include <NetworkManagerQt/VlanDevice>
#include <NetworkManagerQt/BondDevice>
#include <NetworkManagerQt/BridgeDevice>
#include <NetworkManagerQt/GenericDevice>
#include <NetworkManagerQt/InfinibandDevice>
#include <NetworkManagerQt/BluetoothDevice>
#include <NetworkManagerQt/OlpcMeshDevice>
#include <NetworkManagerQt/TeamDevice>
#include <NetworkManagerQt/WirelessSetting>
#include <NetworkManagerQt/WirelessSecuritySetting>
#include <NetworkManagerQt/Utils>
#include <NetworkManagerQt/ConnectionSettings>
#include <QDBusPendingCallWatcher>
#include <QInputDialog>

namespace
{
    NetworkManager::ConnectionSettings::Ptr assembleWpaXPskSettings(const NetworkManager::AccessPoint::Ptr accessPoint)
    {
        //TODO: enhance getting the password from user
        bool ok;
        QString password = QInputDialog::getText(nullptr, NmModel::tr("nm-tray - wireless password")
                , NmModel::tr("Password is needed for connection to '%1':").arg(accessPoint->ssid())
                , QLineEdit::Password, QString(), &ok);
        if (!ok)
            return NetworkManager::ConnectionSettings::Ptr{nullptr};

        NetworkManager::ConnectionSettings::Ptr settings{new NetworkManager::ConnectionSettings{NetworkManager::ConnectionSettings::Wireless}};
        settings->setId(accessPoint->ssid());
        settings->setUuid(NetworkManager::ConnectionSettings::createNewUuid());
        settings->setAutoconnect(true);
        //Note: workaround for wrongly (randomly) initialized gateway-ping-timeout
        settings->setGatewayPingTimeout(0);

        NetworkManager::WirelessSetting::Ptr wifi_sett
            = settings->setting(NetworkManager::Setting::Wireless).dynamicCast<NetworkManager::WirelessSetting>();
        wifi_sett->setInitialized(true);
        wifi_sett->setSsid(accessPoint->ssid().toUtf8());
        wifi_sett->setSecurity("802-11-wireless-security");

        NetworkManager::WirelessSecuritySetting::Ptr security_sett
            = settings->setting(NetworkManager::Setting::WirelessSecurity).dynamicCast<NetworkManager::WirelessSecuritySetting>();
        security_sett->setInitialized(true);
        if (NetworkManager::AccessPoint::Adhoc == accessPoint->mode())
        {
            wifi_sett->setMode(NetworkManager::WirelessSetting::Adhoc);
            security_sett->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaNone);
        } else
        {
            security_sett->setKeyMgmt(NetworkManager::WirelessSecuritySetting::WpaPsk);
        }
        security_sett->setPsk(password);
        return settings;
    }
}

NmModelPrivate::NmModelPrivate()
{
    insertActiveConnections();
    insertConnections();
    insertDevices();
    insertWifiNetworks();

    //initialize NetworkManager signals
    connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceAdded, this, &NmModelPrivate::onDeviceAdded);
    connect(NetworkManager::notifier(), &NetworkManager::Notifier::deviceRemoved, this, &NmModelPrivate::onDeviceRemoved);
    connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionAdded, this, &NmModelPrivate::onActiveConnectionAdded);
    connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &NmModelPrivate::onActiveConnectionRemoved);
    connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionAdded, this, &NmModelPrivate::onConnectionAdded);
    connect(NetworkManager::settingsNotifier(), &NetworkManager::SettingsNotifier::connectionRemoved, this, static_cast<void (NmModelPrivate::*)(QString const &)>(&NmModelPrivate::onConnectionRemoved));

    // Note: the connectionRemoved is never emitted in case network-manager service stop,
    // we need remove the connections manually.
    connect(NetworkManager::notifier(), &NetworkManager::Notifier::serviceDisappeared, this, &NmModelPrivate::clearConnections);
    
//qCDebug(NM_TRAY) << mActiveConns.size() << mConnections.size() << mDevices.size();
}

NmModelPrivate::~NmModelPrivate()
{
}

void NmModelPrivate::removeActiveConnection(int pos)
{
    //active connections signals
    NetworkManager::ActiveConnection::Ptr conn = mActiveConns.takeAt(pos);
    conn->disconnect(this);
}

void NmModelPrivate::clearActiveConnections()
{
    while (0 < mActiveConns.size())
        removeActiveConnection(0);
}

void NmModelPrivate::addActiveConnection(NetworkManager::ActiveConnection::Ptr conn)
{
    mActiveConns.push_back(conn);
    connect(conn.data(), &NetworkManager::ActiveConnection::connectionChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::default4Changed, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::default6Changed, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::dhcp4ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::dhcp6ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::ipV4ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::ipV6ConfigChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::idChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::typeChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::masterChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::specificObjectChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::stateChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::vpnChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::uuidChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    connect(conn.data(), &NetworkManager::ActiveConnection::devicesChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    if (conn->vpn())
    {
        connect(qobject_cast<NetworkManager::VpnConnection *>(conn.data()), &NetworkManager::VpnConnection::bannerChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
        connect(qobject_cast<NetworkManager::VpnConnection *>(conn.data()), &NetworkManager::VpnConnection::stateChanged, this, &NmModelPrivate::onActiveConnectionUpdated);
    }
}

void NmModelPrivate::insertActiveConnections()
{
    for (auto const & conn : NetworkManager::activeConnections())
        addActiveConnection(conn);
}

void NmModelPrivate::removeConnection(int pos)
{
    //connections signals
    NetworkManager::Connection::Ptr conn = mConnections.takeAt(pos);
    conn->disconnect(this);
}

void NmModelPrivate::clearConnections()
{
    while (0 < mConnections.size())
        removeConnection(0);
}

void NmModelPrivate::addConnection(NetworkManager::Connection::Ptr conn)
{
    mConnections.push_back(conn);
    //connections signals
    connect(conn.data(), &NetworkManager::Connection::updated, this, &NmModelPrivate::onConnectionUpdated);
    connect(conn.data(), &NetworkManager::Connection::removed, this, static_cast<void (NmModelPrivate::*)()>(&NmModelPrivate::onConnectionRemoved));
}

void NmModelPrivate::insertConnections()
{
    for (auto const & conn : NetworkManager::listConnections())
        addConnection(conn);
}

void NmModelPrivate::removeDevice(int pos)
{
    //connections signals
    NetworkManager::Device::Ptr device = mDevices.takeAt(pos);
    device->disconnect(this);
}

void NmModelPrivate::clearDevices()
{
    while (0 < mDevices.size())
        removeDevice(0);
}

void NmModelPrivate::addDevice(NetworkManager::Device::Ptr device)
{
    mDevices.push_back(device);
    //device signals
    connect(device.data(), &NetworkManager::Device::stateChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::activeConnectionChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::autoconnectChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::availableConnectionChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::availableConnectionAppeared, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::availableConnectionDisappeared, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::capabilitiesChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::dhcp4ConfigChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::dhcp6ConfigChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::driverChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::driverVersionChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::firmwareMissingChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::firmwareVersionChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::interfaceNameChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::ipV4AddressChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::ipV4ConfigChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::ipV6ConfigChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::ipInterfaceChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::managedChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::physicalPortIdChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::mtuChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::nmPluginMissingChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::meteredChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::connectionStateChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::stateReasonChanged, this, &NmModelPrivate::onDeviceUpdated);
    connect(device.data(), &NetworkManager::Device::udiChanged, this, &NmModelPrivate::onDeviceUpdated);
    switch (device->type())
    {
        case NetworkManager::Ethernet:
            connect(qobject_cast<NetworkManager::WiredDevice *>(device.data()), &NetworkManager::WiredDevice::bitRateChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WiredDevice *>(device.data()), &NetworkManager::WiredDevice::carrierChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WiredDevice *>(device.data()), &NetworkManager::WiredDevice::hardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WiredDevice *>(device.data()), &NetworkManager::WiredDevice::permanentHardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated);
            break;

        case NetworkManager::Device::Wifi:
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::bitRateChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::activeAccessPointChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::modeChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::wirelessCapabilitiesChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::hardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::permanentHardwareAddressChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::wirelessPropertiesChanged, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::accessPointAppeared, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::accessPointDisappeared, this, &NmModelPrivate::onDeviceUpdated);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::networkAppeared, this, &NmModelPrivate::onWifiNetworkAppeared);
            connect(qobject_cast<NetworkManager::WirelessDevice *>(device.data()), &NetworkManager::WirelessDevice::networkDisappeared, this, &NmModelPrivate::onWifiNetworkDisappeared);
            break;
        default:
            //TODO: other device types!
            break;
    }
}

void NmModelPrivate::insertDevices()
{
    for (auto const & device : NetworkManager::networkInterfaces())
        addDevice(device);
}

void NmModelPrivate::removeWifiNetwork(int pos)
{
    //network signals
    NetworkManager::WirelessNetwork::Ptr net = mWifiNets.takeAt(pos);
    net->disconnect(this);
}

void NmModelPrivate::clearWifiNetworks()
{
    while (0 < mWifiNets.size())
        removeWifiNetwork(0);
}

void NmModelPrivate::addWifiNetwork(NetworkManager::WirelessNetwork::Ptr net)
{
    mWifiNets.push_back(net);
    //device signals
    connect(net.data(), &NetworkManager::WirelessNetwork::signalStrengthChanged, this, &NmModelPrivate::onWifiNetworkUpdated);
    connect(net.data(), &NetworkManager::WirelessNetwork::referenceAccessPointChanged, this, &NmModelPrivate::onWifiNetworkUpdated);
    connect(net.data(), &NetworkManager::WirelessNetwork::disappeared, this, &NmModelPrivate::onWifiNetworkUpdated);
}

void NmModelPrivate::insertWifiNetworks()
{
    for (auto const & device : mDevices)
    {
        if (NetworkManager::Device::Wifi == device->type())
        {
            NetworkManager::WirelessDevice::Ptr w_dev = device.objectCast<NetworkManager::WirelessDevice>();
            for (auto const & net : w_dev->networks())
            {
                if (!net.isNull())
                {
                    addWifiNetwork(net);
                }
            }
        }
    }
}

NetworkManager::ActiveConnection::Ptr NmModelPrivate::findActiveConnection(QString const & path)
{
    auto i = std::find_if(mActiveConns.cbegin(), mActiveConns.cend(), [&path] (NetworkManager::ActiveConnection::Ptr const & conn) -> bool {
        return conn->path() == path;
    });
    return mActiveConns.cend() == i ? NetworkManager::ActiveConnection::Ptr{} : *i;
}

template <typename Predicate>
NetworkManager::Device::Ptr NmModelPrivate::findDevice(Predicate const & pred)
{
    auto i = std::find_if(mDevices.cbegin(), mDevices.cend(), pred);
    return mDevices.cend() == i ? NetworkManager::Device::Ptr{} : *i;
}

NetworkManager::Device::Ptr NmModelPrivate::findDeviceUni(QString const & uni)
{
    return findDevice([&uni] (NetworkManager::Device::Ptr const & dev) { return dev->uni() == uni; });
}

NetworkManager::Device::Ptr NmModelPrivate::findDeviceInterface(QString const & interfaceName)
{
    return findDevice([&interfaceName] (NetworkManager::Device::Ptr const & dev) { return dev->interfaceName() == interfaceName; });
}

NetworkManager::WirelessNetwork::Ptr NmModelPrivate::findWifiNetwork(QString const & ssid, QString const & devUni)
{
    auto i = std::find_if(mWifiNets.cbegin(), mWifiNets.cend(), [&ssid, &devUni] (NetworkManager::WirelessNetwork::Ptr const & net) -> bool {
        return net->ssid() == ssid && net->device() == devUni;
    });
    return mWifiNets.cend() == i ? NetworkManager::WirelessNetwork::Ptr{} : *i;
}

void NmModelPrivate::requestScan(NetworkManager::WirelessDevice * dev)
{
qCDebug(NM_TRAY) << __FUNCTION__ << dev->interfaceName();
    QDBusPendingReply<> reply = dev->requestScan();
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, dev);
    connect(watcher, &QDBusPendingCallWatcher::finished, [dev] (QDBusPendingCallWatcher * watcher) {
        if (watcher->isError() || !watcher->isValid())
        {
            //TODO: in what form should we output the warning messages
            qCWarning(NM_TRAY).noquote() << QStringLiteral("requestScan on device '%1' failed: %3").arg(dev->interfaceName())
                    .arg(watcher->error().message());
         }
         watcher->deleteLater();
    });
}

void NmModelPrivate::onConnectionUpdated()
{
    emit connectionUpdate(qobject_cast<NetworkManager::Connection *>(sender()));
}

void NmModelPrivate::onConnectionRemoved()
{
    emit connectionRemove(qobject_cast<NetworkManager::Connection *>(sender()));
}

void NmModelPrivate::onActiveConnectionUpdated()
{
    emit activeConnectionUpdate(qobject_cast<NetworkManager::ActiveConnection *>(sender()));
}

void NmModelPrivate::onDeviceUpdated()
{
    emit deviceUpdate(qobject_cast<NetworkManager::Device *>(sender()));
}

void NmModelPrivate::onWifiNetworkAppeared(QString const & ssid)
{
    NetworkManager::Device * dev = qobject_cast<NetworkManager::Device *>(sender());
    emit wifiNetworkAdd(dev, ssid);
    emit deviceUpdate(dev);
}

void NmModelPrivate::onWifiNetworkDisappeared(QString const & ssid)
{
    NetworkManager::Device * dev = qobject_cast<NetworkManager::Device *>(sender());
    emit wifiNetworkRemove(dev, ssid);
    emit deviceUpdate(dev);
}

void NmModelPrivate::onWifiNetworkUpdated()
{
    emit wifiNetworkUpdate(qobject_cast<NetworkManager::WirelessNetwork *>(sender()));
}

void NmModelPrivate::onDeviceAdded(QString const & uni)
{
    NetworkManager::Device::Ptr dev = NetworkManager::findNetworkInterface(uni);
    if (!dev.isNull())
    {
        if (dev->isValid())
            qCWarning(NM_TRAY).noquote() << Q_FUNC_INFO << uni << " is currently invalid...";
        emit deviceAdd(dev);
    }
}

void NmModelPrivate::onDeviceRemoved(QString const & uni)
{
    NetworkManager::Device::Ptr dev = findDeviceUni(uni);
    if (!dev.isNull())
    {
        if (dev->isValid())
            qCWarning(NM_TRAY).noquote() << Q_FUNC_INFO << uni << " is currently invalid...";
        emit deviceRemove(dev.data());
    }
}

void NmModelPrivate::onActiveConnectionAdded(QString const & path)
{
    NetworkManager::ActiveConnection::Ptr conn = NetworkManager::findActiveConnection(path);//XXX: const QString &uni
    if (!conn.isNull())
    {
        if (conn->isValid())
            qCWarning(NM_TRAY).noquote() << Q_FUNC_INFO << path << " is currently invalid...";
        emit activeConnectionAdd(conn);
    }
}

void NmModelPrivate::onActiveConnectionRemoved(QString const & path)
{
    NetworkManager::ActiveConnection::Ptr conn = findActiveConnection(path);//XXX: const QString &uni
    if (!conn.isNull())
    {
        if (conn->isValid())
            qCWarning(NM_TRAY).noquote() << Q_FUNC_INFO << path << " is currently invalid...";
        emit activeConnectionRemove(conn.data());
    }
}

void NmModelPrivate::onActiveConnectionsChanged()
{
    emit activeConnectionsReset();
}

void NmModelPrivate::onConnectionAdded(QString const & path)
{
    NetworkManager::Connection::Ptr conn = NetworkManager::findConnection(path);
    if (!conn.isNull())
    {
        if (conn->isValid())
            qCWarning(NM_TRAY).noquote() << Q_FUNC_INFO << path << " is currently invalid...";
        emit connectionAdd(conn);
    }
}

void NmModelPrivate::onConnectionRemoved(QString const & path)
{
    NetworkManager::Connection::Ptr conn = NetworkManager::findConnection(path);
    if (!conn.isNull())
    {
        if (conn->isValid())
            qCWarning(NM_TRAY).noquote() << Q_FUNC_INFO << path << " is currently invalid...";
        emit connectionRemove(conn.data());
    }
}




NmModel::NmModel(QObject * parent)
    : QAbstractItemModel(parent)
    , d{new NmModelPrivate}
{
    connect(d.data(), &NmModelPrivate::connectionAdd, [this] (NetworkManager::Connection::Ptr conn) {
//qCDebug(NM_TRAY) << "connectionAdd" << conn->name();
        if (0 > d->mConnections.indexOf(conn))
        {
            const int cnt = d->mConnections.size();
            beginInsertRows(createIndex(1, 0, ITEM_CONNECTION), cnt, cnt);
            d->addConnection(conn);
            endInsertRows();
        } else
        {
            //TODO: onConnectionUpdate
        }
    });
    connect(d.data(), &NmModelPrivate::connectionUpdate, [this] (NetworkManager::Connection * conn) {
//qCDebug(NM_TRAY) << "connectionUpdate" << conn->name();
        auto i = std::find(d->mConnections.cbegin(), d->mConnections.cend(), conn);
        if (d->mConnections.cend() != i)
        {
            QModelIndex index = createIndex(i - d->mConnections.cbegin(), 0, ITEM_CONNECTION_LEAF);
            emit dataChanged(index, index);
        }
    });
    connect(d.data(), &NmModelPrivate::connectionRemove, [this] (NetworkManager::Connection * conn) {
//qCDebug(NM_TRAY) << "connectionRemove" << conn->name();
        auto i = std::find(d->mConnections.cbegin(), d->mConnections.cend(), conn);
        if (d->mConnections.cend() != i)
        {
            const int pos = i - d->mConnections.cbegin();
            beginRemoveRows(createIndex(1, 0, ITEM_CONNECTION), pos, pos);
            d->removeConnection(pos);
            endRemoveRows();
        }
    });
    connect(d.data(), &NmModelPrivate::activeConnectionAdd, [this] (NetworkManager::ActiveConnection::Ptr conn) {
//qCDebug(NM_TRAY) << "activecCnnectionAdd" << conn->connection()->name();
        if (0 > d->mActiveConns.indexOf(conn))
        {
            const int cnt = d->mActiveConns.size();
            beginInsertRows(createIndex(0, 0, ITEM_ACTIVE), cnt, cnt);
            d->addActiveConnection(conn);
            endInsertRows();
        } else
        {
            //TODO: onActiveConnectionUpdate
        }
    });
    connect(d.data(), &NmModelPrivate::activeConnectionUpdate, [this] (NetworkManager::ActiveConnection * conn) {
//qCDebug(NM_TRAY) << "activecCnnectionUpdate" << conn->connection()->name();
        auto i = std::find(d->mActiveConns.cbegin(), d->mActiveConns.cend(), conn);
        if (d->mActiveConns.cend() != i)
        {
            QModelIndex index = createIndex(i - d->mActiveConns.cbegin(), 0, ITEM_ACTIVE_LEAF);
            emit dataChanged(index, index);
        }
    });
    connect(d.data(), &NmModelPrivate::activeConnectionRemove, [this] (NetworkManager::ActiveConnection * conn) {
//qCDebug(NM_TRAY) << "activecCnnectionRemove" << conn->connection()->name();
        auto i = std::find(d->mActiveConns.cbegin(), d->mActiveConns.cend(), conn);
        if (d->mActiveConns.cend() != i)
        {
            const int pos = i - d->mActiveConns.cbegin();
            beginRemoveRows(createIndex(0, 0, ITEM_ACTIVE), pos, pos);
            d->removeActiveConnection(pos);
            endRemoveRows();
        }
    });
    connect(d.data(), &NmModelPrivate::activeConnectionsReset, [this] () {
//qCDebug(NM_TRAY) << "activecCnnectionReset";
        const int cnt = d->mActiveConns.size();
        if (0 < cnt)
        {
            beginRemoveRows(createIndex(0, 0, ITEM_ACTIVE), 0, d->mActiveConns.size() - 1);
            d->clearActiveConnections();
            endRemoveRows();
        }
        const int new_cnt = NetworkManager::activeConnections().size();
        if (0 < new_cnt)
        {
            beginInsertRows(createIndex(0, 0, ITEM_ACTIVE), 0, new_cnt - 1);
            d->insertActiveConnections();
            endInsertRows();
        }
    });
    connect(d.data(), &NmModelPrivate::deviceAdd, [this] (NetworkManager::Device::Ptr dev) {
//qCDebug(NM_TRAY) << "deviceAdd" << dev->interfaceName();
        if (0 > d->mDevices.indexOf(dev))
        {
            const int cnt = d->mDevices.size();
            beginInsertRows(createIndex(2, 0, ITEM_DEVICE), cnt, cnt);
            d->addDevice(dev);
            endInsertRows();
        } else
        {
            //TODO: onDeviceUpdate
        }
    });
    connect(d.data(), &NmModelPrivate::deviceUpdate, [this] (NetworkManager::Device * dev) {
//qCDebug(NM_TRAY) << "deviceUpdate" << dev << dev->interfaceName();
        auto i = std::find(d->mDevices.cbegin(), d->mDevices.cend(), dev);
        if (d->mDevices.cend() != i)
        {
            QModelIndex index = createIndex(i - d->mDevices.cbegin(), 0, ITEM_DEVICE_LEAF);
            emit dataChanged(index, index);
        }
    });
    connect(d.data(), &NmModelPrivate::deviceRemove, [this] (NetworkManager::Device * dev) {
//qCDebug(NM_TRAY) << "deviceRemove" << dev->interfaceName();
        auto i = std::find(d->mDevices.cbegin(), d->mDevices.cend(), dev);
        if (d->mDevices.cend() != i)
        {
            const int pos = i - d->mDevices.cbegin();
            beginRemoveRows(createIndex(2, 0, ITEM_DEVICE), pos, pos);
            d->removeDevice(pos);
            endRemoveRows();
        }
    });
    connect(d.data(), &NmModelPrivate::wifiNetworkAdd, [this] (NetworkManager::Device * dev, QString const & ssid) {
//qCDebug(NM_TRAY) << "wifiNetworkAdd" << dev << dev->interfaceName() << ssid;
        NetworkManager::WirelessDevice * w_dev = qobject_cast<NetworkManager::WirelessDevice *>(dev);
        NetworkManager::WirelessNetwork::Ptr net = w_dev->findNetwork(ssid);
        if (!net.isNull())
        {
            if (0 > d->mWifiNets.indexOf(net))
            {
                const int cnt = d->mWifiNets.size();
                beginInsertRows(createIndex(3, 0, ITEM_WIFINET), cnt, cnt);
                d->addWifiNetwork(net);
                endInsertRows();
            } else
            {
                //TODO: onWifiNetworkUpdate
            }
        }
    });
    connect(d.data(), &NmModelPrivate::wifiNetworkUpdate, [this] (NetworkManager::WirelessNetwork * net) {
//qCDebug(NM_TRAY) << "wifiNetworkUpdate" << net << net->ssid();
        auto i = std::find(d->mWifiNets.cbegin(), d->mWifiNets.cend(), net);
        if (d->mWifiNets.cend() != i)
        {
            if (net->accessPoints().isEmpty())
            {
                //remove
                auto pos = i - d->mWifiNets.cbegin();
                beginRemoveRows(createIndex(3, 0, ITEM_WIFINET), pos, pos);
                d->removeWifiNetwork(pos);
                endRemoveRows();
            } else
            {
                //update
                QModelIndex index = createIndex(i - d->mWifiNets.cbegin(), 0, ITEM_WIFINET_LEAF);
                emit dataChanged(index, index);
                //XXX: active connection
                auto dev = d->findDeviceUni((*i)->device());
                if (!dev.isNull())
                {
                    auto active = dev->activeConnection();
                    if (!active.isNull())
                    {
                        QModelIndex index = createIndex(d->mActiveConns.indexOf(active), 0, ITEM_ACTIVE_LEAF);
                        emit dataChanged(index, index);
                    }
                }
            }
        }
    });
    connect(d.data(), &NmModelPrivate::wifiNetworkRemove, [this] (NetworkManager::Device * dev, QString const & ssid) {
//qCDebug(NM_TRAY) << "wifiNetworkRemove" << dev << dev->interfaceName() << ssid;
        NetworkManager::WirelessNetwork::Ptr net = d->findWifiNetwork(ssid, dev->uni());
        if (!net.isNull())
        {
            auto pos = d->mWifiNets.indexOf(net);
            if (0 <= pos)
            {
                beginRemoveRows(createIndex(3, 0, ITEM_WIFINET), pos, pos);
                d->removeWifiNetwork(pos);
                endRemoveRows();
            }
        }
    });
//qCDebug(NM_TRAY) << __FUNCTION__ << "finished";
}

NmModel::~NmModel()
{
}

/* Model hierarchy
 * root
 *   - mActiveConns
 *   - mConnections
 *   - mDevices
 */
int NmModel::rowCount(const QModelIndex &parent/* = QModelIndex()*/) const
{
    int cnt = 0;
    if (!parent.isValid())
        cnt = 1;
    else
    {
        const int id = parent.internalId();
        if (ITEM_ROOT  == id)
            cnt = 4;
        else if (ITEM_ACTIVE == id)
            cnt =  d->mActiveConns.size();
        else if (ITEM_CONNECTION == id)
            cnt = d->mConnections.size();
        else if (ITEM_DEVICE == id)
            cnt = d->mDevices.size();
        else if (ITEM_WIFINET == id)
            cnt = d->mWifiNets.size();
    }

//qCDebug(NM_TRAY) << __FUNCTION__ << parent << cnt;
    return cnt;
}

int NmModel::columnCount(const QModelIndex & /*parent = QModelIndex()*/) const
{
//qCDebug(NM_TRAY) << __FUNCTION__ << parent << 1;
    //XXX: more columns for wifi connections (for name && icons...)??
    return 1;
}

bool NmModel::isValidDataIndex(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_WIFINET:
            return true;
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns.size() > index.row() && 0 == index.column();
        case ITEM_CONNECTION_LEAF:
            return d->mConnections.size() > index.row() && 0 == index.column();
        case ITEM_DEVICE_LEAF:
            return d->mDevices.size() > index.row() && 0 == index.column();
        case ITEM_WIFINET_LEAF:
            return d->mWifiNets.size() > index.row() && 0 == index.column();
    }
    return false;
}

template <>
QVariant NmModel::dataRole<NmModel::ItemTypeRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_WIFINET:
        case ITEM_DEVICE:
            return HelperType;
        case ITEM_ACTIVE_LEAF:
            return ActiveConnectionType;
        case ITEM_CONNECTION_LEAF:
            return ConnectionType;
        case ITEM_DEVICE_LEAF:
            return DeviceType;
        case ITEM_WIFINET_LEAF:
            return WifiNetworkType;
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::NameRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
            return NmModel::tr("root");
        case ITEM_ACTIVE:
            return NmModel::tr("active connection(s)");
        case ITEM_CONNECTION:
            return NmModel::tr("connection(s)");
        case ITEM_DEVICE:
            return NmModel::tr("device(s)");
        case ITEM_WIFINET:
            return NmModel::tr("wifi network(s)");
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->connection()->name();
        case ITEM_CONNECTION_LEAF:
            return d->mConnections[index.row()]->name();
        case ITEM_DEVICE_LEAF:
            return d->mDevices[index.row()]->interfaceName();
        case ITEM_WIFINET_LEAF:
            return d->mWifiNets[index.row()]->referenceAccessPoint()->ssid();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ConnectionTypeRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_WIFINET:
            return QVariant{};
        case ITEM_DEVICE_LEAF:
            //TODO: other types
            switch (d->mDevices[index.row()]->type())
            {
                case NetworkManager::Device::Wifi:
                    return NetworkManager::ConnectionSettings::Wireless;
                case NetworkManager::Device::Wimax:
                    return NetworkManager::ConnectionSettings::Wimax;
                default:
                    return NetworkManager::ConnectionSettings::Wired;
            }
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->connection()->settings()->connectionType();
        case ITEM_CONNECTION_LEAF:
            return d->mConnections[index.row()]->settings()->connectionType();
        case ITEM_WIFINET_LEAF:
            return NetworkManager::ConnectionSettings::Wireless;
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ConnectionTypeStringRole>(const QModelIndex & index) const
{
    const QVariant conn_type = dataRole<ConnectionTypeRole>(index);
    if (!conn_type.isValid())
    {
        return QVariant{};
    }
    return NetworkManager::ConnectionSettings::typeAsString(static_cast<NetworkManager::ConnectionSettings::ConnectionType>(conn_type.toInt()));
}

template <>
QVariant NmModel::dataRole<NmModel::ActiveConnectionStateRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_CONNECTION_LEAF:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
        case ITEM_WIFINET_LEAF:
            return QVariant{};
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->state();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ActiveConnectionMasterRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_CONNECTION_LEAF:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
        case ITEM_WIFINET_LEAF:
            return QVariant{};
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->master();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ActiveConnectionDevicesRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_CONNECTION_LEAF:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
        case ITEM_WIFINET_LEAF:
            return QVariant{};
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->devices();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::IconSecurityTypeRole>(const QModelIndex & index) const
{
    auto const internal_id = static_cast<ItemId>(index.internalId());
    switch (internal_id)
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_WIFINET:
        case ITEM_DEVICE_LEAF:
            return -1;
        case ITEM_CONNECTION_LEAF:
        case ITEM_ACTIVE_LEAF:
            {
                NetworkManager::ConnectionSettings::Ptr settings = ITEM_CONNECTION_LEAF == internal_id
                    ? d->mConnections[index.row()]->settings()
                    : d->mActiveConns[index.row()]->connection()->settings();
                if (NetworkManager::ConnectionSettings::Wireless == settings->connectionType())
                {
                    NetworkManager::WirelessSecuritySetting::Ptr w_sett = settings->setting(NetworkManager::Setting::WirelessSecurity).staticCast<NetworkManager::WirelessSecuritySetting>();
                    if (w_sett.isNull())
                        return icons::SECURITY_LOW;
                    else if (NetworkManager::WirelessSecuritySetting::WpaNone != w_sett->keyMgmt())
                        return icons::SECURITY_HIGH;
                    else
                        return icons::SECURITY_LOW;
                }
                return -1;
            }
        case ITEM_WIFINET_LEAF:
            return d->mWifiNets[index.row()]->referenceAccessPoint()->capabilities().testFlag(NetworkManager::AccessPoint::Privacy)
                ? icons::SECURITY_HIGH
                : icons::SECURITY_LOW;
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::IconSecurityRole>(const QModelIndex & index) const
{
    const auto type = dataRole<IconSecurityTypeRole>(index).toInt();
    if (0 <= type)
        return icons::getIcon(static_cast<icons::Icon>(type), true);
    else
        return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::SignalRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
            return -1;
        case ITEM_ACTIVE_LEAF:
            {
                NetworkManager::ConnectionSettings::Ptr sett = d->mActiveConns[index.row()]->connection()->settings();
                //TODO: other type with signals!?!
                if (NetworkManager::ConnectionSettings::Wireless == sett->connectionType())
                {
                    NetworkManager::WirelessSetting::Ptr w_sett = sett->setting(NetworkManager::Setting::Wireless).staticCast<NetworkManager::WirelessSetting>();
                    Q_ASSERT(!w_sett.isNull());
                    for (auto const & dev_uni : d->mActiveConns[index.row()]->devices())
                    {
                        auto dev = NetworkManager::findNetworkInterface(dev_uni);
                        if (!dev.isNull() && dev->isValid())
                        {
                            auto w_dev = dev.objectCast<NetworkManager::WirelessDevice>();
                            if (!w_dev.isNull())
                            {
                                auto wifi_net = w_dev->findNetwork(w_sett->ssid());
                                if (!wifi_net.isNull())
                                    return wifi_net->signalStrength();
                            }
                        }
                    }
                }
                return -1;
            }
        case ITEM_CONNECTION_LEAF:
            //TODO: implement
            return -1;
        case ITEM_WIFINET_LEAF:
            return d->mWifiNets[index.row()]->signalStrength();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ConnectionUuidRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
        case ITEM_WIFINET_LEAF:
            return QVariant{};
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->uuid();
        case ITEM_CONNECTION_LEAF:
            return d->mConnections[index.row()]->uuid();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ConnectionPathRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
        case ITEM_WIFINET_LEAF:
            return QVariant{};
        case ITEM_ACTIVE_LEAF:
            return d->mActiveConns[index.row()]->path();
        case ITEM_CONNECTION_LEAF:
            return d->mConnections[index.row()]->path();
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::ActiveConnectionInfoRole>(const QModelIndex & index) const
{
    NetworkManager::Device::Ptr dev;
    NetworkManager::ConnectionSettings::Ptr settings;
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_DEVICE_LEAF:
        case ITEM_WIFINET:
        case ITEM_WIFINET_LEAF:
        case ITEM_CONNECTION_LEAF:
            return QVariant{};
            break;
        case ITEM_ACTIVE_LEAF:
            {
                auto const a_conn = d->mActiveConns[index.row()];
                auto const dev_list = a_conn->devices();
                //TODO: work with all devices?!?
                if (!dev_list.isEmpty())
                    dev = d->findDeviceUni(dev_list.first());
                Q_ASSERT(!a_conn->connection().isNull());
                settings = a_conn->connection()->settings();
            }
            break;
    }
    //TODO: we probably shouldn't assemble a string information (with styling) here and leave it to the consumer
    QString info;
    QDebug str{&info};
    str.noquote();
    str.nospace();
    if (!dev.isNull())
    {
        auto m_enum = NetworkManager::Device::staticMetaObject.enumerator(NetworkManager::Device::staticMetaObject.indexOfEnumerator("Type"));

        QString hw_address = NmModel::tr("unknown", "hardware address");
        QString security;
        int bit_rate = -1;
        switch (dev->type())
        {
            case NetworkManager::Device::Adsl:
                break;
            case NetworkManager::Device::Bond:
                {
                    auto spec_dev = dev->as<NetworkManager::BondDevice>();
                    hw_address = spec_dev->hwAddress();
                }
                break;
            case NetworkManager::Device::Bridge:
                {
                    auto spec_dev = dev->as<NetworkManager::BridgeDevice>();
                    hw_address = spec_dev->hwAddress();
                }
                break;
            case NetworkManager::Device::Generic:
                {
                    auto spec_dev = dev->as<NetworkManager::GenericDevice>();
                    hw_address = spec_dev->hardwareAddress();
                }
                break;
            case NetworkManager::Device::Gre:
                break;
            case NetworkManager::Device::InfiniBand:
                {
                    auto spec_dev = dev->as<NetworkManager::InfinibandDevice>();
                    hw_address = spec_dev->hwAddress();
                }
                break;
            case NetworkManager::Device::IpTunnel:
            case NetworkManager::Device::MacVlan:
            case NetworkManager::Device::Modem:
                break;
            case NetworkManager::Device::Bluetooth:
                {
                    auto spec_dev = dev->as<NetworkManager::BluetoothDevice>();
                    hw_address = spec_dev->hardwareAddress();
                }
                break;
            case NetworkManager::Device::OlpcMesh:
                {
                    auto spec_dev = dev->as<NetworkManager::OlpcMeshDevice>();
                    hw_address = spec_dev->hardwareAddress();
                }
                break;
            case NetworkManager::Device::Team:
                {
                    auto spec_dev = dev->as<NetworkManager::TeamDevice>();
                    hw_address = spec_dev->hwAddress();
                }
                break;
            case NetworkManager::Device::Tun:
            case NetworkManager::Device::Veth:
                break;
            case NetworkManager::Device::Vlan:
                {
                    auto spec_dev = dev->as<NetworkManager::VlanDevice>();
                    hw_address = spec_dev->hwAddress();
                }
                break;
            case NetworkManager::Device::Wimax:
                //Wimax support was dropped in network manager 1.2.0
                //we should never get here in runtime with nm >= 1.2.0
                {
                    auto spec_dev = dev->as<NetworkManager::WimaxDevice>();
                    Q_ASSERT(nullptr != spec_dev);
                    hw_address = spec_dev->hardwareAddress();
                    //bit_rate = spec_dev->bitRate();
                }
                break;
            case NetworkManager::Device::Ethernet:
                {
                    auto spec_dev = dev->as<NetworkManager::WiredDevice>();
                    Q_ASSERT(nullptr != spec_dev);
                    hw_address = spec_dev->hardwareAddress();
                    bit_rate = spec_dev->bitRate();
                }
                break;
            case NetworkManager::Device::Wifi:
                {
                    auto spec_dev = dev->as<NetworkManager::WirelessDevice>();
                    Q_ASSERT(nullptr != spec_dev);
                    hw_address = spec_dev->hardwareAddress();
                    bit_rate = spec_dev->bitRate();
                    NetworkManager::Setting::Ptr setting = settings->setting(NetworkManager::Setting::WirelessSecurity);
                    if (!setting.isNull())
                    {
                        QVariantMap const map = setting->toMap();
                        if (map.contains(QLatin1String(NM_SETTING_WIRELESS_SECURITY_KEY_MGMT)))
                            security = map.value(QLatin1String(NM_SETTING_WIRELESS_SECURITY_KEY_MGMT)).toString();
                    }
                }
                break;
            case NetworkManager::Device::UnknownType:
            case NetworkManager::Device::Unused1:
            case NetworkManager::Device::Unused2:
                break;
            case NetworkManager::Device::VxLan:
            case NetworkManager::Device::MacSec:
            case NetworkManager::Device::Dummy:
                // Note: these devices are still not implemented in networkmanager-qt (as of v5.44.0)
                break;
        }
        str << QStringLiteral("<table>")
            << QStringLiteral("<tr><td colspan='2'><big><strong>") << NmModel::tr("General", "Active connection information") << QStringLiteral("</strong></big></td></tr>")
            << QStringLiteral("<tr><td><strong>") << NmModel::tr("Interface", "Active connection information") << QStringLiteral("</strong>: </td><td>")
                << m_enum.valueToKey(dev->type()) << QStringLiteral(" (") << dev->interfaceName() << QStringLiteral(")</td></tr>")
            << QStringLiteral("<tr><td><strong>") << NmModel::tr("Hardware Address", "Active connection information") << QStringLiteral("</strong>: </td><td>")
                << hw_address << QStringLiteral("</td></tr>")
            << QStringLiteral("<tr><td><strong>") << NmModel::tr("Driver", "Active connection information") << QStringLiteral("</strong>: </td><td>")
                << dev->driver() << QStringLiteral("</td></tr>")
            << QStringLiteral("<tr><td><strong>") << NmModel::tr("Speed", "Active connection information") << QStringLiteral("</strong>: </td><td>");
        if (0 <= bit_rate)
            str << bit_rate << NmModel::tr(" Kb/s");
        else
            str << NmModel::tr("unknown", "Speed");
        str << QStringLiteral("</td></tr>");
        if (!security.isEmpty())
        {
            str << QStringLiteral("<tr><td><strong>") << NmModel::tr("Security", "Active connection information") << QStringLiteral("</strong>: </td><td>")
                << security << QStringLiteral("</td></tr>");
        }
        //IP4/6
        QString const ip4 = NmModel::tr("IPv4", "Active connection information");
        QString const ip6 = NmModel::tr("IPv6", "Active connection information");
        for (auto const & ip : { std::make_tuple(ip4, dev->ipV4Config()) , std::make_tuple(ip6, dev->ipV6Config()) })
        {
            NetworkManager::IpConfig ip_config = std::get<1>(ip);
            if (ip_config.isValid())
            {
                str << QStringLiteral("<tr/><tr><td colspan='2'><big><strong>") << std::get<0>(ip) << QStringLiteral("</strong></big></td></tr>");
                int i = 1;
                for (QNetworkAddressEntry const & address : ip_config.addresses())
                {
                    QString suffix = (i > 1 ? QString{"(%1)"}.arg(i) : QString{});
                    str << QStringLiteral("<tr><td><strong>") << NmModel::tr("IP Address", "Active connection information") << suffix << QStringLiteral("</strong>: </td><td>")
                        << address.ip().toString() << QStringLiteral("</td></tr>")
                        << QStringLiteral("<tr><td><strong>") << NmModel::tr("Subnet Mask", "Active connection information") << suffix << QStringLiteral("</strong>: </td><td>")
                        << address.netmask().toString() << QStringLiteral("</td></tr>")
                        ;
                    ++i;
                }

                QString const gtw = ip_config.gateway();
                if (!gtw.isEmpty())
                {
                    str << QStringLiteral("<tr><td><strong>") << NmModel::tr("Default route", "Active connection information") << QStringLiteral("</strong>: </td><td>")
                        << ip_config.gateway() << QStringLiteral("</td></tr>");
                }
                i = 0;
                for (auto const & nameserver : ip_config.nameservers())
                {
                    str << QStringLiteral("<tr><td><strong>") << NmModel::tr("DNS(%1)", "Active connection information").arg(++i) << QStringLiteral("</strong>: </td><td>")
                        << nameserver.toString() << QStringLiteral("</td></tr>");
                }
            }
        }
        str << QStringLiteral("</table>");
    }
    return info;
}

template <>
QVariant NmModel::dataRole<NmModel::IconTypeRole>(const QModelIndex & index) const
{
    switch (static_cast<ItemId>(index.internalId()))
    {
        case ITEM_ROOT:
        case ITEM_ACTIVE:
        case ITEM_CONNECTION:
        case ITEM_DEVICE:
        case ITEM_WIFINET:
            return -1;
        case ITEM_ACTIVE_LEAF:
        case ITEM_CONNECTION_LEAF:
        case ITEM_DEVICE_LEAF:
            {
                const int type = static_cast<NetworkManager::ConnectionSettings::ConnectionType>(dataRole<ConnectionTypeRole>(index).toInt());
                const QVariant state = dataRole<ActiveConnectionStateRole>(index);
                //TODO other types?
                switch (type)
                {
                    case NetworkManager::ConnectionSettings::Wireless:
                    case NetworkManager::ConnectionSettings::Wimax:
                        if (!state.isValid())
                            return icons::NETWORK_WIFI_DISCONNECTED;
                        switch (static_cast<NetworkManager::ActiveConnection::State>(state.toInt()))
                        {
                            case NetworkManager::ActiveConnection::Activating:
                                return icons::NETWORK_WIFI_ACQUIRING;
                            case NetworkManager::ActiveConnection::Activated:
                                return icons::wifiSignalIcon(dataRole<SignalRole>(index).toInt());
                            case NetworkManager::ActiveConnection::Unknown:
                            case NetworkManager::ActiveConnection::Deactivating:
                            case NetworkManager::ActiveConnection::Deactivated:
                                return icons::NETWORK_WIFI_DISCONNECTED;
                        }
                        break;
                    case NetworkManager::ConnectionSettings::Vpn:
                        return icons::NETWORK_VPN;
                    default:
                        return NetworkManager::ActiveConnection::Activated == state ? icons::NETWORK_WIRED : icons::NETWORK_WIRED_DISCONNECTED;
                }
            }
            break;
        case ITEM_WIFINET_LEAF:
            return icons::wifiSignalIcon(d->mWifiNets[index.row()]->signalStrength());
    }
    return QVariant{};
}

template <>
QVariant NmModel::dataRole<NmModel::IconRole>(const QModelIndex & index) const
{
    return icons::getIcon(static_cast<icons::Icon>(dataRole<IconTypeRole>(index).toInt()), true);
}

QVariant NmModel::data(const QModelIndex &index, int role) const
{
//qCDebug(NM_TRAY) << __FUNCTION__ << index << role;
    Q_ASSERT(isValidDataIndex(index));
    QVariant ret;
    if (index.isValid())
        switch (role)
        {
            case ItemTypeRole:
                ret = dataRole<ItemTypeRole>(index);
                break;
            case Qt::DisplayRole:
            case NameRole:
                ret = dataRole<NameRole>(index);
                break;
            case ConnectionTypeRole:
                ret = dataRole<ConnectionTypeRole>(index);
                break;
            case ConnectionTypeStringRole:
                ret = dataRole<ConnectionTypeStringRole>(index);
                break;
            case ActiveConnectionStateRole:
                ret = dataRole<ActiveConnectionStateRole>(index);
                break;
            case ActiveConnectionMasterRole:
                ret = dataRole<ActiveConnectionMasterRole>(index);
                break;
            case ActiveConnectionDevicesRole:
                ret = dataRole<ActiveConnectionDevicesRole>(index);
                break;
            case SignalRole:
                ret = dataRole<SignalRole>(index);
                break;
            case ConnectionUuidRole:
                ret = dataRole<ConnectionUuidRole>(index);
                break;
            case ConnectionPathRole:
                ret = dataRole<ConnectionPathRole>(index);
                break;
            case ActiveConnectionInfoRole:
                ret = dataRole<ActiveConnectionInfoRole>(index);
                break;
            case IconTypeRole:
                ret = dataRole<IconTypeRole>(index);
                break;
            case Qt::DecorationRole:
            case IconRole:
                ret = dataRole<IconRole>(index);
                break;
            case IconSecurityTypeRole:
                ret = dataRole<IconSecurityTypeRole>(index);
                break;
            case IconSecurityRole:
                ret = dataRole<IconSecurityRole>(index);
                break;
            default:
                ret = QVariant{};
                break;
        }
//qCDebug(NM_TRAY) << __FUNCTION__ << "ret" << index << role << ret;
    return ret;
}

QModelIndex NmModel::index(int row, int column, const QModelIndex &parent/* = QModelIndex()*/) const
{
//qCDebug(NM_TRAY) << __FUNCTION__ << row << column << parent;
    if (!hasIndex(row, column, parent))
        return QModelIndex{};
    const int id = parent.internalId();
    ItemId int_id;
    if (!parent.isValid())
    {
        Q_ASSERT(0 == row && 0 == column);
        int_id = ITEM_ROOT;
    } else if (ITEM_ROOT == id)
    {
        Q_ASSERT(4 > row && 0 == column);
        switch (row)
        {
            case 0: int_id = ITEM_ACTIVE; break;
            case 1: int_id = ITEM_CONNECTION; break;
            case 2: int_id = ITEM_DEVICE; break;
            case 3: int_id = ITEM_WIFINET; break;
        }
    } else if (ITEM_ACTIVE == id)
    {
        Q_ASSERT(d->mActiveConns.size() > row);
        int_id = ITEM_ACTIVE_LEAF;
    }
    else if (ITEM_CONNECTION == id)
    {
        Q_ASSERT(d->mConnections.size() > row);
        int_id = ITEM_CONNECTION_LEAF;
    }
    else if (ITEM_DEVICE == id)
    {
        Q_ASSERT(d->mDevices.size() > row);
        int_id = ITEM_DEVICE_LEAF;
    }
    else if (ITEM_WIFINET == id)
    {
        Q_ASSERT(d->mWifiNets.size() > row);
        int_id = ITEM_WIFINET_LEAF;
    }
    else
    {
        Q_ASSERT(false); //should never get here
        return QModelIndex{};
    }

//qCDebug(NM_TRAY) << __FUNCTION__ << "ret: " << row << column << int_id;
    return createIndex(row, column, int_id);
}

QModelIndex NmModel::parent(const QModelIndex &index) const
{
    QModelIndex parent_i;
    if (index.isValid())
    {
        switch (static_cast<ItemId>(index.internalId()))
        {
            case ITEM_ROOT:
                break;
            case ITEM_ACTIVE:
            case ITEM_CONNECTION:
            case ITEM_DEVICE:
            case ITEM_WIFINET:
                parent_i = createIndex(0, 0, ITEM_ROOT);
                break;
            case ITEM_ACTIVE_LEAF:
                parent_i = createIndex(0, 0, ITEM_ACTIVE);
                break;
            case ITEM_CONNECTION_LEAF:
                parent_i = createIndex(1, 0, ITEM_CONNECTION);
                break;
            case ITEM_DEVICE_LEAF:
                parent_i = createIndex(2, 0, ITEM_DEVICE);
                break;
            case ITEM_WIFINET_LEAF:
                parent_i = createIndex(3, 0, ITEM_WIFINET);
                break;
        }
    }

//qCDebug(NM_TRAY) << __FUNCTION__ << index << parent_i;
    return parent_i;
}

QModelIndex NmModel::indexTypeRoot(ItemType type) const
{
    QModelIndex i;
    switch (type)
    {
        case HelperType:
            i = createIndex(0, 0, ITEM_ROOT);
            break;
        case ActiveConnectionType:
            i = createIndex(0, 0, ITEM_ACTIVE);
            break;
        case ConnectionType:
            i = createIndex(1, 0, ITEM_CONNECTION);
            break;
        case DeviceType:
            i = createIndex(2, 0, ITEM_DEVICE);
            break;
        case WifiNetworkType:
            i = createIndex(3, 0, ITEM_WIFINET);
            break;
    }
    return i;
}

void NmModel::activateConnection(QModelIndex const & index)
{
    ItemId id = static_cast<ItemId>(index.internalId());
    if (!isValidDataIndex(index) || (ITEM_CONNECTION_LEAF != id && ITEM_WIFINET_LEAF != id))
    {
        //TODO: in what form should we output the warning messages
        qCWarning(NM_TRAY).noquote() << "got invalid index for connection activation" << index;
        return;
    }
    QString conn_uni, dev_uni;
    QString conn_name, dev_name;
    QString spec_object;
    NMVariantMapMap map_settings;
    bool add_connection = false;
    switch (id)
    {
        case ITEM_CONNECTION_LEAF:
            {
                auto const & conn = d->mConnections[index.row()];
                conn_uni = conn->path();
                conn_name = conn->name();
                if (NetworkManager::ConnectionSettings::Vpn == conn->settings()->connectionType())
                {
                    spec_object = dev_uni = QStringLiteral("/");
                    /*
                    // find first non-vpn active connection
                    const auto act_i = std::find_if(d->mActiveConns.cbegin(), d->mActiveConns.cend(), [] (NetworkManager::ActiveConnection::Ptr const & conn) -> bool
                    {
                    return nullptr != dynamic_cast<NetworkManager::VpnConnection const *>(conn.data());
                    });
                    if (act_i != d->mActiveConns.cend() && !(*act_i)->devices().empty())
                    {
                    dev_uni = (*act_i)->devices().front();
                    spec_object = (*act_i)->path();
                    }
                    */

                } else
                {
                    dev_name = conn->settings()->interfaceName();
                    for (auto const & dev : d->mDevices)
                        for (auto const & dev_conn : dev->availableConnections())
                            if (dev_conn == conn)
                            {
                                dev_uni = dev->uni();
                                dev_name = dev->interfaceName();
                            }
                    if (dev_uni.isEmpty() && !dev_name.isEmpty())
                    {
                        auto dev = d->findDeviceInterface(dev_name);
                        if (!dev.isNull())
                            dev_uni = dev->uni();
                    }
                }
                if (dev_uni.isEmpty())
                {
                    //TODO: in what form should we output the warning messages
                    qCWarning(NM_TRAY).noquote() << QStringLiteral("can't find device '%1' to activate connection '%2' on").arg(dev_name).arg(conn->name());
                    return;
                }
            }
            break;
        case ITEM_WIFINET_LEAF:
            {
                auto const & net = d->mWifiNets[index.row()];
                auto access_point = net->referenceAccessPoint();
                Q_ASSERT(!access_point.isNull());
                dev_uni = net->device();
                auto dev = d->findDeviceUni(dev_uni);
                Q_ASSERT(!dev.isNull());
                auto spec_dev = dev->as<NetworkManager::WirelessDevice>();
                Q_ASSERT(nullptr != spec_dev);
                conn_uni = access_point->uni();
                conn_name = access_point->ssid();
                //find the device name
                NetworkManager::Connection::Ptr conn;
                dev_name = dev->interfaceName();
                for (auto const & dev_conn : dev->availableConnections())
                    if (dev_conn->settings()->id() == conn_name)
                    {
                        conn = dev_conn;
                    }
                if (conn.isNull())
                {
                    //TODO: in what form should we output the warning messages
                    qCWarning(NM_TRAY).noquote() << QStringLiteral("can't find connection for '%1' on device '%2', will create new...").arg(conn_name).arg(dev_name);
                    add_connection = true;
                    spec_object = conn_uni;
                    NetworkManager::WirelessSecurityType sec_type = NetworkManager::findBestWirelessSecurity(spec_dev->wirelessCapabilities()
                            , true, (spec_dev->mode() == NetworkManager::WirelessDevice::Adhoc)
                            , access_point->capabilities(), access_point->wpaFlags(), access_point->rsnFlags());
                    switch (sec_type)
                    {
                        case NetworkManager::UnknownSecurity:
                            qCWarning(NM_TRAY).noquote() << QStringLiteral("unknown security to use for '%1'").arg(conn_name);
                        case NetworkManager::NoneSecurity:
                            //nothing to do
                            break;
                        case NetworkManager::WpaPsk:
                        case NetworkManager::Wpa2Psk:
                            if (NetworkManager::ConnectionSettings::Ptr settings = assembleWpaXPskSettings(access_point))
                            {
                                map_settings = settings->toMap();
                            } else
                            {
                                qCWarning(NM_TRAY).noquote() << QStringLiteral("connection settings assembly for '%1' failed, abandoning activation...")
                                    .arg(conn_name);
                                return;
                            }
                            break;
                            //TODO: other types...
                    }
                } else
                {
                    conn_uni = conn->path();
                }
            }
            break;
        default:
            Q_ASSERT(false);
    }
qCDebug(NM_TRAY) << __FUNCTION__ << conn_uni << dev_uni << conn_name << dev_name << spec_object;
    //TODO: check vpn type etc..
    QDBusPendingCallWatcher * watcher;
    if (add_connection)
        watcher = new QDBusPendingCallWatcher{NetworkManager::addAndActivateConnection(map_settings, dev_uni, spec_object), this};
    else
        watcher = new QDBusPendingCallWatcher{NetworkManager::activateConnection(conn_uni, dev_uni, spec_object), this};
    connect(watcher, &QDBusPendingCallWatcher::finished, [conn_name, dev_name] (QDBusPendingCallWatcher * watcher) {
        if (watcher->isError() || !watcher->isValid())
        {
            //TODO: in what form should we output the warning messages
            qCWarning(NM_TRAY).noquote() << QStringLiteral("activation of connection '%1' on interface '%2' failed: %3").arg(conn_name)
                    .arg(dev_name).arg(watcher->error().message());
         }
         watcher->deleteLater();
    });
}

void NmModel::deactivateConnection(QModelIndex const & index)
{
    ItemId id = static_cast<ItemId>(index.internalId());
    if (!isValidDataIndex(index) || ITEM_ACTIVE_LEAF != id)
    {
        //TODO: in what form should we output the warning messages
        qCWarning(NM_TRAY).noquote() << "got invalid index for connection deactivation" << index;
        return;
    }

    auto const & active = d->mActiveConns[index.row()];
qCDebug(NM_TRAY) << __FUNCTION__ << active->path();
    QDBusPendingReply<> reply = NetworkManager::deactivateConnection(active->path());
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, [active] (QDBusPendingCallWatcher * watcher) {
        if (watcher->isError() || !watcher->isValid())
        {
            //TODO: in what form should we output the warning messages
            qCWarning(NM_TRAY).noquote() << QStringLiteral("deactivation of connection '%1' failed: %3").arg(active->connection()->name())
                    .arg(watcher->error().message());
         }
         watcher->deleteLater();
    });
}

void NmModel::requestScan(QModelIndex const & index) const
{
    if (!isValidDataIndex(index) || ITEM_DEVICE != static_cast<ItemId>(index.internalId()))
    {
        //TODO: in what form should we output the warning messages
        qCWarning(NM_TRAY).noquote() << "got invalid index for scanning request" << index;
        return;
    }

    auto const & dev = d->mDevices[index.row()];
    auto spec_dev = dev->as<NetworkManager::WirelessDevice>();
    if (nullptr == spec_dev)
    {
        //TODO: in what form should we output the warning messages
        qCWarning(NM_TRAY).noquote() << "dropping request for scan on non wireles device " << dev->interfaceName();
        return;
    }

    d->requestScan(spec_dev);
}

void NmModel::requestAllWifiScan() const
{
    for (auto const & dev : d->mDevices)
    {
        auto spec_dev = dev->as<NetworkManager::WirelessDevice>();
        if (nullptr != spec_dev)
            d->requestScan(spec_dev);
    }
}
