/*
    KSysGuard, the KDE System Guard

	Copyright (c) 1999 - 2001 Chris Schlaeger <cs@kde.org>

    This program is free software; you can redistribute it and/or
    modify it under the terms version 2 of of the GNU General Public
    License as published by the Free Software Foundation.

    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.

	KSysGuard is currently maintained by Chris Schlaeger <cs@kde.org>.
	Please do not commit any changes without consulting me first. Thanks!

*/

#include <assert.h>

#include <qtimer.h>

#include <kapplication.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kdialogbase.h>
#include <klistviewsearchline.h>

#include <ksgrd/SensorManager.h>

#include "ProcessController.moc"
#include "SignalIDs.h"

#include <qcheckbox.h>
#include <qcombobox.h>
#include <qgroupbox.h>
#include <qlayout.h>

#include <kapplication.h>
#include <kpushbutton.h>



ProcessController::ProcessController(QWidget* parent, const char* name, const QString &title, bool nf)
	: KSGRD::SensorDisplay(parent, name, title, nf)
{
	dict.setAutoDelete(true);
	dict.insert("Name", new QString(i18n("Name")));
	dict.insert("PID", new QString(i18n("PID")));
	dict.insert("PPID", new QString(i18n("PPID")));
	dict.insert("UID", new QString(i18n("UID")));
	dict.insert("GID", new QString(i18n("GID")));
	dict.insert("Status", new QString(i18n("Status")));
	dict.insert("User%", new QString(i18n("User%")));
	dict.insert("System%", new QString(i18n("System%")));
	dict.insert("Nice", new QString(i18n("Nice")));
	dict.insert("VmSize", new QString(i18n("VmSize")));
	dict.insert("VmRss", new QString(i18n("VmRss")));
	dict.insert("Login", new QString(i18n("Login")));
	dict.insert("Command", new QString(i18n("Command")));

	// Setup the geometry management.
	gm = new QVBoxLayout(this, 10);
	Q_CHECK_PTR(gm);
	gm->addSpacing(15);

	gmSearch = new QHBoxLayout();
	Q_CHECK_PTR(gmSearch);
	gm->addLayout(gmSearch, 0);

	// Create the table that lists the processes.
	pList = new ProcessList(this, "pList");
	Q_CHECK_PTR(pList);
	pList->setShowSortIndicator(true);
	pListSearchLine = new KListViewSearchLineWidget(pList, this, "process_list_search_line");
	gmSearch->addWidget(pListSearchLine, 1);

	connect(pList, SIGNAL(killProcess(int, int)),
			this, SLOT(killProcess(int, int)));
	connect(pList, SIGNAL(reniceProcess(const QValueList<int> &, int)),
			this, SLOT(reniceProcess(const QValueList<int> &, int)));
	connect(pList, SIGNAL(listModified(bool)),
			this, SLOT(setModified(bool)));

	/* Create the combo box to configure the process filter. The
	 * cbFilter must be created prior to constructing pList as the
	 * pList constructor sets cbFilter to its start value. */
	cbFilter = new QComboBox(this, "pList_cbFilter");
	Q_CHECK_PTR(cbFilter);
	gmSearch->addWidget(cbFilter,0);
	cbFilter->insertItem(i18n("All Processes"), 0);
	cbFilter->insertItem(i18n("System Processes"), 1);
	cbFilter->insertItem(i18n("User Processes"), 2);
	cbFilter->insertItem(i18n("Own Processes"), 3);
	cbFilter->setMinimumSize(cbFilter->sizeHint());
	// Create the check box to switch between tree view and list view.
	xbTreeView = new QCheckBox(i18n("&Tree"), this, "xbTreeView");
	Q_CHECK_PTR(xbTreeView);
	xbTreeView->setMinimumSize(xbTreeView->sizeHint());
	connect(xbTreeView, SIGNAL(toggled(bool)),
			this, SLOT(setTreeView(bool)));


	/* When the both cbFilter and pList are constructed we can connect the
	 * missing link. */
	connect(cbFilter, SIGNAL(activated(int)),
			this, SLOT(filterModeChanged(int)));

	// Create the 'Refresh' button.
	bRefresh = new KPushButton( KGuiItem(  i18n( "&Refresh" ), "reload" ),
            this, "bRefresh" );
	Q_CHECK_PTR(bRefresh);
	bRefresh->setMinimumSize(bRefresh->sizeHint());
	connect(bRefresh, SIGNAL(clicked()), this, SLOT(updateList()));

	// Create the 'Kill' button.
	bKill = new KPushButton(i18n("&Kill"), this, "bKill");
	Q_CHECK_PTR(bKill);
	bKill->setMinimumSize(bKill->sizeHint());
	connect(bKill, SIGNAL(clicked()), this, SLOT(killProcess()));
	/* Disable the kill button until we know that the daemon supports the
	 * kill command. */
	bKill->setEnabled(false);
	killSupported = false;

	gm->addWidget(pList, 1);

	gm1 = new QHBoxLayout();
	Q_CHECK_PTR(gm1);
	gm->addLayout(gm1, 0);
	gm1->addStretch();
	gm1->addWidget(xbTreeView);
	gm1->addStretch();
	gm1->addWidget(bRefresh);
	gm1->addStretch();
	gm1->addWidget(bKill);
	gm1->addStretch();
	gm->addSpacing(5);

	gm->activate();

	setPlotterWidget(pList);

	setMinimumSize(sizeHint());
	fixTabOrder(); 
}

