/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "bearermonitor.h"
#include "sessionwidget.h"

#include <QtCore/QDebug>

#ifdef Q_OS_WIN
#include <winsock2.h>
#undef interface

#ifndef NS_NLA
#define NS_NLA 15
#endif
#endif

BearerMonitor::BearerMonitor(QWidget *parent)
:   QWidget(parent)
{
    setupUi(this);
    delete tabWidget->currentWidget();
    sessionGroup->hide();
    updateConfigurations();
    onlineStateChanged(!manager.allConfigurations(QNetworkConfiguration::Active).isEmpty());
    QNetworkConfiguration defaultConfiguration = manager.defaultConfiguration();
    for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = treeWidget->topLevelItem(i);

        if (item->data(0, Qt::UserRole).toString() == defaultConfiguration.identifier()) {
            treeWidget->setCurrentItem(item);
            showConfigurationFor(item);
            break;
        }
    }
    connect(&manager, SIGNAL(onlineStateChanged(bool)), this ,SLOT(onlineStateChanged(bool)));
    connect(&manager, SIGNAL(configurationAdded(const QNetworkConfiguration&)),
            this, SLOT(configurationAdded(const QNetworkConfiguration&)));
    connect(&manager, SIGNAL(configurationRemoved(const QNetworkConfiguration&)),
            this, SLOT(configurationRemoved(const QNetworkConfiguration&)));
    connect(&manager, SIGNAL(configurationChanged(const QNetworkConfiguration&)),
            this, SLOT(configurationChanged(const QNetworkConfiguration)));
    connect(&manager, SIGNAL(updateCompleted()), this, SLOT(updateConfigurations()));

#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
    connect(registerButton, SIGNAL(clicked()), this, SLOT(registerNetwork()));
    connect(unregisterButton, SIGNAL(clicked()), this, SLOT(unregisterNetwork()));
#else // Q_OS_WIN && !Q_OS_WINRT
    nlaGroup->hide();
#endif

    connect(treeWidget, SIGNAL(itemActivated(QTreeWidgetItem*,int)),
            this, SLOT(createSessionFor(QTreeWidgetItem*)));

    connect(treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
            this, SLOT(showConfigurationFor(QTreeWidgetItem*)));

    connect(newSessionButton, SIGNAL(clicked()),
            this, SLOT(createNewSession()));
    connect(deleteSessionButton, SIGNAL(clicked()),
            this, SLOT(deleteSession()));
    connect(scanButton, SIGNAL(clicked()),
            this, SLOT(performScan()));

    // Just in case update all configurations so that all
    // configurations are up to date.
    manager.updateConfigurations();
}

BearerMonitor::~BearerMonitor()
{
}

static void updateItem(QTreeWidgetItem *item, const QNetworkConfiguration &config)
{
    item->setText(0, config.name());
    item->setData(0, Qt::UserRole, config.identifier());

    QFont font = item->font(1);
    font.setBold((config.state() & QNetworkConfiguration::Active) == QNetworkConfiguration::Active);
    item->setFont(0, font);
}

void BearerMonitor::configurationAdded(const QNetworkConfiguration &config, QTreeWidgetItem *parent)
{
    if (!config.isValid())
        return;

    QTreeWidgetItem *item = new QTreeWidgetItem;
    updateItem(item, config);

    if (parent)
        parent->addChild(item);
    else
        treeWidget->addTopLevelItem(item);

    if (config.type() == QNetworkConfiguration::ServiceNetwork) {
        const QList<QNetworkConfiguration> children = config.children();
        for (const QNetworkConfiguration &child : children)
            configurationAdded(child, item);
    }
}

void BearerMonitor::configurationRemoved(const QNetworkConfiguration &config)
{
    for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = treeWidget->topLevelItem(i);

        if (item->data(0, Qt::UserRole).toString() == config.identifier()) {
            delete item;
            break;
        }
    }
}

