/*
    knetd, KDE Network Daemon

    Copyright (c) 2005 by Engin AYDOGAN		<engin @ bzzzt.biz>

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

#include "device.h"
#include "processwrapper.h"

#include <qtimer.h>
#include <kinstance.h>
#include <kconfig.h>
#include <kstandarddirs.h>
#include <kprocess.h>
#include <kdebug.h>

class Device::Private
{
public:
	AttributeList devAttrList;
	AttributeList wirelessAttrList;
	QString name;
	QString path;
	QString wpath;
	int linkScanFreq;
	int apScanFreq;
	int ifScanFreq;
	QTimer signalScan;
	ProcessWrapper ethtool, iwconfig, ifconfig;
	bool link;
	bool ifUP;
	Device::Speed speed;
	Device::Duplex duplex;
	QString ip4;
	QString ip6;
};

Device*
Device::build( const QString& name, bool read )
{
	sysfs_class* cl = 0L;
	sysfs_class_device* dev = 0L;
	sysfs_directory* dir = 0L;
	char* cname = 0L;
	QString path, wPath;
	if( !( cl = sysfs_open_class( "net" ) ) )
	{
		kdError() << k_funcinfo << "Could not open net class." << endl;
		return 0L;
	}
	cname = strdup( name.ascii() );

	if( !( dev = sysfs_get_class_device( cl, cname ) ) )
	{
		kdError() << k_funcinfo << "Could not open device [ " << cname << " ]" << endl;
		sysfs_close_class( cl );
		free( cname );
		return 0L;
	}

	if( !( dir = sysfs_open_directory( dev->path ) ) )
	{
		kdError() << k_funcinfo << "Could not open device directory [ " << dev->path << " ]" << endl;
		sysfs_close_class( cl );
		free( cname );
		return 0L;
	}
	path = dev->path;

	if( sysfs_read_directory( dir ) )
	{
		kdError() << k_funcinfo << "Could not read directory [ " <<  dir->path << "]" << endl;
		sysfs_close_class( cl );
		sysfs_close_directory( dir );
		free( cname );
		return 0L;
	}
	else
	{
		sysfs_directory* t;
		dlist_for_each_data( dir->subdirs, t, sysfs_directory )
		{
			if(!strcmp(t->name, "wireless" ) )
			{
				wPath = t->path;
			}
		}
	}
	sysfs_close_class( cl );
	sysfs_close_directory( dir );
	free( cname );
	return new Device( name, path, wPath, read );
}

Device::Device( const QString& name, const QString& path, const QString& wPath, bool r )
 :  AbstractDevice( name ), d( new Private )
{
	d->name = name;
	d->path = path;
	d->wpath = wPath;
	d->link = false;
	if( r ) read();
	connect( &d->signalScan, SIGNAL( timeout() ), this, SLOT( checkSignal() ) );

	QString knetdsuid = KStandardDirs::findExe( "knetdsuid" );

	// ethtool parser
	d->ethtool << knetdsuid << "ethtool" << name;
	connect( &d->ethtool, SIGNAL( stdoutReady( const QString& ) ),
		this, SLOT( ethtoolStdout( const QString& ) ) );
	connect( &d->ethtool, SIGNAL( stderrReady( const QString& ) ),
		this, SLOT( ethtoolStderr( const QString& ) ) );
	connect( &d->ethtool, SIGNAL( processExited( KProcess* ) ),
		this, SLOT( ethtoolExited( KProcess* ) ) );

	// ifconfig parser
	d->ifconfig << knetdsuid << "ifconfig" << name;
	connect( &d->ifconfig, SIGNAL( stdoutReady( const QString& ) ),
		this, SLOT( ifconfigStdout( const QString& ) ) );
	connect( &d->ifconfig, SIGNAL( stderrReady( const QString& ) ),
		this, SLOT( ifconfigStderr( const QString& ) ) );
	connect( &d->ifconfig, SIGNAL( processExited( KProcess* ) ),
		this, SLOT( ifconfigExited( KProcess* ) ) );

	// iwconfig parser
	d->iwconfig << knetdsuid << name;
	connect( &d->iwconfig, SIGNAL( stdoutReady( const QString& ) ),
		this, SLOT( iwconfigStdout( const QString& ) ) );
	connect( &d->iwconfig, SIGNAL( stderrReady( const QString& ) ),
		this, SLOT( iwconfigStderr( const QString& ) ) );
	connect( &d->iwconfig, SIGNAL( processExited( KProcess* ) ),
		this, SLOT( iwconfigExited( KProcess* ) ) );

	// read fork frequency
	KConfig *c = KGlobal::config();
	c->setGroup( "Daemon" );
	d->linkScanFreq = c->readNumEntry( "linkScanFreq", 500 );
	d->apScanFreq = c->readNumEntry( "apScanFreq", 60000 );
	d->ifScanFreq = c->readNumEntry( "ifScanFreq", 1000 );
	d->signalScan.start( c->readNumEntry( "signalScanFreq", 1000 ) );
	checkLink();
	checkAccessPoints();
	checkUpDown();
}

Device::~Device()
{
	kdWarning() << k_funcinfo << "~Device[ " << name() << " ]" << endl;
	delete d;
}

bool
Device::isAvailable() const
{
	return true;
}

void
Device::read()
{
	sysfs_attribute* attr = 0L, *sattr;
	sysfs_directory* dir = 0L, *adir = 0L;
	if( !( dir = sysfs_open_directory( d->path.ascii() ) ) )
	{
		kdError() << k_funcinfo << "Could not open directory." << endl;
		return;
	}

	if( !sysfs_read_directory( dir ) )
	{
		d->devAttrList.clear();
		dlist_for_each_data( dir->attributes, attr, sysfs_attribute )
		{
 			d->devAttrList[attr->name] = Attribute( attr );
		}

		if( isWireless() )
		{
			d->wirelessAttrList.clear();
			if( ( adir = sysfs_open_directory( d->wpath.ascii() ) ) )
			{
				if( !sysfs_read_directory( adir ) )
				{
					dlist_for_each_data( adir->attributes, sattr, sysfs_attribute )
					{
						d->wirelessAttrList[ sattr->name ] = Attribute( sattr );
					}
				}
				else
				{
					kdError() << k_funcinfo << "Could not read wireless directory." << endl;
				}
			}
			else
			{
				kdError() << k_funcinfo << "Could not open wireless directory." << endl;
			}
		}
	}
	else
	{
		kdError() << k_funcinfo << "Could not read directory information." << endl;
		return;
	}
}

const QString&
Device::name() const
{
	return d->name;
}

const QString&
Device::mac() const
{
	return "fixme";
}

const QString&
Device::netmask() const
{
	return "fixme";
}

const QString&
Device::gateway() const
{
	return "fixme";
}

bool
Device::isEthernet() const
{
	//! FIXME
	return true;
}

bool
Device::isWireless() const
{
	return !d->wpath.isNull();
}

void
Device::setSignalScanFreq( int f )
{
	d->signalScan.changeInterval( f );
}

void
Device::setLinkScanFreq( int f )
{
	d->linkScanFreq = f;
}

int
Device::linkScanFreq() const
{
	return d->linkScanFreq;
}

void
Device::setApScanFreq( int f )
{
	d->apScanFreq = f;
}

int
Device::apScanFreq() const
{
	return d->apScanFreq;
}

void
Device::checkLink()
{
	d->ethtool.start();
}

void
Device::checkAccessPoints()
{
	d->iwconfig.start();
}

void
Device::checkSignal()
{
/*
	int level = d->wirelessAttrList["level"].value().toInt();
	d->wirelessAttrList["level"].reRead();
	int newLevel = d->wirelessAttrList["level"].value().toInt();
	if( level != newLevel ) emit signalChanged( this, newLevel );
*/
}

