/* This file is part of KNemo
   Copyright (C) 2004, 2006 Percy Leonhardt <percy@eris23.de>

   KNemo is free software; you can redistribute it and/or modify
   it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of
   the License, or (at your option) any later version.

   KNemo 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 <tqmap.h>
#include <tqregexp.h>
#include <tqstringlist.h>

#include <kdebug.h>
#include <kprocess.h>
#include <tdeio/global.h>

#include "nettoolsbackend.h"

#include "config.h"

NetToolsBackend::NetToolsBackend( TQDict<Interface>& interfaces )
    : TQObject(),
      BackendBase( interfaces ),
      mRouteProcess(0L),
      mIfconfigProcess(0L),
      mIwconfigProcess(0L)
{
}

NetToolsBackend::~NetToolsBackend()
{
    if ( mRouteProcess )
    {
        mRouteProcess->kill();
        delete mRouteProcess;
    }
    if ( mIfconfigProcess )
    {
        mIfconfigProcess->kill();
        delete mIfconfigProcess;
    }
    if ( mIwconfigProcess )
    {
        mIwconfigProcess->kill();
        delete mIwconfigProcess;
    }
}

BackendBase* NetToolsBackend::createInstance( TQDict<Interface>& interfaces )
{
    return new NetToolsBackend( interfaces );
}

void NetToolsBackend::update()
{
    if ( !mIfconfigProcess )
    {
        mIfconfigStdout = TQString();
        mIfconfigProcess = new TDEProcess();
        mIfconfigProcess->setEnvironment( "LANG", "C" );
        mIfconfigProcess->setEnvironment( "LC_ALL", "C" );
        *mIfconfigProcess << PATH_IFCONFIG << "-a";
        connect( mIfconfigProcess,  TQ_SIGNAL( receivedStdout( TDEProcess*, char*, int ) ),
                 this, TQ_SLOT( ifconfigProcessStdout( TDEProcess*, char*, int ) ) );
        connect( mIfconfigProcess,  TQ_SIGNAL( processExited( TDEProcess* ) ),
                 this, TQ_SLOT( ifconfigProcessExited( TDEProcess* ) ) );

        if ( !mIfconfigProcess->start( TDEProcess::NotifyOnExit, TDEProcess::Stdout ) )
        {
            delete mIfconfigProcess;
            mIfconfigProcess = 0L;
        }
    }

#ifdef PATH_IWCONFIG
    if ( !mIwconfigProcess )
    {
        mIwconfigStdout = TQString();
        mIwconfigProcess = new TDEProcess();
        mIwconfigProcess->setEnvironment( "LANG", "C" );
        mIwconfigProcess->setEnvironment( "LC_ALL", "C" );
        *mIwconfigProcess << PATH_IWCONFIG;
        connect( mIwconfigProcess,  TQ_SIGNAL( receivedStdout( TDEProcess*, char*, int ) ),
                 this, TQ_SLOT( iwconfigProcessStdout( TDEProcess*, char*, int ) ) );
        connect( mIwconfigProcess,  TQ_SIGNAL( receivedStderr( TDEProcess*, char*, int ) ),
                 this, TQ_SLOT( iwconfigProcessStdout( TDEProcess*, char*, int ) ) );
        connect( mIwconfigProcess,  TQ_SIGNAL( processExited( TDEProcess* ) ),
                 this, TQ_SLOT( iwconfigProcessExited( TDEProcess* ) ) );

        if ( !mIwconfigProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput ) )
        {
            delete mIwconfigProcess;
            mIwconfigProcess = 0L;
        }
    }
#endif

#ifdef PATH_ROUTE
    if ( !mRouteProcess )
    {
        mRouteStdout = TQString();
        mRouteProcess = new TDEProcess();
        mRouteProcess->setEnvironment( "LANG", "C" );
        mRouteProcess->setEnvironment( "LC_ALL", "C" );
        *mRouteProcess << PATH_ROUTE << "-n";
        connect( mRouteProcess,  TQ_SIGNAL( receivedStdout( TDEProcess*, char*, int ) ),
                 this, TQ_SLOT( routeProcessStdout( TDEProcess*, char*, int ) ) );
        connect( mRouteProcess,  TQ_SIGNAL( receivedStderr( TDEProcess*, char*, int ) ),
                 this, TQ_SLOT( routeProcessStdout( TDEProcess*, char*, int ) ) );
        connect( mRouteProcess,  TQ_SIGNAL( processExited( TDEProcess* ) ),
                 this, TQ_SLOT( routeProcessExited( TDEProcess* ) ) );

        if ( !mRouteProcess->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput ) )
        {
            delete mRouteProcess;
            mRouteProcess = 0L;
        }
    }
#endif
}

void NetToolsBackend::routeProcessExited( TDEProcess* process )
{
    if ( process == mRouteProcess )
    {
        mRouteProcess->deleteLater(); // we're in a slot connected to mRouteProcess
        mRouteProcess = 0L;
        parseRouteOutput();
    }
}

void NetToolsBackend::routeProcessStdout( TDEProcess*, char* buffer, int buflen )
{
    mRouteStdout += TQString::fromLatin1( buffer, buflen );
}

void NetToolsBackend::ifconfigProcessExited( TDEProcess* process )
{
    if ( process == mIfconfigProcess )
    {
        delete mIfconfigProcess;
        mIfconfigProcess = 0L;
        parseIfconfigOutput();
    }
}

void NetToolsBackend::ifconfigProcessStdout( TDEProcess*, char* buffer, int buflen )
{
    mIfconfigStdout += TQString::fromLatin1( buffer, buflen );
}

void NetToolsBackend::iwconfigProcessExited( TDEProcess* process )
{
    if ( process == mIwconfigProcess )
    {
        delete mIwconfigProcess;
        mIwconfigProcess = 0L;
        parseIwconfigOutput();
    }
}

void NetToolsBackend::iwconfigProcessStdout( TDEProcess*, char* buffer, int buflen )
{
    mIwconfigStdout += TQString::fromLatin1( buffer, buflen );
}

void NetToolsBackend::parseIfconfigOutput()
{
    /* mIfconfigStdout contains the complete output of 'ifconfig' which we
     * are going to parse here.
     */
    TQMap<TQString, TQString> configs;
    TQStringList ifList = TQStringList::split( "\n\n", mIfconfigStdout );
    TQStringList::Iterator it;
    for ( it = ifList.begin(); it != ifList.end(); ++it )
    {
        int index = ( *it ).find( ' ' );
        if ( index == -1 )
            continue;
        TQString key = ( *it ).left( index );
        configs[key] = ( *it ).mid( index );
    }

    /* We loop over the interfaces the user wishs to monitor.
     * If we find the interface in the output of 'ifconfig'
     * we update its data, otherwise we mark it as
     * 'not existing'.
     */
    TQDictIterator<Interface> ifIt( mInterfaces );
    for ( ; ifIt.current(); ++ifIt )
    {
        TQString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( configs.find( key ) == configs.end() )
        {
            // The interface does not exist. Meaning the driver
            // isn't loaded and/or the interface has not been created.
            interface->getData().existing = false;
            interface->getData().available = false;
        }
        // JJ 2005-07-18: use RUNNING instead of UP to detect whether interface is connected
        else if ( !configs[key].contains( "inet " ) ||
                  !configs[key].contains( "RUNNING" ) )
        {
            // The interface is up or has an IP assigned but not both
            interface->getData().existing = true;
            interface->getData().available = false;
        }
        else
        {
            // ...determine the type of the interface
            if ( configs[key].contains( "Ethernet" ) )
                interface->setType( Interface::ETHERNET );
            else
                interface->setType( Interface::PPP );

            // Update the interface.
            interface->getData().existing = true;
            interface->getData().available = true;
            updateInterfaceData( configs[key], interface->getData(), interface->getType() );
        }
    }
    updateComplete();
}

