/*
 * Copyright © 2013 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3,
 * as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors:
 *     Antti Kaijanmäki <antti.kaijanmaki@canonical.com>
 *     Marcus Tomlinson <marcus.tomlinson@canonical.com>
 */

#include <nmofono/manager-impl.h>
#include <nmofono/connectivity-service-settings.h>
#include <nmofono/ethernet/ethernet-link.h>
#include <nmofono/wifi/wifi-link-impl.h>
#include <nmofono/wwan/ofono-nemo-slotmanager.h>
#include <nmofono/wifi/wifi-toggle.h>
#include <nmofono/wwan/sim-manager.h>
#include <NetworkManagerInterface.h>
#include <NetworkManagerSettingsInterface.h>
#include <NetworkManagerSettingsConnectionInterface.h>
#include <PropertiesInterface.h>

#define slots
#include <qofono-qt5/qofonomanager.h>
#include <qofono-qt5/qofonomodem.h>
#undef slots
#include <ofono/dbus.h>

#include <notify-cpp/notification-manager.h>
#include <notify-cpp/snapdecision/sim-unlock.h>
#include <sim-unlock-dialog.h>
#include <util/qhash-sharedptr.h>

#include <QMap>
#include <QList>
#include <QRegularExpression>
#include <QSettings>
#include <NetworkManager.h>
#include <QDebug>
#include <algorithm>
#include <iostream>

#include "nm-device-statistics-monitor.h"

using namespace std;

namespace nmofono {

class ManagerImpl::Private : public QObject
{
    Q_OBJECT
public:
    Manager& p;

    shared_ptr<OrgFreedesktopNetworkManagerInterface> m_nm;
    shared_ptr<OrgFreedesktopDBusPropertiesInterface> nm_prop;
    shared_ptr<OrgFreedesktopNetworkManagerSettingsInterface> m_settingsInterface;
    shared_ptr<QOfonoManager> m_ofono;
    connection::ActiveConnectionManager::SPtr m_activeConnectionManager;

    bool m_unstoppableOperationHappening = false;
    Manager::NetworkingStatus m_status = NetworkingStatus::offline;
    uint32_t m_characteristics = 0;

    bool m_hasWifi = false;
    FlightModeToggle::SPtr m_flightModeToggle;
    wifi::WifiToggle::SPtr m_wifiToggle;

    bool m_modemAvailable = false;

    QMap<QDBusObjectPath, Link::SPtr> m_nmDevices;

    QMap<QString, wwan::Modem::Ptr> m_ofonoLinks;

    SimUnlockDialog::Ptr m_unlockDialog;
    QList<wwan::Modem::Ptr> m_pendingUnlocks;

    HotspotManager::SPtr m_hotspotManager;

    bool m_mobileDataEnabled = false;

    wwan::Sim::Ptr m_simForMobileData;

    QList<wwan::Modem::Ptr> m_modems;
    QList<wwan::Sim::Ptr> m_sims;

    wwan::SimManager::Ptr m_simManager;
    wwan::OfonoNemoSlotManager::Ptr m_ofonoNemoSlotManager;

    ConnectivityServiceSettings::Ptr m_settings;

    QTimer m_checkSimForMobileDataTimer;
    QDeadlineTimer m_deferredUnlockAllModemTimer;

    NMDeviceStatisticsMonitor::Ptr m_statisticsMonitor;

    Private(Manager& parent) :
        p(parent)
    {
    }


public Q_SLOTS:

    void startCheckSimForMobileDataTimer()
    {
        m_checkSimForMobileDataTimer.start();
    }

    void checkSimForMobileData()
    {
        if (m_simForMobileData && m_simForMobileData->present())
        {
            return;
        }

        int presentSimCount = 0;
        for (auto modem : m_modems)
        {
            if (modem->sim())
            {
                presentSimCount++;
            }
        }

        if (presentSimCount == 1)
        {
            for (auto modem : m_modems)
            {
                if (modem->sim())
                {
                    setSimForMobileData(modem->sim());
                    break;
                }
            }
        }
    }