void ProcessController::setSearchFocus() {
	//stupid search line widget.  See rant in fixTabOrder
	if(!pListSearchLine->searchLine())
		QTimer::singleShot(100, this, SLOT(setSearchFocus()));
	else {
		pListSearchLine->searchLine()->setFocus();
	}
}
void ProcessController::fixTabOrder() {

	//Wow, I hate this search line widget so much.
	//It creates the searchline in a singleshot timer.  This makes it totally unpredictable when searchLine is actually valid.
	//So we set up singleshot timer and call ourselves over and over until it's ready.
	//
	//Did i mention I hate this?
	if(!pListSearchLine->searchLine())
		QTimer::singleShot(100, this, SLOT(fixTabOrder()));
	else {
		setTabOrder(pListSearchLine->searchLine(), cbFilter);
		setTabOrder(cbFilter, pList);
		setTabOrder(pList, xbTreeView);
		setTabOrder(xbTreeView, bRefresh);
		setTabOrder(bRefresh, bKill);
	}
}

void
ProcessController::resizeEvent(QResizeEvent* ev)
{
	if(frame())
		frame()->setGeometry(0, 0, width(), height());

	QWidget::resizeEvent(ev);
}

bool
ProcessController::addSensor(const QString& hostName,
							 const QString& sensorName,
							const QString& sensorType,
							 const QString& title)
{
	if (sensorType != "table")
		return (false);

	registerSensor(new KSGRD::SensorProperties(hostName, sensorName, sensorType, title));
	/* This just triggers the first communication. The full set of
	 * requests are send whenever the sensor reconnects (detected in
	 * sensorError(). */

	sendRequest(hostName, "test kill", 4);

	if (title.isEmpty())
		setTitle(i18n("%1: Running Processes").arg(hostName));
	else
		setTitle(title);

	return (true);
}

void
ProcessController::updateList()
{
	sendRequest(sensors().at(0)->hostName(), "ps", 2);
}

void
ProcessController::killProcess(int pid, int sig)
{
	sendRequest(sensors().at(0)->hostName(),
				QString("kill %1 %2" ).arg(pid).arg(sig), 3);

	if ( !timerOn() )
	    // give ksysguardd time to update its proccess list
	    QTimer::singleShot(3000, this, SLOT(updateList()));
	else
	    updateList();
}

void
ProcessController::killProcess()
{
	const QStringList& selectedAsStrings = pList->getSelectedAsStrings();
	if (selectedAsStrings.isEmpty())
	{
		KMessageBox::sorry(this,
						   i18n("You need to select a process first."));
		return;
	}
	else
	{
		QString  msg = i18n("Do you want to kill the selected process?",
				"Do you want to kill the %n selected processes?",
				selectedAsStrings.count());

		KDialogBase *dlg = new KDialogBase (  i18n ("Kill Process"), 
						      KDialogBase::Yes | KDialogBase::Cancel,
						      KDialogBase::Yes, KDialogBase::Cancel, this->parentWidget(),
						      "killconfirmation",
			       			      true, true, KGuiItem(i18n("Kill")));

		bool dontAgain = false;
		
		int res = KMessageBox::createKMessageBox(dlg, QMessageBox::Question,
			                                 msg, selectedAsStrings,
							 i18n("Do not ask again"), &dontAgain, 
							 KMessageBox::Notify);

		if (res != KDialogBase::Yes)
		{
			return;
		}
	}

	const QValueList<int>& selectedPIds = pList->getSelectedPIds();

	// send kill signal to all seleted processes
	QValueListConstIterator<int> it;
	for (it = selectedPIds.begin(); it != selectedPIds.end(); ++it)
		sendRequest(sensors().at(0)->hostName(), QString("kill %1 %2" ).arg(*it)
					.arg(MENU_ID_SIGKILL), 3);

	if ( !timerOn())
		// give ksysguardd time to update its proccess list
		QTimer::singleShot(3000, this, SLOT(updateList()));
	else
		updateList();
}

void
ProcessController::reniceProcess(const QValueList<int> &pids, int niceValue)
{
	for( QValueList<int>::ConstIterator it = pids.constBegin(), end = pids.constEnd(); it != end; ++it )
		sendRequest(sensors().at(0)->hostName(),
					QString("setpriority %1 %2" ).arg(*it).arg(niceValue), 5);
	sendRequest(sensors().at(0)->hostName(), "ps", 2);  //update the display afterwards
}