void NetToolsBackend::updateInterfaceData( TQString& config, InterfaceData& data, int type )
{
    TQRegExp regExp( ".*RX.*:(\\d+).*:\\d+.*:\\d+.*:\\d+" );
    if ( regExp.search( config ) > -1 )
        data.rxPackets = regExp.cap( 1 ).toULong();

    regExp.setPattern( ".*TX.*:(\\d+).*:\\d+.*:\\d+.*:\\d+" );
    if ( regExp.search( config ) > -1 )
        data.txPackets = regExp.cap( 1 ).toULong();

    regExp.setPattern( "RX bytes:(\\d+)\\s*\\(\\d+\\.\\d+\\s*\\w+\\)" );
    if ( regExp.search( config ) > -1 )
    {
        // We count the traffic on ourself to avoid an overflow after
        // 4GB of traffic.
        unsigned long currentRxBytes = regExp.cap( 1 ).toULong();
        if ( currentRxBytes < data.prevRxBytes )
        {
            // there was an overflow
            if ( type == Interface::ETHERNET )
            {
                // This makes data counting more accurate but will not work
                // for interfaces that reset the transfered data to zero
                // when deactivated like ppp does.
                data.rxBytes += 0xFFFFFFFF - data.prevRxBytes;
            }
            data.prevRxBytes = 0L;
        }
        if ( data.rxBytes == 0L )
        {
            // on startup set to currently received bytes
            data.rxBytes = currentRxBytes;
            // this is new: KNemo only counts the traffic transfered
            // while it is running. Important to not falsify statistics!
            data.prevRxBytes = currentRxBytes;
        }
        else
            // afterwards only add difference to previous number of bytes
            data.rxBytes += currentRxBytes - data.prevRxBytes;

        data.incomingBytes = currentRxBytes - data.prevRxBytes;
        data.prevRxBytes = currentRxBytes;
        data.rxString = TDEIO::convertSize( data.rxBytes );
    }

    regExp.setPattern( "TX bytes:(\\d+)\\s*\\(\\d+\\.\\d+\\s*\\w+\\)" );
    if ( regExp.search( config ) > -1 )
    {
        // We count the traffic on ourself to avoid an overflow after
        // 4GB of traffic.
        unsigned long currentTxBytes = regExp.cap( 1 ).toULong();
        if ( currentTxBytes < data.prevTxBytes )
        {
            // there was an overflow
            if ( type == Interface::ETHERNET )
            {
                // This makes data counting more accurate but will not work
                // for interfaces that reset the transfered data to zero
                // when deactivated like ppp does.
                data.txBytes += 0xFFFFFFFF - data.prevTxBytes;
            }
            data.prevTxBytes = 0L;
        }
        if ( data.txBytes == 0L )
        {
            // on startup set to currently transmitted bytes
            data.txBytes = currentTxBytes;
            // this is new: KNemo only counts the traffic transfered
            // while it is running. Important to not falsify statistics!
            data.prevTxBytes = currentTxBytes;
        }
        else
            // afterwards only add difference to previous number of bytes
            data.txBytes += currentTxBytes - data.prevTxBytes;

        data.outgoingBytes = currentTxBytes - data.prevTxBytes;
        data.prevTxBytes = currentTxBytes;
        data.txString = TDEIO::convertSize( data.txBytes );
    }

    regExp.setPattern( "inet\\s+\\w+:(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})" );
    if ( regExp.search( config ) > -1 )
        data.ipAddress = regExp.cap( 1 );

    regExp.setPattern( "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})" );
    if ( regExp.search( config ) > -1 )
    {
        data.broadcastAddress = regExp.cap( 2 );
        data.subnetMask = regExp.cap( 3 );
    }

    if ( type == Interface::ETHERNET )
    {
        regExp.setPattern( "(.{2}:.{2}:.{2}:.{2}:.{2}:.{2})" );
        if ( regExp.search( config ) > -1 )
            data.hwAddress = regExp.cap( 1 );
    }
    else if (  type == Interface::PPP )
    {
        regExp.setPattern( "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}).*(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})" );
        if ( regExp.search( config ) > -1 )
            data.ptpAddress = regExp.cap( 2 );
    }
}