    void matchModemsAndSims()
    {
        for (wwan::Modem::Ptr modem: m_ofonoLinks)
        {
            bool match = false;
            for(wwan::Sim::Ptr sim : m_sims)
            {
                if (sim->ofonoPath() == modem->ofonoPath())
                {
                    match = true;
                    modem->setSim(sim);

                    break;
                }
            }
            if (!match)
            {
                modem->setSim(wwan::Sim::Ptr());
            }
        }
    }

    void simAdded(wwan::Sim::Ptr sim)
    {
        m_sims.append(sim);
        connect(sim.get(), &wwan::Sim::presentChanged, this, &Private::matchModemsAndSims);
        connect(sim.get(), &wwan::Sim::presentChanged, this, &Private::startCheckSimForMobileDataTimer);
        Q_EMIT p.simsChanged();

        QString iccid = m_settings->simForMobileData().toString();
        if (!iccid.isEmpty())
        {
            if (sim->iccid() == iccid) {
                setSimForMobileData(sim);
            }
        }

        matchModemsAndSims();
        m_checkSimForMobileDataTimer.start();
    }

    void modemReady()
    {
        wwan::Modem *modem_raw = qobject_cast<wwan::Modem*>(sender());
        if (!modem_raw)
        {
            Q_ASSERT(0);
            return;
        }
        auto modem = m_ofonoLinks[modem_raw->name()];
        if (!modem->sim())
        {
            matchModemsAndSims();
        }

        m_modems.append(modem);
        Q_EMIT p.modemsChanged();

        m_checkSimForMobileDataTimer.start();
    }

    void setUnstoppableOperationHappening(bool happening)
    {
        if (m_unstoppableOperationHappening == happening)
        {
            return;
        }

        m_unstoppableOperationHappening = happening;
        Q_EMIT p.unstoppableOperationHappeningUpdated(m_unstoppableOperationHappening);
    }

    void loadSettings()
    {
        QVariant ret = m_settings->mobileDataEnabled();
        if (ret.isNull())
        {
            /* This is the first time we are running on a system.
             *
             * Default to mobile data turned off, the user can enable it later.
             */
            m_settings->setMobileDataEnabled(false);
        }
        else
        {
            setMobileDataEnabled(ret.toBool());
        }

        ret = m_settings->simForMobileData();
        if (ret.isNull())
        {
            /* This is the first time we are running on a system.
             *
             * Default to no SIM active for mobile data.
             */
            m_settings->setSimForMobileData(QString());
        }
        else
        {
            QString iccid = ret.toString();
            wwan::Sim::Ptr sim;
            for(auto i = m_sims.begin(); i != m_sims.end(); i++)
            {
                if ((*i)->iccid() == iccid) {
                    sim = *i;
                    break;
                }
            }
            setSimForMobileData(sim);
        }
    }

    void updateHasWifi()
    {
        if (m_wifiToggle->state() != wifi::WifiToggle::State::not_available)
        {
            bool hasWifi = true;

            if (hasWifi != m_hasWifi) {
                m_hasWifi = hasWifi;
                Q_EMIT p.hasWifiUpdated(m_hasWifi);
            }
            return;
        }

        // ok, killswitch not supported, but we still might have wifi devices
        bool hasWifi = false;
        for (auto link : m_nmDevices.values())
        {
            if (link->type() == Link::Type::wifi)
            {
                hasWifi = true;
            }
        }
        if (hasWifi != m_hasWifi) {
            m_hasWifi = hasWifi;
            Q_EMIT p.hasWifiUpdated(m_hasWifi);
        }
    }

    void updateModemAvailable()
    {
        bool modemAvailable = !m_ofonoLinks.empty();

        if (m_modemAvailable == modemAvailable)
        {
            return;
        }

        m_modemAvailable = modemAvailable;
        Q_EMIT p.modemAvailableChanged(m_modemAvailable);
    }

