/* This file is part of KNemo
   Copyright (C) 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 <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <tqmap.h>
#include <tqdir.h>
#include <tqregexp.h>
#include <tqstringlist.h>

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

#include "sysbackend.h"

#include "config.h"

#ifdef HAVE_LIBIW
#include <iwlib.h>
#else
#include <net/if.h>
#endif

#ifndef KILO
#define KILO	1e3
#endif

#define RTF_GATEWAY 0x0002
#define SYSPATH "/sys/class/net/"
#define PROCROUTE "/proc/net/route"

SysBackend::SysBackend( TQDict<Interface>& interfaces )
    : BackendBase( interfaces )
{
}

SysBackend::~SysBackend()
{
}

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

void SysBackend::update()
{
    TQDir dir( SYSPATH );
    TQStringList ifList = dir.entryList( TQDir::Dirs );

    TQDictIterator<Interface> ifIt( mInterfaces );
    for ( ; ifIt.current(); ++ifIt )
    {
        TQString key = ifIt.currentKey();
        Interface* interface = ifIt.current();

        if ( ifList.find( key ) == ifList.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;
        }
        else
        {
            if ( TQFile::exists( SYSPATH + key + "/wireless" ) )
            {
                interface->getData().wirelessDevice = true;
            }

            unsigned int carrier = 0;
            if ( !readNumberFromFile( SYSPATH + key + "/carrier", carrier ) ||
                 carrier == 0 )
            {
                // The interface is there but not useable.
                interface->getData().existing = true;
                interface->getData().available = false;
            }
            else
            {
                // ...determine the type of the interface
                unsigned int type = 0;
                if ( readNumberFromFile( SYSPATH + key + "/type", type ) &&
                     type == 512 )
                {
                    interface->setType( Interface::PPP );
                }
                else
                {
                    interface->setType( Interface::ETHERNET );
                }

                // Update the interface.
                interface->getData().existing = true;
                interface->getData().available = true;
                updateInterfaceData( key, interface->getData(), interface->getType() );

                if ( interface->getData().wirelessDevice == true )
                {
                    updateWirelessData( key, interface->getWirelessData() );
                }
            }
        }
    }
    updateComplete();
}

bool SysBackend::readNumberFromFile( const TQString& fileName, unsigned int& value )
{
    FILE* file = fopen( fileName.latin1(), "r" );
    if ( file != NULL )
    {
        if ( fscanf( file, "%ul", &value ) > 0 )
        {
            fclose( file );
            return true;
        }
        fclose( file );
    }

    return false;
}

bool SysBackend::readStringFromFile( const TQString& fileName, TQString& string )
{
    char buffer[64];
    FILE* file = fopen( fileName.latin1(), "r" );
    if ( file != NULL )
    {
        if ( fscanf( file, "%s", buffer ) > 0 )
        {
            fclose( file );
            string = buffer;
            return true;
        }
        fclose( file );
    }

    return false;
}

void SysBackend::updateInterfaceData( const TQString& ifName, InterfaceData& data, int type )
{
    TQString ifFolder = SYSPATH + ifName + "/";

    unsigned int rxPackets = 0;
    if ( readNumberFromFile( ifFolder + "statistics/rx_packets", rxPackets ) )
    {
        data.rxPackets = rxPackets;
    }

    unsigned int txPackets = 0;
    if ( readNumberFromFile( ifFolder + "statistics/tx_packets", txPackets ) )
    {
        data.txPackets = txPackets;
    }

    unsigned int rxBytes = 0;
    if ( readNumberFromFile( ifFolder + "statistics/rx_bytes", rxBytes ) )
    {
        // We count the traffic on ourself to avoid an overflow after
        // 4GB of traffic.
        if ( rxBytes < 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 = rxBytes;
            // this is new: KNemo only counts the traffic transfered
            // while it is running. Important to not falsify statistics!
            data.prevRxBytes = rxBytes;
        }
        else
            // afterwards only add difference to previous number of bytes
            data.rxBytes += rxBytes - data.prevRxBytes;

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

    unsigned int txBytes = 0;
    if ( readNumberFromFile( ifFolder + "statistics/tx_bytes", txBytes ) )
    {
        // We count the traffic on ourself to avoid an overflow after
        // 4GB of traffic.
        if ( txBytes < 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 received bytes
            data.txBytes = txBytes;
            // this is new: KNemo only counts the traffic transfered
            // while it is running. Important to not falsify statistics!
            data.prevTxBytes = txBytes;
        }
        else
            // afterwards only add difference to previous number of bytes
            data.txBytes += txBytes - data.prevTxBytes;

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

    if ( type == Interface::ETHERNET )
    {
        TQString hwAddress;
        if ( readStringFromFile( ifFolder + "address", hwAddress ) )
        {
            data.hwAddress = hwAddress;
        }

        // for the default gateway we use the proc filesystem
        TQFile routeFile( PROCROUTE );
        if ( routeFile.open( IO_ReadOnly ) )
        {
            TQString routeData( routeFile.readAll().data() );
            TQStringList routeEntries = TQStringList::split( "\n", routeData );
            TQStringList::Iterator it;
            for ( it = routeEntries.begin(); it != routeEntries.end(); ++it )
            {
                TQRegExp regExp( ".*\\s+[\\w\\d]{8}\\s+([\\w\\d]{8})\\s+(\\d{4})" );
                if (   ( regExp.search( *it ) > -1 )
                    && ( regExp.cap( 2 ).toUInt() & RTF_GATEWAY ) )
                {
                    bool ok;
                    struct in_addr in;
                    in.s_addr = regExp.cap( 1 ).toULong( &ok, 16 );
                    data.defaultGateway = inet_ntoa( in );
                    break;
                }
            }
            routeFile.close();
        }

    }

    // use ioctls for the rest
    int fd;
    struct ifreq ifr;
    if ( ( fd = socket(AF_INET, SOCK_DGRAM, 0) ) > -1 )
    {
        strcpy( ifr.ifr_name, ifName.latin1() );
        ifr.ifr_addr.sa_family = AF_INET;
        if ( ioctl( fd, SIOCGIFADDR, &ifr ) > -1 )
        {
            data.ipAddress = inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr);
        }
        if ( ioctl( fd, SIOCGIFDSTADDR, &ifr) > -1 )
        {
            data.ptpAddress = inet_ntoa(((struct sockaddr_in*)&ifr.ifr_dstaddr)->sin_addr);
        }

        if ( ioctl( fd, SIOCGIFBRDADDR, &ifr ) > -1 )
        {
            data.broadcastAddress = inet_ntoa(((struct sockaddr_in*)&ifr.ifr_broadaddr)->sin_addr);
        }

        if ( ioctl( fd, SIOCGIFNETMASK, &ifr ) > -1 )
        {
            data.subnetMask = inet_ntoa(((struct sockaddr_in*)&ifr.ifr_netmask)->sin_addr);
        }
        close( fd );
    }
}

void SysBackend::updateWirelessData( const TQString& ifName, WirelessData& data )
{
    TQString wirelessFolder = SYSPATH + ifName + "/wireless/";

    unsigned int link = 0;
    if ( readNumberFromFile( wirelessFolder + "link", link ) )
    {
        data.linkQuality = TQString::number( link );
    }

#ifdef HAVE_LIBIW
    // The following code was taken from iwconfig.c and iwlib.c.
    int fd;
    if ( ( fd = iw_sockets_open() ) > 0 )
    {
        struct iwreq wrq;
        char buffer[128];
        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWFREQ, &wrq ) >= 0 )
        {
            int channel = -1;
            double freq = iw_freq2float( &( wrq.u.freq ) );
            struct iw_range range;
            if( iw_get_range_info( fd, ifName.latin1(), &range ) >= 0 )
            {
                if ( freq < KILO )
                {
                    channel = iw_channel_to_freq( (int) freq, &freq, &range );
                }
                else
                {
                    channel = iw_freq_to_channel( freq, &range );
                }
                iw_print_freq_value( buffer, sizeof( buffer ), freq );
                data.frequency = buffer;
                data.channel = TQString::number( channel );
            }
        }

        char essid[IW_ESSID_MAX_SIZE + 1];
        memset( essid, 0, IW_ESSID_MAX_SIZE + 1 );
        wrq.u.essid.pointer = (caddr_t) essid;
        wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
        wrq.u.essid.flags = 0;
        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWESSID, &wrq ) >= 0 )
        {
            if ( wrq.u.data.flags > 0 )
            {
                data.essid = essid;
            }
            else
            {
                data.essid = "any";
            }
        }

        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWAP, &wrq ) >= 0 )
        {
            char ap_addr[128];
            iw_ether_ntop( (const ether_addr*) wrq.u.ap_addr.sa_data, ap_addr);
            data.accessPoint = ap_addr;
        }

        memset( essid, 0, IW_ESSID_MAX_SIZE + 1 );
        wrq.u.essid.pointer = (caddr_t) essid;
        wrq.u.essid.length = IW_ESSID_MAX_SIZE + 1;
        wrq.u.essid.flags = 0;
        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWNICKN, &wrq ) >= 0 )
        {
            if ( wrq.u.data.length > 1 )
            {
                data.nickName = essid;
            }
            else
            {
                data.nickName = TQString();
            }
        }

        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWRATE, &wrq ) >= 0 )
        {
            iwparam bitrate;
            memcpy (&(bitrate), &(wrq.u.bitrate), sizeof (iwparam));
            iw_print_bitrate( buffer, sizeof( buffer ), wrq.u.bitrate.value );
            data.bitRate = buffer;
        }

        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWMODE, &wrq ) >= 0 )
        {
            int mode = wrq.u.mode;
            if ( mode < IW_NUM_OPER_MODE && mode >= 0 )
            {
                data.mode = iw_operation_mode[mode];
            }
            else
            {
                data.mode = TQString();
            }
        }

        unsigned char key[IW_ENCODING_TOKEN_MAX];
        wrq.u.data.pointer = (caddr_t) key;
        wrq.u.data.length = IW_ENCODING_TOKEN_MAX;
        wrq.u.data.flags = 0;
        if ( iw_get_ext( fd, ifName.latin1(), SIOCGIWENCODE, &wrq ) >= 0 )
        {
            if ( ( wrq.u.data.flags & IW_ENCODE_DISABLED ) || ( wrq.u.data.length == 0 ) )
            {
                data.encryption = false;
            }
            else
            {
                data.encryption = true;
            }
        }
        else
        {
            data.encryption = false;
        }
        close( fd );
    }
#endif
}