void
ProcessController::answerReceived(int id, const QString& answer)
{
	/* We received something, so the sensor is probably ok. */
	sensorError(id, false);

	switch (id)
	{
	case 1:
	{
		/* We have received the answer to a ps? command that contains
		 * the information about the table headers. */
		KSGRD::SensorTokenizer lines(answer, '\n');
		if (lines.count() != 2)
		{
			kdDebug (1215) << "ProcessController::answerReceived(1)"
				  "wrong number of lines [" <<  answer << "]" << endl;
			sensorError(id, true);
			return;
		}
		KSGRD::SensorTokenizer headers(lines[0], '\t');
		KSGRD::SensorTokenizer colTypes(lines[1], '\t');

		pList->removeColumns();
		for (unsigned int i = 0; i < headers.count(); i++)
		{
			QString header;
			if (dict[headers[i]])
				header = *dict[headers[i]];
			else
				header = headers[i];
			pList->addColumn(header, colTypes[i]);
		}

		break;
	}
	case 2:
		/* We have received the answer to a ps command that contains a
		 * list of processes with various additional information. */
		pList->update(answer);
		pListSearchLine->searchLine()->updateSearch(); //re-apply the filter
		break;
	case 3:
	{
		// result of kill operation
		kdDebug(1215) << answer << endl;
		KSGRD::SensorTokenizer vals(answer, '\t');
		switch (vals[0].toInt())
		{
		case 0:	// successful kill operation
			break;
		case 1:	// unknown error
			KSGRD::SensorMgr->notify(
				i18n("Error while attempting to kill process %1.")
				.arg(vals[1]));
			break;
		case 2:
			KSGRD::SensorMgr->notify(
				i18n("Insufficient permissions to kill "
							 "process %1.").arg(vals[1]));
			break;
		case 3:
			KSGRD::SensorMgr->notify(
				i18n("Process %1 has already disappeared.")
				.arg(vals[1]));
			break;
		case 4:
			KSGRD::SensorMgr->notify(i18n("Invalid Signal."));
			break;
		}
		break;
	}
	case 4:
		killSupported = (answer.toInt() == 1);
		pList->setKillSupported(killSupported);
		bKill->setEnabled(killSupported);
		break;
	case 5:
	{
		// result of renice operation
		kdDebug(1215) << answer << endl;
		KSGRD::SensorTokenizer vals(answer, '\t');
		switch (vals[0].toInt())
		{
		case 0:	// successful renice operation
			break;
		case 1:	// unknown error
			KSGRD::SensorMgr->notify(
				i18n("Error while attempting to renice process %1.")
				.arg(vals[1]));
			break;
		case 2:
			KSGRD::SensorMgr->notify(
				i18n("Insufficient permissions to renice "
							 "process %1.").arg(vals[1]));
			break;
		case 3:
			KSGRD::SensorMgr->notify(
				i18n("Process %1 has already disappeared.")
				.arg(vals[1]));
			break;
		case 4:
			KSGRD::SensorMgr->notify(i18n("Invalid argument."));
			break;
		}
		break;
	}
	}
}

void
ProcessController::sensorError(int, bool err)
{
	if (err == sensors().at(0)->isOk())
	{
		if (!err)
		{
			/* Whenever the communication with the sensor has been
			 * (re-)established we need to requests the full set of
			 * properties again, since the back-end might be a new
			 * one. */
			sendRequest(sensors().at(0)->hostName(), "test kill", 4);
			sendRequest(sensors().at(0)->hostName(), "ps?", 1);
			sendRequest(sensors().at(0)->hostName(), "ps", 2);
		}

		/* This happens only when the sensorOk status needs to be changed. */
		sensors().at(0)->setIsOk( !err );
	}
	setSensorOk(sensors().at(0)->isOk());
}

bool
ProcessController::restoreSettings(QDomElement& element)
{
	bool result = addSensor(element.attribute("hostName"),
							element.attribute("sensorName"), (element.attribute("sensorType").isEmpty() ? "table" : element.attribute("sensorType")),
							QString::null);

	xbTreeView->setChecked(element.attribute("tree").toInt());
	setTreeView(element.attribute("tree").toInt());

	uint filter = element.attribute("filter").toUInt();
	cbFilter->setCurrentItem(filter);
	filterModeChanged(filter);

	uint col = element.attribute("sortColumn").toUInt();
	bool inc = element.attribute("incrOrder").toUInt();

	if (!pList->load(element))
		return (false);

	pList->setSortColumn(col, inc);

	SensorDisplay::restoreSettings(element);

	setModified(false);

	return (result);
}

bool
ProcessController::saveSettings(QDomDocument& doc, QDomElement& element, bool save)
{
	element.setAttribute("hostName", sensors().at(0)->hostName());
	element.setAttribute("sensorName", sensors().at(0)->name());
	element.setAttribute("sensorType", sensors().at(0)->type());
	element.setAttribute("tree", (uint) xbTreeView->isChecked());
	element.setAttribute("filter", cbFilter->currentItem());
	element.setAttribute("sortColumn", pList->getSortColumn());
	element.setAttribute("incrOrder", pList->getIncreasing());

	if (!pList->save(doc, element))
		return (false);

	SensorDisplay::saveSettings(doc, element);

	if (save)
		setModified(false);

	return (true);
}