    void sim_unlock_ready()
    {
        if (!m_pendingUnlocks.empty())
        {
            auto modem = m_pendingUnlocks.front();
            m_pendingUnlocks.pop_front();
            if (modem->requiredPin() != wwan::Modem::PinType::none)
            {
                m_unlockDialog->unlock(modem);
            }
        }
    }

    void modemReadyToUnlock(const QString& name)
    {
        auto modem = m_ofonoLinks[name];
        if (modem)
        {
            p.unlockModem(modem);
        }
    }

    void modems_changed(const QStringList& value)
    {
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
        QSet<QString> modemPaths(value.toSet());
        QSet<QString> currentModemPaths(m_ofonoLinks.keys().toSet());
#else
        QSet<QString> modemPaths(QSet<QString>(value.begin(), value.end()));

        auto currentModemPathsList = m_ofonoLinks.keys();
        QSet<QString> currentModemPaths(
            QSet<QString>(currentModemPathsList.begin(), currentModemPathsList.end()));
#endif

        auto toRemove = currentModemPaths;
        toRemove.subtract(modemPaths);

        auto toAdd = modemPaths;
        toAdd.subtract(currentModemPaths);

        if (!toRemove.isEmpty()) {
            qDebug() << "Removing modems" << toRemove;
        }
        for (const auto& path : toRemove)
        {
            auto modem = m_ofonoLinks.take(path);
            if (m_pendingUnlocks.contains(modem))
            {
                m_unlockDialog->cancel();
                m_pendingUnlocks.removeOne(modem);
                disconnect(modem.get(), &wwan::Modem::readyToUnlock, this, &Private::modemReadyToUnlock);
            }
            m_modems.removeAll(modem);
            Q_EMIT p.modemsChanged();
        }

        if (!toAdd.isEmpty()) {
            qDebug() << "Adding modems" << toAdd;
        }
        for (const auto& path : toAdd)
        {
            auto modemInterface = make_shared<QOfonoModem>();
            modemInterface->setModemPath(path);

            auto modem = make_shared<wwan::Modem>(modemInterface);
            m_ofonoLinks[path] = modem;
            connect(modem.get(), &wwan::Modem::readyToUnlock, this, &Private::modemReadyToUnlock);
            connect(modem.get(), &wwan::Modem::ready, this, &Private::modemReady);

            if (!m_deferredUnlockAllModemTimer.hasExpired())
                p.unlockModem(modem);

            for (const auto &nmobjpath : m_nmDevices.keys())
            {
                auto dev = make_shared<OrgFreedesktopNetworkManagerDeviceInterface>(
                            NM_DBUS_SERVICE,
                            nmobjpath.path(),
                            m_nm->connection());
                if (dev->udi() == path) {
                    modem->setNmPath(nmobjpath.path());
                    m_statisticsMonitor->addLink(modem);
                    break;
                }
            }
        }

        Q_EMIT p.linksUpdated();
        m_unlockDialog->setShowSimIdentifiers(m_ofonoLinks.size() > 1);

        updateModemAvailable();
    }

    void setMobileDataEnabled(bool value) {
        if (m_mobileDataEnabled == value)
        {
            return;
        }

        m_settings->setMobileDataEnabled(value);
        m_mobileDataEnabled = value;
        Q_EMIT p.mobileDataEnabledChanged(value);

        if (m_mobileDataEnabled)
        {
            for (wwan::Modem::Ptr modem : m_modems)
            {
                if (modem->sim() && modem->sim() == m_simForMobileData)
                {
                    modem->sim()->setMobileDataEnabled(true);
                }
            }
        }
        else
        {
            for (wwan::Modem::Ptr modem : m_modems)
            {
                if (modem->sim()) {
                    modem->sim()->setMobileDataEnabled(false);
                }
            }
        }
    }

