/*
 * Copyright (C) 2011 ~ 2018 Deepin Technology Co., Ltd.
 *
 * Author:     sbw <sbw@sbw.so>
 *
 * Maintainer: sbw <sbw@sbw.so>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include "networkmanager.h"
#include "networkdevice.h"

NetworkManager *NetworkManager::INSTANCE = nullptr;

NetworkManager *NetworkManager::instance(QObject *parent)
{
    if (!INSTANCE)
        INSTANCE = new NetworkManager(parent);

    return INSTANCE;
}

void NetworkManager::init()
{
    QTimer *dbusCheckTimer = new QTimer;
    dbusCheckTimer->setInterval(100);
    dbusCheckTimer->setSingleShot(false);

    auto checkFunc = [=] {
        if (!m_networkInter->isValid())
            return;

        QTimer::singleShot(100, this, &NetworkManager::reloadDevices);
        QTimer::singleShot(150, this, &NetworkManager::reloadActiveConnections);
        dbusCheckTimer->deleteLater();
    };

    connect(dbusCheckTimer, &QTimer::timeout, checkFunc);
    dbusCheckTimer->start();
}

NetworkManager::GlobalNetworkState NetworkManager::globalNetworkState() const
{
    return GlobalNetworkState(m_networkInter->state());
}

const NetworkDevice::NetworkTypes NetworkManager::states() const
{
    return m_states;
}

const NetworkDevice::NetworkTypes NetworkManager::types() const
{
    return m_types;
}

const QSet<NetworkDevice> NetworkManager::deviceList() const
{
    return m_deviceSet;
}

const QSet<QUuid> NetworkManager::activeConnSet() const
{
    return m_activeConnSet;
}

NetworkDevice::NetworkState NetworkManager::deviceState(const QString &path) const
{
    const auto item = device(path);
    if (item == m_deviceSet.cend())
        return NetworkDevice::Unknow;

    return item->state();
}

bool NetworkManager::deviceEnabled(const QString &path) const
{
    return m_networkInter->IsDeviceEnabled(QDBusObjectPath(path));
}

void NetworkManager::setDeviceEnabled(const QString path, const bool enable)
{
    m_networkInter->EnableDevice(QDBusObjectPath(path), enable);
}

const QString NetworkManager::deviceHwAddr(const QString &path) const
{
    const auto item = device(path);
    if (item == m_deviceSet.cend())
        return QString();

    return item->usingHwAddr();
}

const QString NetworkManager::devicePath(const QString &path) const
{
    const auto item = device(path);
    if (item == m_deviceSet.cend())
        return QString();

    return item->path();
}

const QJsonObject NetworkManager::deviceConnInfo(const QString &path) const
{
    const QString addr = deviceHwAddr(path);
    if (addr.isEmpty())
        return QJsonObject();

    const QJsonDocument infos = QJsonDocument::fromJson(m_networkInter->GetActiveConnectionInfo().value().toUtf8());
    Q_ASSERT(infos.isArray());

    for (auto info : infos.array())
    {
        Q_ASSERT(info.isObject());
        const QJsonObject obj = info.toObject();
        if (obj.contains("HwAddress") && obj.value("HwAddress").toString() == addr)
            return obj;
    }

    return QJsonObject();
}

NetworkManager::NetworkManager(QObject *parent)
    : QObject(parent),

      m_states(NetworkDevice::None),
      m_types(NetworkDevice::None),

      m_networkInter(new DBusNetwork(this))
{
    connect(m_networkInter, &DBusNetwork::StateChanged, this, &NetworkManager::globalNetworkStateChanged);
    connect(m_networkInter, &DBusNetwork::DevicesChanged, this, &NetworkManager::reloadDevices);
    connect(m_networkInter, &DBusNetwork::ActiveConnectionsChanged, this, &NetworkManager::reloadActiveConnections);
}

const QSet<NetworkDevice>::const_iterator NetworkManager::device(const QString &path) const
{
    return std::find_if(m_deviceSet.cbegin(), m_deviceSet.cend(),
                        [&] (const NetworkDevice &dev) {return dev.path() == path;});
}

void NetworkManager::reloadDevices()
{
    const QJsonDocument doc = QJsonDocument::fromJson(m_networkInter->devices().toUtf8());
    Q_ASSERT(doc.isObject());
    const QJsonObject obj = doc.object();

    NetworkDevice::NetworkTypes types = NetworkDevice::None;
    QSet<NetworkDevice> deviceSet;
    for (auto infoList(obj.constBegin()); infoList != obj.constEnd(); ++infoList)
    {
        Q_ASSERT(infoList.value().isArray());
        const NetworkDevice::NetworkType deviceType = NetworkDevice::deviceType(infoList.key());

        const auto list = infoList.value().toArray();
        if (list.isEmpty())
            continue;

        types |= deviceType;

        for (auto device : list)
            deviceSet.insert(NetworkDevice(deviceType, device.toObject()));
    }

    const QSet<NetworkDevice> removedDeviceList = m_deviceSet - deviceSet;
    for (auto dev : removedDeviceList)
        emit deviceRemoved(dev);
    for (auto dev : deviceSet)
    {
        if (m_deviceSet.contains(dev))
            emit deviceChanged(dev);
        else
            emit deviceAdded(dev);
    }

    m_deviceSet = std::move(deviceSet);
    if (m_types == types)
        return;

    m_types = types;
    emit deviceTypesChanged(m_types);

//    qDebug() << "device type: " << m_types;
}

void NetworkManager::reloadActiveConnections()
{
    const QJsonDocument doc = QJsonDocument::fromJson(m_networkInter->activeConnections().toUtf8());
    Q_ASSERT(doc.isObject());
    const QJsonObject obj = doc.object();

    QSet<QUuid> activeConnList;
    for (auto info(obj.constBegin()); info != obj.constEnd(); ++info)
    {
        Q_ASSERT(info.value().isObject());
        const QJsonObject infoObj = info.value().toObject();
        const QUuid uuid = infoObj.value("Uuid").toString();

        activeConnList.insert(uuid);
    }

    const QSet<QUuid> removedConnList = m_activeConnSet - activeConnList;
    m_activeConnSet = std::move(activeConnList);

    reloadNetworkState();

    for (auto uuid : removedConnList)
        emit activeConnectionChanged(uuid);

    for (auto uuid : m_activeConnSet)
        emit activeConnectionChanged(uuid);
}

void NetworkManager::reloadNetworkState()
{
    NetworkDevice::NetworkTypes states = NetworkDevice::None;
    QSet<QString> activedDevices;

    const QJsonDocument doc = QJsonDocument::fromJson(m_networkInter->GetActiveConnectionInfo().value().toUtf8());
    for (const auto info : doc.array())
    {
        const auto detail = info.toObject();
        const QString type = detail.value("ConnectionType").toString();
        const QString device = detail.value("Device").toString();

        activedDevices.insert(device);

        if (type == "wired")
            states |= NetworkDevice::Wired;
        else if (type == "wireless")
            states |= NetworkDevice::Wireless;
    }

    m_activeDeviceSet = std::move(activedDevices);

    if (m_states == states)
        return;

    m_states = states;
    emit networkStateChanged(m_states);

//    qDebug() << "network states: " << m_states;
}