void
Device::ethtoolStdout( const QString& output )
{
	QStringList lines = QStringList::split( "\n", output );

	// Parse speed from "Speed: 100Mb/s"
	QString speed = lines.grep( "Speed: " )[0].section( " ", 1, 1 );

	speed = speed.mid( 0, speed.length() - 4 );
	switch( speed.toInt() )
	{
	case 10:
		d->speed = Speed10;
		break;
	case 100:
		d->speed = Speed100;
		break;
	case 1000:
		d->speed = Speed1000;
		break;
	default:
		d->speed = SpeedUnknown;
	}

	// Parse dublexity from "Dublex: Full"
	QString duplex = lines.grep( "Duplex: " )[0].section( " ", 1, 1 );
	if( duplex == "Full" )
	{
		d->duplex = Full;
	}
	else if( duplex == "Half" )
	{
		d->duplex = Half;
	}
	else
	{
		d->duplex = DuplexUnknown;
	}

	// Parse link status from "Link detected: yes"
	QString link = lines.grep( "Link detected: " )[0].section( " ", 2, 2 );
	bool nlink;
	if( link == "yes" )
	{
		nlink = true;
	}
	else if( link ==  "no" )
	{
		nlink = false;
	}
	else
	{
		nlink = false;
	}
	if( d->link != nlink )
	{
		d->link = nlink;
		if( d->link )
		{
			emit linkUp( this );
		}
		else
		{
			emit linkDown( this );
		}
	}
}