    void setSimForMobileData(wwan::Sim::Ptr sim) {
        if (m_simForMobileData == sim)
        {
            return;
        }

        if (m_simForMobileData)
        {
            m_simForMobileData->setMobileDataEnabled(false);
        }

        if (!sim)
        {
            m_settings->setSimForMobileData("");
        }
        else
        {
            m_settings->setSimForMobileData(sim->iccid());
        }


        m_simForMobileData = sim;
        if (m_simForMobileData)
        {
            m_simForMobileData->setMobileDataEnabled(m_mobileDataEnabled);
        }

        m_ofonoNemoSlotManager->setDataSim(m_simForMobileData);

        Q_EMIT p.simForMobileDataChanged();
    }
};

void
ManagerImpl::updateNetworkingStatus(uint status)
{
    switch(status) {
    case NM_STATE_UNKNOWN:
    case NM_STATE_ASLEEP:
    case NM_STATE_DISCONNECTED:
    case NM_STATE_DISCONNECTING:
    {
        d->m_status = NetworkingStatus::offline;
        break;
    }
    case NM_STATE_CONNECTING:
    {
        d->m_status = NetworkingStatus::connecting;
        break;
    }
    case NM_STATE_CONNECTED_LOCAL:
    case NM_STATE_CONNECTED_SITE:
    case NM_STATE_CONNECTED_GLOBAL:
    {
        d->m_status = NetworkingStatus::online;
        break;
    }
    }

    Q_EMIT statusUpdated(d->m_status);
}

ManagerImpl::ManagerImpl(notify::NotificationManager::SPtr notificationManager,
                         FlightModeToggle::SPtr flightModeToggle,
                         wifi::WifiToggle::SPtr wifiToggle,
                         HotspotManager::SPtr hotspotManager,
                         connection::ActiveConnectionManager::SPtr activeConnectionManager,
                         const QDBusConnection& systemConnection) :
        d(new ManagerImpl::Private(*this))
{
    d->m_nm = make_shared<OrgFreedesktopNetworkManagerInterface>(NM_DBUS_SERVICE, NM_DBUS_PATH, systemConnection);
    d->nm_prop = make_shared<OrgFreedesktopDBusPropertiesInterface>(NM_DBUS_SERVICE, NM_DBUS_PATH, systemConnection);
    d->m_settingsInterface = make_shared<OrgFreedesktopNetworkManagerSettingsInterface>(
                    NM_DBUS_SERVICE, NM_DBUS_PATH_SETTINGS, systemConnection);
    d->m_activeConnectionManager = activeConnectionManager;

    d->m_unlockDialog = make_shared<SimUnlockDialog>(notificationManager);
    connect(d->m_unlockDialog.get(), &SimUnlockDialog::ready, d.get(), &Private::sim_unlock_ready);

    d->m_statisticsMonitor = make_shared<NMDeviceStatisticsMonitor>();

    connect(d->m_statisticsMonitor.get(), &NMDeviceStatisticsMonitor::txChanged, this, &ManagerImpl::txChanged);
    connect(d->m_statisticsMonitor.get(), &NMDeviceStatisticsMonitor::rxChanged, this, &ManagerImpl::rxChanged);


    d->m_ofono = make_shared<QOfonoManager>();

    // Load the SIM manager before we connect to the signals
    d->m_settings = make_shared<ConnectivityServiceSettings>();
    d->m_simManager = make_shared<wwan::SimManager>(d->m_ofono, d->m_settings, d->m_activeConnectionManager);
    connect(d->m_simManager.get(), &wwan::SimManager::simAdded, d.get(), &Private::simAdded);
    d->m_sims = d->m_simManager->knownSims();
    for (auto sim : d->m_sims)
    {
        connect(sim.get(), &wwan::Sim::presentChanged, d.get(), &Private::matchModemsAndSims);
    }
    d->m_ofonoNemoSlotManager = make_shared<wwan::OfonoNemoSlotManager>(systemConnection);

    connect(d->m_ofono.get(), &QOfonoManager::modemsChanged, d.get(), &Private::modems_changed);
    d->modems_changed(d->m_ofono->modems());

    d->m_flightModeToggle = flightModeToggle;
    connect(d->m_flightModeToggle.get(), &FlightModeToggle::flightModeChanged, this, &Manager::flightModeUpdated);

    d->m_wifiToggle = wifiToggle;
    connect(d->m_wifiToggle.get(), &wifi::WifiToggle::stateChanged, d.get(), &Private::updateHasWifi);
    connect(d->m_wifiToggle.get(), &wifi::WifiToggle::enabledChanged, this, &Manager::wifiEnabledUpdated);

    d->m_hotspotManager = hotspotManager;
    connect(d->m_hotspotManager.get(), &HotspotManager::enabledChanged, this, &Manager::hotspotEnabledChanged);
    connect(d->m_hotspotManager.get(), &HotspotManager::ssidChanged, this, &Manager::hotspotSsidChanged);
    connect(d->m_hotspotManager.get(), &HotspotManager::passwordChanged, this, &Manager::hotspotPasswordChanged);
    connect(d->m_hotspotManager.get(), &HotspotManager::modeChanged, this, &Manager::hotspotModeChanged);
    connect(d->m_hotspotManager.get(), &HotspotManager::authChanged, this, &Manager::hotspotAuthChanged);
    connect(d->m_hotspotManager.get(), &HotspotManager::storedChanged, this, &Manager::hotspotStoredChanged);

    connect(d->m_hotspotManager.get(), &HotspotManager::reportError, this, &Manager::reportError);

    connect(d->m_nm.get(), &OrgFreedesktopNetworkManagerInterface::DeviceAdded, this, &ManagerImpl::device_added);
    QList<QDBusObjectPath> devices = d->m_nm->GetDevices();
    for(const auto &path : devices) {
        device_added(path);
    }

    connect(d->m_nm.get(), &OrgFreedesktopNetworkManagerInterface::DeviceRemoved, this, &ManagerImpl::device_removed);
    updateNetworkingStatus(d->m_nm->state());
    connect(d->nm_prop.get(), &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &ManagerImpl::nm_properties_changed);

    /// @todo set by the default connections.
    d->m_characteristics = Link::Characteristics::empty;

    d->loadSettings();

    d->updateHasWifi();

    d->m_checkSimForMobileDataTimer.setInterval(5000);
    d->m_checkSimForMobileDataTimer.setSingleShot(true);
    connect(&d->m_checkSimForMobileDataTimer, &QTimer::timeout, d.get(), &Private::checkSimForMobileData);
    for(auto sim : d->m_sims) {
        connect(sim.get(), &wwan::Sim::presentChanged, d.get(), &Private::startCheckSimForMobileDataTimer);
    }
    d->m_checkSimForMobileDataTimer.start();
}

bool
ManagerImpl::wifiEnabled() const
{
    return d->m_wifiToggle->isEnabled() && (d->m_nm && d->m_nm->wirelessEnabled());
}


void
ManagerImpl::setWifiEnabled(bool enabled)
{
    if (!d->m_hasWifi)
    {
        return;
    }

    if (wifiEnabled() == enabled)
    {
        return;
    }

    qDebug() << "Setting WiFi enabled =" << enabled;

    d->setUnstoppableOperationHappening(true);
    // Disable hotspot before disabling WiFi
    if (!enabled && d->m_hotspotManager->enabled())
    {
        d->m_hotspotManager->setEnabled(false);
    }

    setMtkWifiEnabled(enabled);

    d->m_wifiToggle->setEnabled(enabled);
    d->setUnstoppableOperationHappening(false);
}

bool
ManagerImpl::modemAvailable() const
{
    return !d->m_ofonoLinks.empty();
}

bool
ManagerImpl::hotspotEnabled() const
{
    return d->m_hotspotManager->enabled();
}

void
ManagerImpl::setHotspotEnabled(bool enabled)
{
    qDebug() << "Setting hotspot enabled =" << enabled;

    if (d->m_hotspotManager->enabled() == enabled)
    {
        return;
    }

    if (enabled && d->m_flightModeToggle->isFlightMode())
    {
        qWarning() << "Cannot set hotspot enabled when flight mode is on";
        return;
    }

    d->setUnstoppableOperationHappening(true);

    if (enabled && !d->m_wifiToggle->isEnabled())
    {
        d->m_wifiToggle->setEnabled(true);
    }

    d->m_hotspotManager->setEnabled(enabled);
    d->setUnstoppableOperationHappening(false);
}

void
ManagerImpl::setFlightMode(bool enabled)
{
    if (enabled == d->m_flightModeToggle->isFlightMode())
    {
        return;
    }

    qDebug() << "Setting flight mode enabled =" << enabled;

    d->setUnstoppableOperationHappening(true);
    // Disable hotspot before enabling flight mode
    if (enabled && d->m_hotspotManager->enabled())
    {
        d->m_hotspotManager->setEnabled(false);
    }
    if (!d->m_flightModeToggle->setFlightMode(enabled))
    {
        qWarning() << "Failed to change flightmode.";
    }
    d->setUnstoppableOperationHappening(false);
}

bool
ManagerImpl::flightMode() const
{
    return d->m_flightModeToggle->isFlightMode();
}

bool
ManagerImpl::flightModeAvailable() const
{
    return d->m_flightModeToggle->isValid();
}

void ManagerImpl::nm_properties_changed(const QString&,
                                        const QVariantMap &properties,
                                        const QStringList&)
{
    auto stateIt = properties.find("State");
    if (stateIt != properties.cend())
    {
        updateNetworkingStatus(stateIt->toUInt());
    }
}

void
ManagerImpl::device_removed(const QDBusObjectPath &path)
{
    qDebug() << "Device Removed:" << path.path();

    d->m_statisticsMonitor->remove(path.path());

    Link::SPtr toRemove;
    for (auto it = d->m_nmDevices.cbegin(); it != d->m_nmDevices.cend(); ++it)
    {
        auto link = it.value();

        if (link && it.key() == path)
        {
            toRemove = link;
            break;
        }
    }
    if (toRemove)
    {
        d->m_nmDevices.remove(path);
        Q_EMIT linksUpdated();
        d->updateHasWifi();
    }
}

void
ManagerImpl::device_added(const QDBusObjectPath &path)
{
    qDebug() << "Device Added:" << path.path();

    for (const auto &dev : d->m_nmDevices.values())
    {
        auto wifiLink = dynamic_pointer_cast<wifi::WifiLinkImpl>(dev);
        if (wifiLink && wifiLink->device_path() == path) {
            // already in the list
            return;
        }
    }

    Link::SPtr link;
    try {
        auto dev = make_shared<OrgFreedesktopNetworkManagerDeviceInterface>(
            NM_DBUS_SERVICE, path.path(), d->m_nm->connection());
        switch (dev->deviceType())
        {
            case NM_DEVICE_TYPE_WIFI:
            {
                wifi::WifiLink::SPtr tmp = make_shared<wifi::WifiLinkImpl>(dev,
                                                    d->m_nm,
                                                    d->m_wifiToggle,
                                                    d->m_activeConnectionManager);

                // We're not interested in showing access points
                if (tmp->name() != d->m_hotspotManager->interface())
                {
                    tmp->setDisconnectWifi(d->m_hotspotManager->disconnectWifi());
                    QObject::connect(d->m_hotspotManager.get(), &HotspotManager::disconnectWifiChanged,
                            tmp.get(), &wifi::WifiLink::setDisconnectWifi);

                    link = tmp;
                }
                break;
            }
            case NM_DEVICE_TYPE_ETHERNET:
            {
                link = make_shared<ethernet::EthernetLink>(
                        dev, d->m_settingsInterface, d->m_activeConnectionManager);
                break;
            }
            case NM_DEVICE_TYPE_MODEM:
            {
                if (dev->driver() == "ofono")
                {
                    for (const auto &modem : d->m_ofonoLinks)
                    {
                        if (modem->ofonoPath() == dev->udi())
                        {
                            modem->setNmPath(path.path());
                            d->m_statisticsMonitor->addLink(modem);
                            break;
                        }
                    }
                }
                break;
            }
            default:
                break;
        }
    } catch (const exception &e) {
        qDebug() << ": failed to create Device proxy for "<< path.path() << ": ";
        qDebug() << "\t" << e.what();
        qDebug() << "\tIgnoring.";
        return;
    }

    if (link) {
        d->m_nmDevices.insert(path, link);
        Q_EMIT linksUpdated();

        d->m_statisticsMonitor->addLink(link);
    }

    d->updateHasWifi();
}


bool
ManagerImpl::unstoppableOperationHappening() const
{
    return d->m_unstoppableOperationHappening;
}

QSet<Link::SPtr>
ManagerImpl::links() const
{
    QList<Link::SPtr> result(d->m_nmDevices.values());
    for (auto modem : d->m_ofonoLinks) {
        result.append(modem);
    }

    return result.toSet();
}

Manager::NetworkingStatus
ManagerImpl::status() const
{
    return d->m_status;
}

uint32_t
ManagerImpl::characteristics() const
{
    return d->m_characteristics;
}

bool
ManagerImpl::hasWifi() const
{
    return d->m_hasWifi;
}

bool
ManagerImpl::roaming() const
{
    for (auto modem : d->m_ofonoLinks)
    {
        if (modem->modemStatus() == wwan::Modem::ModemStatus::roaming) {
            return true;
        }
    }

    return false;
}

void
ManagerImpl::unlockModem(wwan::Modem::Ptr modem)
{
    try {
        if (!d->m_ofonoLinks.values().contains(modem)
                || d->m_unlockDialog->modem() == modem
                || count(d->m_pendingUnlocks.begin(), d->m_pendingUnlocks.end(), modem) != 0)
        {
            qDebug() << "Didn't unlock modem because it's already being unlocked or is queued for unlock" << modem->simIdentifier();
            return;
        }

        if (d->m_unlockDialog->state() == SimUnlockDialog::State::ready
                && (d->m_pendingUnlocks.size() == 0))
        {
            if (modem->isReadyToUnlock())
            {
                qDebug() << "Unlocking modem" << modem->simIdentifier();
                d->m_unlockDialog->unlock(modem);
            }
            else
            {
                qDebug() << "Waiting for modem to be ready" << modem->simIdentifier();
                modem->notifyWhenReadyToUnlock();
            }
        }
        else
        {
            qDebug() << "Queueing modem for unlock" << modem->simIdentifier();
            d->m_pendingUnlocks.push_back(modem);
        }
    } catch(const exception &e) {
        // Something unexpected has happened. As an example, unity8 might have
        // crashed taking the notification server with it. There is no graceful
        // and reliable way to recover so die and get restarted.
        // See also https://bugs.launchpad.net/unity-notifications/+bug/1238990
        qWarning() << " sim unlocking failed: " << QString::fromStdString(e.what());
    }
}

void
ManagerImpl::unlockAllModems()
{
    qDebug() << "Unlock all modems";
    for (auto& m : d->m_ofonoLinks)
    {
        unlockModem(m);
    }

    /* Since modems can come up later (due to RIL being slow to start or
     * whatever), also considers modem which gets added in the next ~30
     * seconeds or so to also be within this request.
     */
    d->m_deferredUnlockAllModemTimer.setRemainingTime(std::chrono::seconds(30));
}

void
ManagerImpl::unlockModemByName(const QString &name)
{
    qDebug() << "Unlock modem:" << name;
    auto it = d->m_ofonoLinks.find(name);
    if (it != d->m_ofonoLinks.cend())
    {
        unlockModem(it.value());
    }
}


QSet<wifi::WifiLink::SPtr>
ManagerImpl::wifiLinks() const
{
    QSet<wifi::WifiLink::SPtr> result;
    for(auto link: d->m_nmDevices.values())
    {
        if (link->type() == Link::Type::wifi && link->isManaged())
        {
            result.insert(dynamic_pointer_cast<wifi::WifiLink>(link));
        }
    }
    return result;
}

QSet<ethernet::EthernetLink::SPtr>
ManagerImpl::ethernetLinks() const
{
    QSet<ethernet::EthernetLink::SPtr> result;
    for(auto link: d->m_nmDevices.values())
    {
        if (link->type() == Link::Type::ethernet && link->isManaged())
        {
            result.insert(dynamic_pointer_cast<ethernet::EthernetLink>(link));
        }
    }
    return result;
}

QSet<wwan::Modem::Ptr>
ManagerImpl::modemLinks() const
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
    return d->m_ofonoLinks.values().toSet();
#else
    auto modemLinksList = d->m_ofonoLinks.values();
    return QSet<std::shared_ptr<nmofono::wwan::Modem>>(
        modemLinksList.begin(), modemLinksList.end());
#endif
}

