/***********************************************************************************
* System Monitor: Plasmoid and data engines to monitor CPU/Memory/Swap Usage.
* Copyright (C) 2008  Matthew Dawson
*
* 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 "cpu_load.h"
#include <QList>
#include <QVector>
#include <QFile>
#include <QTextStream>

struct cpu_jiffies{

	unsigned long user;
	unsigned long sys;
	unsigned long nice;
	unsigned long disk;
	unsigned long idle;

	///Simply 0 inits all values.
	cpu_jiffies():user(0),sys(0),nice(0),disk(0),idle(0){}

};

CPUMonitor::CPUMonitor(QObject* parent, const QVariantList& args):
		Plasma::DataEngine(parent),
		average(new cpu_jiffies()),
		procstat(new QFile("/proc/stat")),
		m_numcpus(1){

	Q_UNUSED(args)

	setMinimumPollingInterval(500);

}

CPUMonitor::~CPUMonitor(){

	cpuTimeVector.clear();
	delete average;

}

void CPUMonitor::init(){

	setData("Average CPU Usage", DataEngine::Data());
	setData("Number of CPUs", m_numcpus);

	cpuTimeVector.push_back(cpu_jiffies());

}

bool CPUMonitor::sourceRequestEvent(const QString &proc){

//	setData(proc, DataEngine::Data());

	/// @todo:When solid gets proc support, use that
	bool ok;

	int procNum = proc.toInt(&ok);

	kDebug() << ok << " " << proc;

	if(!ok){

			if(proc == "Average CPU Usage"){
/*				setData(proc, "User", 0.0);
				setData(proc, "Sys", 0.0);
				setData(proc, "Nice", 0.0);
				setData(proc, "Disk", 0.0);
				setData(proc, "Idle", 0.0);*/
				setData(proc, "iIdle", 0);
				setData(proc, "iSys",  0);
				setData(proc, "iNice", 0);
				setData(proc, "iDisk", 0);
				setData(proc, "iLoad", 0);
				return true;
			}else if(proc == "Number of CPUs"){
				setData(proc, m_numcpus);
				return true;
			}else {
				return false;
			}

	}else if(procNum < m_numcpus){

		if(cpuTimeVector.count() < procNum){cpuTimeVector.resize(procNum + 1);}
		
		/*setData(proc, "User", 0.0);
		setData(proc, "Sys", 0.0);
		setData(proc, "Nice", 0.0);
		setData(proc, "Disk", 0.0);
		setData(proc, "Idle", 0.0);*/
		setData(proc, "iIdle", 0);
		setData(proc, "iSys",  0);
		setData(proc, "iNice", 0);
		setData(proc, "iDisk", 0);
		setData(proc, "iLoad", 0);

		return true;

	}else{

		return false;

	}

}

bool CPUMonitor::updateProc(QString ProcessorNumber, cpu_jiffies &old_jiffies, cpu_jiffies &diff){

	cpu_jiffies temp;
	bool ok;
	QString input;
	QTextStream readin;

	if(procstat->openMode() == QIODevice::NotOpen){

		if(!procstat->open(QIODevice::ReadOnly | QIODevice::Text)){

			return false;

		}

	}

	readin.setDevice(procstat);
	readin.seek(0);

	do {

		readin >> input;
		if(input == QString("cpu%1").arg(ProcessorNumber) ){
			break;
		}

	}while(!readin.atEnd());

	if(readin.status() & QTextStream::ReadPastEnd){
		return false;
	}

	readin >> input;
	temp.user = input.toLong();
	readin >> input;
	temp.nice = input.toLong();
	readin >> input;
	temp.sys = input.toLong();
	readin >> input;
	temp.idle = input.toLong();
	readin >> input;
	temp.disk = input.toLong(&ok);
	///Neccessary as some kernels may not support IOWait.
	if(!ok){
		temp.disk = 0;
	}

	diff.user = temp.user - old_jiffies.user;
	diff.sys = temp.sys - old_jiffies.sys;
	diff.nice = temp.nice - old_jiffies.nice;
	diff.disk = temp.disk - old_jiffies.disk;
	diff.idle = temp.idle - old_jiffies.idle;

	old_jiffies = temp;

	return true;

}

bool CPUMonitor::updateProcessor(QString ProcessorNumber, cpu_jiffies &diff){

	if(ProcessorNumber.toInt() >= cpuTimeVector.count()){

		cpuTimeVector.resize(ProcessorNumber.toInt() + 1);

	}

	if(!updateProc(ProcessorNumber, cpuTimeVector[ProcessorNumber.toInt()], diff)){

		return false;

	}else {

		return true;

	}



}

bool CPUMonitor::updateSourceEvent(const QString& source){

	unsigned long total_diff;
	cpu_jiffies diff;
	bool ok;
	int procNumber = source.toInt(&ok);

	if(ok){

		if(procNumber < cpuTimeVector.count()){

			if(!updateProcessor(source, diff)){

				return false;

			}

			total_diff = diff.user + diff.sys + diff.nice + diff.disk + diff.idle;

			if(total_diff == 0){

				total_diff = 1;

			}
	
			/*setData(source, "User", (double)diff.user / (double)total_diff);
			setData(source, "Sys", diff.sys / (double)total_diff);
			setData(source, "Nice", diff.nice / (double)total_diff);
			setData(source, "Disk", diff.disk / (double)total_diff);
			setData(source, "Idle", diff.idle / (double)total_diff);*/
			setData(source, "iIdle", (int)(diff.idle*100 / (double)total_diff));
			setData(source, "iSys",  (int)(diff.sys*100 / (double)total_diff));
			setData(source, "iNice", (int)((diff.sys+diff.nice)*100 / (double)total_diff));
			setData(source, "iDisk", (int)((diff.sys+diff.nice+diff.disk)*100 / (double)total_diff));
			setData(source, "iLoad", 100-(int)(diff.idle*100 / (double)total_diff) );

			return true;

		}else{

			return false;

		}

	}else if(source == "Number of CPUs"){

		setData(source, m_numcpus);
		return true;

	}else if(source == "Average CPU Usage"){
///@todo Implement Properly
		cpu_jiffies diff;

		updateProc("", *average, diff);

		total_diff = diff.user + diff.sys + diff.nice + diff.disk + diff.idle;

		if(total_diff == 0){

			total_diff = 1;

		}
	
/*		setData(source, "User", (double)diff.user / (double)total_diff);
		setData(source, "Sys", diff.sys / (double)total_diff);
		setData(source, "Nice", diff.nice / (double)total_diff);
		setData(source, "Disk", diff.disk / (double)total_diff);
		setData(source, "Idle", diff.idle / (double)total_diff);*/
		setData(source, "iIdle", (int)(diff.idle*100 / (double)total_diff));
		setData(source, "iSys",  (int)(diff.sys*100 / (double)total_diff));
		setData(source, "iNice", (int)((diff.sys+diff.nice)*100 / (double)total_diff));
		setData(source, "iDisk", (int)((diff.sys+diff.nice+diff.disk)*100 / (double)total_diff));
		setData(source, "iLoad", 100-(int)(diff.idle*100 / (double)total_diff) );

		return true;

	}

	return false;

}

#include "cpu_load.moc"
