/*  This file is part of the KDE project
    Copyright (C) 2008 Dario Freddi <drf54321@gmail.com>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.

*/

#include "networkmanager.h"

#include <QtCore/QDir>
#include <QtCore/QFileInfo>

#include <kdirwatch.h>
#include <kdebug.h>

class SysClassNetworkManagerPrivate
{
public:
    SysClassNetworkManagerPrivate();
    ~SysClassNetworkManagerPrivate() { delete watch; }
    void populateWatch();
    void recacheStatus();

    Solid::Networking::Status cachedStatus;
    Solid::Networking::Status wirelessStatus;
    bool wirelessEnabled;
    bool wirelessHardwareEnabled;
    KDirWatch *watch;
    QStringList files;
};

SysClassNetworkManagerPrivate::SysClassNetworkManagerPrivate()
        : cachedStatus(Solid::Networking::Unknown)
        , wirelessStatus(Solid::Networking::Unknown)
        , watch(new KDirWatch())
{
    populateWatch();
    recacheStatus();
}


void SysClassNetworkManagerPrivate::populateWatch()
{
    kDebug() << "Populating the watch";

    QDir dir("/sys/class/net");

    if (!dir.exists()) {
        return;
    }

    dir.setFilter(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot);

    const QFileInfoList list = dir.entryInfoList();
    for (int i = 0; i < list.size(); ++i) {
        QFileInfo fileInfo = list.at(i);
        if (fileInfo.baseName() == "lo" || fileInfo.baseName().startsWith("wmaster")) {
            continue;
        }

        if (!QFile::exists(fileInfo.absoluteFilePath() + "/operstate")) {
            kDebug() << "Couldn't find a valid " + fileInfo.absoluteFilePath() + "/operstate";
        } else {
            kDebug() << "Adding " + fileInfo.absoluteFilePath() + "/operstate";
            watch->addFile(fileInfo.absoluteFilePath() + "/operstate");
            files.append(fileInfo.absoluteFilePath() + "/operstate");
        }
    }
}

void SysClassNetworkManagerPrivate::recacheStatus()
{

    Solid::Networking::Status status = Solid::Networking::Unknown;
    Solid::Networking::Status wstatus = Solid::Networking::Unknown;

    foreach (const QString &path, files) {
        QFile file(path);

        if (!file.open(QFile::ReadOnly | QFile::Text)) {
            kDebug() << "Could not open " + path;
        }

        QString contents(file.readAll());

        if (contents.contains("up")) {
            status = Solid::Networking::Connected;
        } else if (contents.contains("down")) {
            if (status == Solid::Networking::Unknown) {
                status = Solid::Networking::Unconnected;
            }
        }

        if (!path.contains("eth")) {
            // Wireless!
            if (contents.contains("up")) {
                wstatus = Solid::Networking::Connected;
            } else if (contents.contains("down")) {
                if (wstatus == Solid::Networking::Unknown) {
                    wstatus = Solid::Networking::Unconnected;
                }
            }
        }

        file.close();
    }

    if (status != cachedStatus) {
        cachedStatus = status;
    }

    if (wstatus != wirelessStatus) {
        wirelessStatus = wstatus;
        switch (wirelessStatus) {
            case Solid::Networking::Connected:
                if (!wirelessEnabled) {
                    wirelessEnabled = true;
                }
                if (!wirelessHardwareEnabled) {
                    wirelessHardwareEnabled = true;
                }
                break;
            case Solid::Networking::Unconnected:
                if (wirelessEnabled) {
                    wirelessEnabled = false;
                }
                if (!wirelessHardwareEnabled) {
                    wirelessHardwareEnabled = true;
                }
                break;
            case Solid::Networking::Unknown:
                if (wirelessEnabled) {
                    wirelessEnabled = false;
                }
                if (wirelessHardwareEnabled) {
                    wirelessHardwareEnabled = false;
                }
                break;
            default:
                break;
        }
    }
}

SysClassNetworkManager::SysClassNetworkManager(QObject * parent, const QVariantList  & /*args */)
        : NetworkManager(parent)
        , d_ptr(new SysClassNetworkManagerPrivate())
{
    Q_D(SysClassNetworkManager);
    connect(d->watch, SIGNAL(dirty(const QString&)), this, SLOT(recacheStatus()));
}