bool
ManagerImpl::hotspotStored() const
{
    return d->m_hotspotManager->stored();
}

QByteArray
ManagerImpl::hotspotSsid() const
{
    return d->m_hotspotManager->ssid();
}

QString
ManagerImpl::hotspotPassword() const
{
    return d->m_hotspotManager->password();
}

QString
ManagerImpl::hotspotMode() const
{
    return d->m_hotspotManager->mode();
}

QString
ManagerImpl::hotspotAuth() const
{
    return d->m_hotspotManager->auth();
}

void
ManagerImpl::setHotspotSsid(const QByteArray& ssid)
{
    d->m_hotspotManager->setSsid(ssid);
}

void
ManagerImpl::setHotspotPassword(const QString& password)
{
    d->m_hotspotManager->setPassword(password);
}

void
ManagerImpl::setHotspotMode(const QString& mode)
{
    d->m_hotspotManager->setMode(mode);
}

void
ManagerImpl::setHotspotAuth(const QString& auth)
{
    d->m_hotspotManager->setAuth(auth);
}

bool
ManagerImpl::mobileDataEnabled() const
{
    return d->m_mobileDataEnabled;
}

void
ManagerImpl::setMobileDataEnabled(bool value)
{
    d->setMobileDataEnabled(value);
}

wwan::Sim::Ptr
ManagerImpl::simForMobileData() const
{
    return d->m_simForMobileData;
}