void BearerMonitor::configurationChanged(const QNetworkConfiguration &config)
{
    for (int i = 0; i < treeWidget->topLevelItemCount(); ++i) {
        QTreeWidgetItem *item = treeWidget->topLevelItem(i);

        if (item->data(0, Qt::UserRole).toString() == config.identifier()) {
            updateItem(item, config);

            if (config.type() == QNetworkConfiguration::ServiceNetwork)
                updateSnapConfiguration(item, config);

            if (item == treeWidget->currentItem())
                showConfigurationFor(item);

            break;
        }
    }
}

void BearerMonitor::updateSnapConfiguration(QTreeWidgetItem *parent, const QNetworkConfiguration &snap)
{
    QMap<QString, QTreeWidgetItem *> itemMap;
    const QList<QTreeWidgetItem *> children = parent->takeChildren();
    for (QTreeWidgetItem *item : children)
        itemMap.insert(item->data(0, Qt::UserRole).toString(), item);

    QList<QNetworkConfiguration> allConfigurations = snap.children();

    while (!allConfigurations.isEmpty()) {
        QNetworkConfiguration config = allConfigurations.takeFirst();

        QTreeWidgetItem *item = itemMap.take(config.identifier());
        if (item) {
            updateItem(item, config);

            parent->addChild(item);

            if (config.type() == QNetworkConfiguration::ServiceNetwork)
                updateSnapConfiguration(item, config);
        } else {
            configurationAdded(config, parent);
        }
    }

    qDeleteAll(itemMap);
}

void BearerMonitor::updateConfigurations()
{
    progressBar->hide();
    scanButton->show();

    // Just in case update online state, on Symbian platform
    // WLAN scan needs to be triggered initially to have their true state.
    onlineStateChanged(manager.isOnline());

    QList<QTreeWidgetItem *> items = treeWidget->findItems(QLatin1String("*"), Qt::MatchWildcard);
    QMap<QString, QTreeWidgetItem *> itemMap;
    while (!items.isEmpty()) {
        QTreeWidgetItem *item = items.takeFirst();
        itemMap.insert(item->data(0, Qt::UserRole).toString(), item);
    }

    QNetworkConfiguration defaultConfiguration = manager.defaultConfiguration();
    QTreeWidgetItem *defaultItem = itemMap.take(defaultConfiguration.identifier());

    if (defaultItem) {
        updateItem(defaultItem, defaultConfiguration);

        if (defaultConfiguration.type() == QNetworkConfiguration::ServiceNetwork)
            updateSnapConfiguration(defaultItem, defaultConfiguration);
    } else {
        configurationAdded(defaultConfiguration);
    }

    QList<QNetworkConfiguration> allConfigurations = manager.allConfigurations();

    while (!allConfigurations.isEmpty()) {
        QNetworkConfiguration config = allConfigurations.takeFirst();

        if (config.identifier() == defaultConfiguration.identifier())
            continue;

        QTreeWidgetItem *item = itemMap.take(config.identifier());
        if (item) {
            updateItem(item, config);

            if (config.type() == QNetworkConfiguration::ServiceNetwork)
                updateSnapConfiguration(item, config);
        } else {
            configurationAdded(config);
        }
    }

    qDeleteAll(itemMap);
}

void BearerMonitor::onlineStateChanged(bool isOnline)
{
    if (isOnline)
        onlineState->setText(tr("Online"));
    else
        onlineState->setText(tr("Offline"));
}

#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
void BearerMonitor::registerNetwork()
{
    QTreeWidgetItem *item = treeWidget->currentItem();
    if (!item) return;

    QNetworkConfiguration configuration =
        manager.configurationFromIdentifier(item->data(0, Qt::UserRole).toString());

    const QString name = configuration.name();

    qDebug() << "Registering" << name << "with system";

    WSAQUERYSET networkInfo;
    memset(&networkInfo, 0, sizeof(networkInfo));
    networkInfo.dwSize = sizeof(networkInfo);
    networkInfo.lpszServiceInstanceName = (LPWSTR)name.utf16();
    networkInfo.dwNameSpace = NS_NLA;

    if (WSASetService(&networkInfo, RNRSERVICE_REGISTER, 0) == SOCKET_ERROR)
        qDebug() << "WSASetService(RNRSERVICE_REGISTER) returned" << WSAGetLastError();
}