void NetToolsBackend::parseIwconfigOutput()
{
    /* mIwconfigStdout contains the complete output of 'iwconfig' which we
     * are going to parse here.
     */
    TQMap<TQString, TQString> configs;
    TQStringList ifList = TQStringList::split( "\n\n", mIwconfigStdout );
    TQStringList::Iterator it;
    for ( it = ifList.begin(); it != ifList.end(); ++it )
    {
        int index = ( *it ).find( ' ' );
        if ( index == -1 )
            continue;
        TQString key = ( *it ).left( index );
        configs[key] = ( *it ).mid( index );
    }

    /* We loop over the interfaces the user wishs to monitor.
     * If we find the interface in the output of 'iwconfig'
     * we update its data.
     */
    TQDictIterator<Interface> ifIt( mInterfaces );
    for ( ; ifIt.current(); ++ifIt )
    {
        TQString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( configs.find( key ) == configs.end() )
        {
            // The interface was not found.
            continue;
        }
        else if ( configs[key].contains( "no wireless extensions" ) )
        {
            // The interface isn't a wireless device.
            interface->getData().wirelessDevice = false;
        }
        else
        {
            // Update the wireless data of the interface.
            interface->getData().wirelessDevice = true;
            updateWirelessData( configs[key], interface->getWirelessData() );
        }
    }
}