void
ManagerImpl::setSimForMobileData(wwan::Sim::Ptr sim)
{
    d->setSimForMobileData(sim);
}

QList<wwan::Modem::Ptr>
ManagerImpl::modems() const
{
    return d->m_modems;
}

QList<wwan::Sim::Ptr>
ManagerImpl::sims() const
{
    return d->m_sims;
}

void
ManagerImpl::setMtkWifiEnabled(bool enabled)
{
    // Enable/disable MediaTek wmtWifi adapter for power savings when WLAN is
    // toggled on/off.
    QFile wmtWifi_file("/dev/wmtWifi");
    if (wmtWifi_file.exists())
    {
        if (wmtWifi_file.open(QIODevice::WriteOnly)) {
            // Configure new adapter enabled state
            const char newAdapterEnabledState = (enabled ? '1' : '0');
            qDebug() << "setMtkWifiEnabled() -> Opened the wmtWifi device for writing, configuring new state =" << newAdapterEnabledState;
            if (!wmtWifi_file.putChar(newAdapterEnabledState))
                qWarning() << "setMtkWifiEnabled() -> An error occurred while changing the wmtWifi adapter enabled state!";
            wmtWifi_file.close();
        } else
            qWarning() << "setMtkWifiEnabled() -> Couldn't open /dev/wmtWifi for writing! (insufficient permissions?)";
    }
}

bool
ManagerImpl::tx() const
{
    return d->m_statisticsMonitor->tx();
}

bool
ManagerImpl::rx() const
{
    return d->m_statisticsMonitor->rx();
}

}

#include "manager-impl.moc"