void BearerMonitor::unregisterNetwork()
{
    QTreeWidgetItem *item = treeWidget->currentItem();
    if (!item) return;

    QNetworkConfiguration configuration =
        manager.configurationFromIdentifier(item->data(0, Qt::UserRole).toString());

    const QString name = configuration.name();

    qDebug() << "Unregistering" << name << "with system";

    WSAQUERYSET networkInfo;
    memset(&networkInfo, 0, sizeof(networkInfo));
    networkInfo.dwSize = sizeof(networkInfo);
    networkInfo.lpszServiceInstanceName = (LPWSTR)name.utf16();
    networkInfo.dwNameSpace = NS_NLA;

    if (WSASetService(&networkInfo, RNRSERVICE_DELETE, 0) == SOCKET_ERROR)
        qDebug() << "WSASetService(RNRSERVICE_DELETE) returned" << WSAGetLastError();
}
#endif // Q_OS_WIN && !Q_OS_WINRT

void BearerMonitor::showConfigurationFor(QTreeWidgetItem *item)
{
    QString identifier;

    if (item)
        identifier = item->data(0, Qt::UserRole).toString();

    QNetworkConfiguration conf = manager.configurationFromIdentifier(identifier);

    switch (conf.state()) {
    case QNetworkConfiguration::Active:
        configurationState->setText(tr("Active"));
        break;
    case QNetworkConfiguration::Discovered:
        configurationState->setText(tr("Discovered"));
        break;
    case QNetworkConfiguration::Defined:
        configurationState->setText(tr("Defined"));
        break;
    case QNetworkConfiguration::Undefined:
        configurationState->setText(tr("Undefined"));
        break;
    default:
        configurationState->setText(QString());
    }

    switch (conf.type()) {
    case QNetworkConfiguration::InternetAccessPoint:
        configurationType->setText(tr("Internet Access Point"));
        break;
    case QNetworkConfiguration::ServiceNetwork:
        configurationType->setText(tr("Service Network"));
        break;
    case QNetworkConfiguration::UserChoice:
        configurationType->setText(tr("User Choice"));
        break;
    case QNetworkConfiguration::Invalid:
        configurationType->setText(tr("Invalid"));
        break;
    default:
        configurationType->setText(QString());
    }

    switch (conf.purpose()) {
    case QNetworkConfiguration::UnknownPurpose:
        configurationPurpose->setText(tr("Unknown"));
        break;
    case QNetworkConfiguration::PublicPurpose:
        configurationPurpose->setText(tr("Public"));
        break;
    case QNetworkConfiguration::PrivatePurpose:
        configurationPurpose->setText(tr("Private"));
        break;
    case QNetworkConfiguration::ServiceSpecificPurpose:
        configurationPurpose->setText(tr("Service Specific"));
        break;
    default:
        configurationPurpose->setText(QString());
    }

    configurationIdentifier->setText(conf.identifier());

    configurationRoaming->setText(conf.isRoamingAvailable() ? tr("Available") : tr("Not available"));

    configurationChildren->setText(QString::number(conf.children().count()));

    configurationName->setText(conf.name());
}

void BearerMonitor::createSessionFor(QTreeWidgetItem *item)
{
    const QString identifier = item->data(0, Qt::UserRole).toString();

    QNetworkConfiguration conf = manager.configurationFromIdentifier(identifier);

    SessionWidget *session = new SessionWidget(conf);

    tabWidget->addTab(session, conf.name());

    sessionGroup->show();

    sessionWidgets.append(session);
}

void BearerMonitor::createNewSession()
{
    QTreeWidgetItem *item = treeWidget->currentItem();
    if (!item) return;

    createSessionFor(item);
}

void BearerMonitor::deleteSession()
{
    SessionWidget *session = qobject_cast<SessionWidget *>(tabWidget->currentWidget());
    if (session) {
        sessionWidgets.removeAll(session);

        delete session;

        if (tabWidget->count() == 0)
            sessionGroup->hide();
    }
}

void BearerMonitor::performScan()
{
    scanButton->hide();
    progressBar->show();
    manager.updateConfigurations();
}
