/***************************************************************************
 *   Copyright (C) 2004-2005 by Hugo Parente Lima                               *
 *   hugo_pl@users.sourceforge.net                                         *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program 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 General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/
#include "knetstatsview.h"
#include "knetstats.h"

// KDE headers
#include <tdeapplication.h>
#include <kiconloader.h>
#include <tdepopupmenu.h>
#include <tdemessagebox.h>
#include <tdeaboutapplication.h>
#include <tdeconfig.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>

#include <tdeaction.h>
#include <tdestdaccel.h>
#include <kstdaction.h>
#include <khelpmenu.h>
#include <kpassivepopup.h>

// TQt headers
#include <tqtimer.h>
#include <tqfile.h>
#include <tqtooltip.h>
#include <tqcursor.h>
#include <tqevent.h>
#include <tqfont.h>
#include <tqfontmetrics.h>
#include <tqpainter.h>

// C headers
#include <cstring>
#include <cstdio>

// Linux headers
extern "C" {
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
}

#include "configure.h"
#include "statistics.h"

extern const char* programName;

KNetStatsView::KNetStatsView(KNetStats* parent, const TQString& interface)
: KSystemTray(parent, 0), mSysDevPath("/sys/class/net/"+interface+"/"), mInterface(interface) {
	mFdSock = 0;
	mFirstUpdate = true;
	mMaxSpeedAge = 0;
	mMaxSpeed = 0.0;
	mBRx = mBTx = mPRx = mPTx = 0;
	mConnected = mCarrier = true;
	mTotalBytesRx = mTotalBytesTx = mTotalPktRx = mTotalPktTx = 0;
	mSpeedBufferPtr = mSpeedHistoryPtr = 0;
	mStatistics = 0;

	readOptions(interface, &mOptions, true);
	resetBuffers();
	openFdSocket();
	memset(&mDevInfo, 0, sizeof(mDevInfo));
	strcpy(mDevInfo.ifr_name, mInterface.latin1());

	setTextFormat(TQt::PlainText);
	mContextMenu = parent->contextMenu();
	show();

	// Timer
	mTimer = new TQTimer(this, "timer");
	connect(mTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT(updateStats(void)));

	TQToolTip::add(this, i18n("Monitoring %1").arg(mInterface));
	setup();
	mStatistics = new Statistics(this);
}

KNetStatsView::~KNetStatsView() {
	if (mFdSock != -1)
		shutdown(mFdSock, SHUT_RDWR);

}

void KNetStatsView::setup() {
	if (mOptions.mViewMode == Text)
		setFont(mOptions.mTxtFont);
	else if (mOptions.mViewMode == Icon) {
		// Load Icons
		TDEIconLoader* loader = kapp->iconLoader();
		mIconError = loader->loadIcon("theme"+TQString::number(mOptions.mTheme)+"_error.png",
							TDEIcon::Panel, ICONSIZE);
		mIconNone = loader->loadIcon("theme"+TQString::number(mOptions.mTheme)+"_none.png",
							TDEIcon::Panel, ICONSIZE);
		mIconTx = loader->loadIcon("theme"+TQString::number(mOptions.mTheme)+"_tx.png",
						TDEIcon::Panel, ICONSIZE);
		mIconRx =loader->loadIcon("theme"+TQString::number(mOptions.mTheme)+"_rx.png",
						TDEIcon::Panel, ICONSIZE);
		mIconBoth = loader->loadIcon("theme"+TQString::number(mOptions.mTheme)+"_both.png",
							TDEIcon::Panel, ICONSIZE);
		mCurrentIcon = &mIconNone;
	}
	mTimer->start(mOptions.mUpdateInterval);
	updateStats();
	TQWidget::update();
	mFirstUpdate = false;
}

void KNetStatsView::say(const TQString& message) {
		KPassivePopup::message(programName, message, kapp->miniIcon(), this);
}

void KNetStatsView::updateViewOptions() {
	readOptions(mInterface, &mOptions);
	setup();
}

void KNetStatsView::updateStats() {
	FILE* flags_fp = fopen((mSysDevPath+"flags").latin1(), "r");
	bool currentStatus;
	if (!flags_fp)
		currentStatus = false;
	else {
		int flags;
		fscanf(flags_fp, "%Xu", &flags);
		fclose(flags_fp);
		currentStatus = IFF_UP & flags;
	}

	if (!currentStatus && mConnected) { // interface down...
		mConnected = false;
		resetBuffers();
		TQWidget::update();
		say(i18n("%1 is inactive").arg(mInterface));
	} else if (currentStatus && !mConnected) {
		mConnected = true;
		say(i18n("%1 is active").arg(mInterface));
	}

	// kernel < 2.6.9 (I think) does not have carrier info, considering carrier ever on.
	FILE* carrier_fp = fopen((mSysDevPath+"carrier").latin1(), "r");
	char carrierFlag = '1';
	if (carrier_fp) {
		carrierFlag = fgetc(carrier_fp);
		fclose(carrier_fp);
	}

	if (!mConnected)
		return;
	if (carrierFlag == '0') { // carrier down
		if (mCarrier) {
			mCarrier = false;
			TQWidget::update();
			say(i18n("%1 is disconnected").arg(mInterface));
		}
		return;
	} else if (!mCarrier) { // carrier up
		mCarrier = true;
		say(i18n("%1 is connected").arg(mInterface));
	}

	unsigned int brx = readInterfaceNumValue("rx_bytes");
	unsigned int btx = readInterfaceNumValue("tx_bytes");
	unsigned int prx = readInterfaceNumValue("rx_packets");
	unsigned int ptx = readInterfaceNumValue("tx_packets");


	if (!mFirstUpdate) { // a primeira velocidade sempre eh absurda, para evitar isso temos o mFirstUpdate
		if (++mSpeedBufferPtr >= SPEED_BUFFER_SIZE)
			mSpeedBufferPtr = 0;

		// Calcula as velocidades
		mSpeedBufferTx[mSpeedBufferPtr] = ((btx - mBTx)*(1000.0f/mOptions.mUpdateInterval));
		mSpeedBufferRx[mSpeedBufferPtr] = ((brx - mBRx)*(1000.0f/mOptions.mUpdateInterval));
		mSpeedBufferPTx[mSpeedBufferPtr] = ((ptx - mPTx)*(1000.0f/mOptions.mUpdateInterval));
		mSpeedBufferPRx[mSpeedBufferPtr] = ((prx - mPRx)*(1000.0f/mOptions.mUpdateInterval));

		if (++mSpeedHistoryPtr >= HISTORY_SIZE)
			mSpeedHistoryPtr = 0;
		mSpeedHistoryRx[mSpeedHistoryPtr] = calcSpeed(mSpeedBufferRx);
		mSpeedHistoryTx[mSpeedHistoryPtr] = calcSpeed(mSpeedBufferTx);

		mMaxSpeedAge--;

		if (mSpeedHistoryTx[mSpeedHistoryPtr] > mMaxSpeed) {
			mMaxSpeed = mSpeedHistoryTx[mSpeedHistoryPtr];
			mMaxSpeedAge = HISTORY_SIZE;
		}
		if (mSpeedHistoryRx[mSpeedHistoryPtr] > mMaxSpeed)  {
			mMaxSpeed = mSpeedHistoryRx[mSpeedHistoryPtr];
			mMaxSpeedAge = HISTORY_SIZE;
		}
		if (mMaxSpeedAge < 1)
			calcMaxSpeed();
	}

	if (mOptions.mViewMode == Icon) {
		TQPixmap* newIcon;
		if (brx == mBRx) {
			if (btx == mBTx )
				newIcon = &mIconNone;
			else
				newIcon = &mIconTx;
		} else {
			if (btx == mBTx )
				newIcon = &mIconRx;
			else
				newIcon = &mIconBoth;
		}

		if (newIcon != mCurrentIcon) {
			mCurrentIcon = newIcon;
			TQWidget::update();
		}
	} else if (mOptions.mViewMode == Graphic || (btx != mBTx && brx != mBRx))
		TQWidget::update();

	// Update stats
	mTotalBytesRx += brx - mBRx;
	mTotalBytesTx += btx - mBTx;
	mTotalPktRx += prx - mPRx;
	mTotalPktTx += ptx - mPTx;

	mBRx = brx;
	mBTx = btx;
	mPRx = prx;
	mPTx = ptx;
}

unsigned long KNetStatsView::readInterfaceNumValue(const char* name) {
	// stdio functions appear to be more fast than TQFile?
	FILE* fp = fopen((mSysDevPath+"statistics/"+name).latin1(), "r");
	long retval;
	fscanf(fp, "%lu", &retval);
	fclose(fp);
	return retval;
}

TQString KNetStatsView::readInterfaceStringValue(const char* name, int maxlength) {
	TQFile macFile(mSysDevPath+name);
	macFile.open(IO_ReadOnly);
	TQString value;
	macFile.readLine(value, maxlength);
	return value;
}

void KNetStatsView::resetBuffers() {
	memset(mSpeedHistoryRx, 0, sizeof(double)*HISTORY_SIZE);
	memset(mSpeedHistoryTx, 0, sizeof(double)*HISTORY_SIZE);
	memset(mSpeedBufferRx, 0, sizeof(double)*SPEED_BUFFER_SIZE);
	memset(mSpeedBufferTx, 0, sizeof(double)*SPEED_BUFFER_SIZE);
	memset(mSpeedBufferPRx, 0, sizeof(double)*SPEED_BUFFER_SIZE);
	memset(mSpeedBufferPTx, 0, sizeof(double)*SPEED_BUFFER_SIZE);
}

void KNetStatsView::paintEvent( TQPaintEvent* ) {
	TQPainter paint(this);
	switch(mOptions.mViewMode) {
		case Icon:
			if (!mCarrier || !mConnected)
				mCurrentIcon = &mIconError;
			paint.drawPixmap(0, 0, *mCurrentIcon);
			break;
		case Text:
			drawText(paint);
			break;
		case Graphic:
			drawGraphic(paint);
			break;
	}
}

void KNetStatsView::drawText(TQPainter& paint) {
	if (!mCarrier || !mConnected) {
		paint.drawText(rect(), TQt::AlignCenter, "?");
	} else {
		paint.setFont( mOptions.mTxtFont );
		paint.setPen( mOptions.mTxtUplColor );
		paint.drawText( rect(), TQt::AlignTop, Statistics::byteFormat(byteSpeedTx(), "KB", "MB"));
		paint.setPen( mOptions.mTxtDldColor );
		paint.drawText( rect(), TQt::AlignBottom, Statistics::byteFormat(byteSpeedRx(), "KB", "MB"));
	}
}

void KNetStatsView::drawGraphic(TQPainter& paint) {
	if (!mCarrier || !mConnected) {
		paint.drawText(rect(), TQt::AlignCenter, "X");
		return;
	}

	TQSize size = this->size();

	if (!mOptions.mChartTransparentBackground)
		paint.fillRect(0, 0, size.width(), size.height(), mOptions.mChartBgColor);

	const int HEIGHT = size.height()-1;

	//	tqDebug("MaxSpeed: %d, age: %d", int(mMaxSpeed), mMaxSpeedAge);
	int lastX;
	int lastRxY = HEIGHT - int(HEIGHT * (mSpeedHistoryRx[mSpeedHistoryPtr]/mMaxSpeed));
	int lastTxY = HEIGHT - int(HEIGHT * (mSpeedHistoryTx[mSpeedHistoryPtr]/mMaxSpeed));
	int x = lastX = size.width();
	int count = 0;
	for (int i = mSpeedHistoryPtr; count < width(); i--) {
		if (i < 0)
			i = HISTORY_SIZE-1;

		int rxY = HEIGHT - int(HEIGHT * (mSpeedHistoryRx[i]/mMaxSpeed));
		int txY = HEIGHT - int(HEIGHT * (mSpeedHistoryTx[i]/mMaxSpeed));
		paint.setPen(mOptions.mChartDldColor);
		paint.drawLine(lastX, lastRxY, x, rxY);
		paint.setPen(mOptions.mChartUplColor);
		paint.drawLine(lastX, lastTxY, x, txY);
		//tqDebug("%d => %d", i, int(mSpeedHistoryRx[i]));
		lastX = x;
		lastRxY = rxY;
		lastTxY = txY;

		count++;
		x = width()-int(count+1);
	}
}

void KNetStatsView::mousePressEvent(TQMouseEvent* ev) {
	if (ev->button() == TQt::RightButton )
		mContextMenu->exec(TQCursor::pos());
	else if (ev->button() == TQt::LeftButton)
		if (mStatistics->isShown())
			mStatistics->accept();
		else
			mStatistics->show();
}

bool KNetStatsView::openFdSocket() {
	if (mFdSock > 0)
		return true;
	if ((mFdSock = socket(AF_INET, SOCK_DGRAM, 0)) > 0)
		return true;
	return false;
}

TQString KNetStatsView::getIp() {
	if (mFdSock == -1 && !openFdSocket())
		return "";

	ioctl(mFdSock, SIOCGIFADDR, &mDevInfo);
	sockaddr_in sin = ((sockaddr_in&)mDevInfo.ifr_addr);
	return inet_ntoa(sin.sin_addr);
}

TQString KNetStatsView::getNetmask() {
	if (mFdSock == -1 && !openFdSocket())
		return "";
	ioctl(mFdSock, SIOCGIFNETMASK, &mDevInfo);
	sockaddr_in mask = ((sockaddr_in&)mDevInfo.ifr_netmask);
	return inet_ntoa(mask.sin_addr);
}

void KNetStatsView::readOptions( const TQString& name, KNetStatsView::Options* opts, bool defaultVisibility ) {
	TDEConfig* cfg = kapp->config();
	TDEConfigGroupSaver groupSaver(cfg, name);

	// general
	opts->mUpdateInterval = cfg->readNumEntry("UpdateInterval", 300);
	opts->mViewMode = (Mode)cfg->readNumEntry("ViewMode", 0);
	opts->mMonitoring = cfg->readBoolEntry("Monitoring", defaultVisibility);
	// txt view
	opts->mTxtFont = cfg->readFontEntry("TxtFont");
	opts->mTxtUplColor = cfg->readColorEntry("TxtUplColor", &TQt::red);
	opts->mTxtDldColor = cfg->readColorEntry("TxtDldColor", &TQt::green);
	// IconView
	int defaultTheme = 0;
	if (name.startsWith("wlan"))
		defaultTheme = 3; // three... is a magic number... a magic number....
	opts->mTheme = cfg->readNumEntry("Theme", defaultTheme);
	// Graphic
	opts->mChartUplColor = cfg->readColorEntry("ChartUplColor", &TQt::red);
	opts->mChartDldColor = cfg->readColorEntry("ChartDldColor", &TQt::blue);
	opts->mChartBgColor = cfg->readColorEntry("ChartBgColor", &TQt::white);
	opts->mChartTransparentBackground = cfg->readBoolEntry("ChartUseTransparentBackground", true);
}


#include "knetstatsview.moc"