void NetToolsBackend::updateWirelessData( TQString& config, WirelessData& data )
{
    TQRegExp regExp( "ESSID:([^\"][\\S]*)" );
    if ( regExp.search( config ) > -1 )
        data.essid = regExp.cap( 1 );
    else
    {
        regExp.setPattern( "ESSID:\"([^\"]*)" );
        if ( regExp.search( config ) > -1 )
            data.essid = regExp.cap( 1 );
        else
            data.essid = TQString();
    }

    regExp.setPattern( "Mode:(\\w*)" );
    if ( regExp.search( config ) > -1 )
        data.mode = regExp.cap( 1 );

    regExp.setPattern( "Frequency:([\\w|\\.]*\\s*\\w*)" );
    if ( regExp.search( config ) > -1 )
    {
        data.frequency = regExp.cap( 1 );
        data.channel = "-";
    }
    else
    {
        data.frequency = "-";
        regExp.setPattern( "Channel:(\\d*)" );
        if ( regExp.search( config ) > -1 )
            data.channel = regExp.cap( 1 );
        else
            data.channel = "-";
    }

    regExp.setPattern( "Bit Rate[=:](\\d*\\s*[\\w/]*)" );
    if ( regExp.search( config ) > -1 )
        data.bitRate = regExp.cap( 1 );

    regExp.setPattern( "(.{2}:.{2}:.{2}:.{2}:.{2}:.{2})" );
    if ( regExp.search( config ) > -1 )
        data.accessPoint = regExp.cap( 1 );

    regExp.setPattern( "Nickname:\"(\\w*)\"" );
    if ( regExp.search( config ) > -1 )
        data.nickName = regExp.cap( 1 );

    regExp.setPattern( "Link Quality[=:]([\\d]*)" );
    if ( regExp.search( config ) > -1 )
        data.linkQuality = regExp.cap( 1 );

    regExp.setPattern( "Encryption key:" );
    if ( regExp.search( config ) > -1 )
    {
        regExp.setPattern( "Encryption key:off" );
        if ( regExp.search( config ) > -1 )
        {
            data.encryption = false;
        }
        else
        {
            data.encryption = true;
        }
    }
    else
    {
        data.encryption = false;
    }
}

void NetToolsBackend::parseRouteOutput()
{
    /* mRouteStdout contains the complete output of 'route' which we
     * are going to parse here.
     */
    TQMap<TQString, TQStringList> configs;
    TQStringList routeList = TQStringList::split( "\n", mRouteStdout );
    TQStringList::Iterator it;
    for ( it = routeList.begin(); it != routeList.end(); ++it )
    {
        TQStringList routeParameter = TQStringList::split( " ", *it );
        if ( routeParameter.count() < 8 ) // no routing entry
            continue;
        if ( routeParameter[0] != "0.0.0.0" ) // no default route
            continue;
        configs[routeParameter[7]] = routeParameter;
    }

    /* We loop over the interfaces the user wishs to monitor.
     * If we find the interface in the output of 'route' we update
     * the data of the interface.
     */
    TQDictIterator<Interface> ifIt( mInterfaces );
    for ( ; ifIt.current(); ++ifIt )
    {
        TQString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( configs.find( key ) != configs.end() )
        {
            // Update the default gateway.
            TQStringList routeParameter = configs[key];
            interface->getData().defaultGateway = routeParameter[1];
        }
        else
        {
            // Reset the default gateway.
            interface->getData().defaultGateway = TQString();
        }
    }
}
#include "nettoolsbackend.moc"