SysClassNetworkManager::~SysClassNetworkManager()
{
    delete d_ptr;
}

Solid::Networking::Status SysClassNetworkManager::status() const
{
    Q_D(const SysClassNetworkManager);
    return d->cachedStatus;
}

QStringList SysClassNetworkManager::networkInterfaces() const
{
    QDir dir("/sys/class/net");

    if (!dir.exists()) {
        return QStringList();
    }

    QStringList retlist;

    dir.setFilter(QDir::Dirs | QDir::Hidden | QDir::NoDotAndDotDot);

    const QFileInfoList list = dir.entryInfoList();
    for (int i = 0; i < list.size(); ++i) {
        QFileInfo fileInfo = list.at(i);
        if (fileInfo.baseName() == "lo" || fileInfo.baseName().startsWith("wmaster")) {
            continue;
        }

        retlist.append(fileInfo.baseName());
    }

    return retlist;
}

QObject * SysClassNetworkManager::createNetworkInterface(const QString &)
{
    return 0;
}

bool SysClassNetworkManager::isNetworkingEnabled() const
{
    Q_D(const SysClassNetworkManager);

    return Solid::Networking::Connecting == d->cachedStatus ||
           Solid::Networking::Connected == d->cachedStatus ||
           Solid::Networking::Unconnected == d->cachedStatus;
}

bool SysClassNetworkManager::isWirelessEnabled() const
{
    Q_D(const SysClassNetworkManager);

    return d->wirelessEnabled;
}

bool SysClassNetworkManager::isWirelessHardwareEnabled() const
{
    Q_D(const SysClassNetworkManager);

    return d->wirelessHardwareEnabled;
}

void SysClassNetworkManager::setNetworkingEnabled(bool)
{

}

void SysClassNetworkManager::setWirelessEnabled(bool)
{

}

void SysClassNetworkManager::recacheStatus()
{
    Q_D(SysClassNetworkManager);

    Solid::Networking::Status status = d->cachedStatus;
    Solid::Networking::Status wstatus = d->wirelessStatus;
    bool wirelessEnabled = d->wirelessEnabled;
    bool wirelessHardwareEnabled = d->wirelessHardwareEnabled;

    d->recacheStatus();

    if (status != d->cachedStatus) {
        emit statusChanged(d->cachedStatus);
    }

    if (wstatus != d->wirelessStatus) {
        switch (d->wirelessStatus) {
            case Solid::Networking::Connected:
                if (!wirelessEnabled) {
                    emit wirelessEnabledChanged(d->wirelessEnabled);
                }
                if (!wirelessHardwareEnabled) {
                    emit wirelessHardwareEnabledChanged(d->wirelessHardwareEnabled);
                }
                break;
            case Solid::Networking::Unconnected:
                if (wirelessEnabled) {
                    emit wirelessEnabledChanged(d->wirelessEnabled);
                }
                if (!wirelessHardwareEnabled) {
                    emit wirelessHardwareEnabledChanged(d->wirelessHardwareEnabled);
                }
                break;
            case Solid::Networking::Unknown:
                if (wirelessEnabled) {
                    emit wirelessEnabledChanged(d->wirelessEnabled);
                }
                if (wirelessHardwareEnabled) {
                    emit wirelessHardwareEnabledChanged(d->wirelessHardwareEnabled);
                }
                break;
            default:
                break;
        }
    }
}

QStringList SysClassNetworkManager::activeConnections() const
{
    return QStringList();
}

void SysClassNetworkManager::activateConnection(const QString &, const QString &, const QVariantMap &)
{
    // Not implemented
}

void SysClassNetworkManager::deactivateConnection(const QString &)
{
    // Not implemented
}

Solid::Control::NetworkInterface::Types SysClassNetworkManager::supportedInterfaceTypes() const
{
    return (Solid::Control::NetworkInterface::Types)(
           Solid::Control::NetworkInterface::Ieee8023 |
           Solid::Control::NetworkInterface::Ieee80211
           );
}

#include "networkmanager.moc"