void
Device::ethtoolStderr( const QString& error )
{
	emit ethtoolError( this, error );
}

void
Device::ethtoolExited( KProcess* )
{
	QTimer::singleShot( d->linkScanFreq, this, SLOT( checkLink() ) );
}

Device::Speed
Device::speed() const
{
	return d->speed;
}

Device::Duplex
Device::duplex() const
{
	return d->duplex;
}

bool
Device::isLinkUp() const
{
	return d->link;
}

void
Device::checkUpDown()
{
	d->ifconfig.start();
}

void
Device::ifconfigStdout( const QString& output )
{
	QStringList lines = QStringList::split( "\n", output );

	for( QStringList::iterator it = lines.begin(); it != lines.end(); ++it )
	{
		if( (*it).startsWith( "inet addr:" ) )
		{
			kdWarning() << k_funcinfo << "fo" << endl;
			QString ip = (*it).mid(9,3);
		}
	}

	bool up = ( lines.grep( "UP ").count() == 1  );
	if( d->ifUP != up )
	{
		d->ifUP = up;
		if( d->ifUP )
		{
			emit interfaceUp( this );
		}
		else
		{
			emit interfaceDown( this );
		}
	}
	if( isUp() )
	{
		QString ip4 = lines.grep( "inet addr:" )[0].section( " ", 11, 11 ).mid(5);
		QString ip6 = lines.grep( "inet6 addr:" )[0].section( " ", 12, 12 );
		if( ip4 != d->ip4 )
		{
			d->ip4 = ip4;
			emit ipChanged( this );
		}

		if( ip6 != d->ip6 )
		{
			d->ip6 = ip6;
			emit ipChanged( this );
		}
	}
}

bool
Device::isUp() const
{
	return d->ifUP;
}

const QString&
Device::ip4() const
{
	return d->ip4;
}

const QString&
Device::ip6() const
{
	return d->ip6;
}

void
Device::ifconfigStderr( const QString& error )
{
	emit ifconfigError( this, error );
}

void
Device::ifconfigExited( KProcess* )
{
	QTimer::singleShot( d->ifScanFreq, this, SLOT( checkUpDown() ) );
}

void
Device::iwconfigStdout( const QString& output )
{
}

void
Device::iwconfigStderr( const QString& error )
{
	emit iwconfigError( this, error );
}

void
Device::iwconfigExited( KProcess* )
{
	QTimer::singleShot( d->apScanFreq, this, SLOT( checkAccessPoints() ) );
}

#include "device.moc"
