/*
 * Copyright 2013-2014 Canonical Ltd.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of version 3 of the GNU Lesser General Public
 * License 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
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <QDebug>
#include <QScopedPointer>
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusPendingReply>

#include <glog/logging.h>

#include "network_session.h"

namespace {
    const QString NM_PATH = "org.freedesktop.NetworkManager";
    const QString NM_OBJ_PATH = "/org/freedesktop/NetworkManager";
    const QString NM_INTERFACE = "org.freedesktop.NetworkManager";
    const QString PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties";
    const QString NM_PROPERTY = "PrimaryConnectionType";
}

namespace Lomiri {

namespace Transfers {

namespace System {

NetworkSession* NetworkSession::_instance = nullptr;
QMutex NetworkSession::_mutex;

NetworkSession::NetworkSession(QObject* parent)
    : QObject(parent) {

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    QNetworkInformation::loadDefaultBackend();
    CHECK(QNetworkInformation::instance())
        << "Unable to load QNetworkInformation plugin.";

    CHECK(connect(QNetworkInformation::instance(), &QNetworkInformation::reachabilityChanged,
               this, &NetworkSession::onReachabilityChanged))
            << "Could not connect to signal";
#else
    _configManager = new QNetworkConfigurationManager();

    CHECK(connect(_configManager, &QNetworkConfigurationManager::onlineStateChanged,
                this, &NetworkSession::onlineStateChanged))
             << "Could not connect to signal";
#endif

    _nm = new NMInterface(NM_PATH, NM_OBJ_PATH, QDBusConnection::systemBus());

    CHECK(connect(_nm, &NMInterface::PropertiesChanged,
        this, &NetworkSession::onPropertiesChanged))
	    << "Could not connect to signal";

    QScopedPointer<QDBusInterface> interface(new QDBusInterface(NM_PATH, NM_OBJ_PATH,
        PROPERTIES_INTERFACE, QDBusConnection::systemBus()));

    QDBusPendingReply<QDBusVariant> reply =
        interface->call("Get", NM_INTERFACE, NM_PROPERTY);

    reply.waitForFinished();
    if (!reply.isError()) {
        LOG(INFO) << "Connection type " << reply.value().variant().toString().toStdString();
        _sessionType = convertNMString(reply.value().variant().toString());
    } else {
        _error = true;
        _errorMsg = reply.error().message();
        LOG(ERROR) << _errorMsg.toStdString();
    }

}

NetworkSession::~NetworkSession() {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    delete _configManager;
#endif
}

bool
NetworkSession::isError() {
    return _error;
}

bool
NetworkSession::isOnline() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    return QNetworkInformation::instance()->reachability() == QNetworkInformation::Reachability::Online;
#else
    return _configManager->isOnline();
#endif
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QNetworkInformation::TransportMedium
#else
QNetworkConfiguration::BearerType
#endif
NetworkSession::sessionType() {
    return _sessionType;
}

NetworkSession*
NetworkSession::instance() {
    if(_instance == nullptr) {
        LOG(INFO) << "Instance is null";
        _mutex.lock();
        if(_instance == nullptr){
            LOG(INFO) << "Create new instance";
            _instance = new NetworkSession();
        }
        _mutex.unlock();
    }
    return _instance;
}

void
NetworkSession::setInstance(NetworkSession* instance) {
    _instance = instance;
}

void
NetworkSession::deleteInstance() {
    if(_instance != nullptr) {
        _mutex.lock();
        if(_instance != nullptr) {
            delete _instance;
            _instance = nullptr;
        }
        _mutex.unlock();
    }
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QNetworkInformation::TransportMedium
#else
QNetworkConfiguration::BearerType
#endif
NetworkSession::convertNMString(const QString& str) {
    if (str.contains("wireless")) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        return QNetworkInformation::TransportMedium::WiFi;
#else
        return QNetworkConfiguration::BearerWLAN;
#endif
    }

    if (str.contains("ethernet")) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        return QNetworkInformation::TransportMedium::Ethernet;
#else
        return QNetworkConfiguration::BearerEthernet;
#endif
    }

    if (str == "gsm") {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        return QNetworkInformation::TransportMedium::Cellular;
#else
        return QNetworkConfiguration::Bearer3G;
#endif
    }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    return QNetworkInformation::TransportMedium::Unknown;
#else
    return QNetworkConfiguration::BearerUnknown;
#endif
}

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
void
NetworkSession::onReachabilityChanged() {
    Q_EMIT onlineStateChanged(isOnline());
}
#endif

void
NetworkSession::onPropertiesChanged(const QVariantMap& changedProperties) {
    if (changedProperties.contains(NM_PROPERTY)) {
	auto nmStr = changedProperties[NM_PROPERTY].toString();
	LOG(INFO) << "Connection type " << nmStr.toStdString();
	auto type = convertNMString(nmStr);
	if (type != _sessionType) {
            _sessionType = type;
            emit sessionTypeChanged(type);
	}
    }

}

}

}

}
