/*
	appobserver.cpp - An application observer/killer
	Copyright (C) 2005  Konrad Twardowski <kdtonline@poczta.onet.pl>

	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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <sys/types.h>

#include "appobserver.h"
#include "miscutils.h"
#include "mmainwindow.h"

#include <errno.h>
#include <signal.h>

#include <tqcombobox.h>
#include <tqlistbox.h>
#include <tqprocess.h>
#include <tqwhatsthis.h>

#include <kdebug.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpushbutton.h>

// TODO: 2.0: bigger combo box list
// http://ariya.blogspot.com/2006/03/resize-pop-up-list-of-combo-box_15.html

// public

AppObserver::AppObserver(TQWidget *parent)
	: TQHBox(parent),
	_processesMap(new TQMap<int, Process>()),
	_process(0),
	_buf(TQString::null)
{
	setName("AppObserver");

	// refresh button
	b_refresh = new KPushButton(this, "KPushButton::b_refresh");
	b_refresh->setIconSet(SmallIcon("reload"));
	b_refresh->setSizePolicy(TQSizePolicy(TQSizePolicy::Fixed, TQSizePolicy::Preferred));
	MiscUtils::setHint(b_refresh, i18n("Refresh the list of processes"));
	connect(b_refresh, TQ_SIGNAL(clicked()), TQ_SLOT(slotRefresh()));

	// processes combo box
	cb_processes = new TQComboBox(this, "TQComboBox::cb_processes");
	cb_processes->setFocusPolicy(StrongFocus);
	MiscUtils::setHint(cb_processes, i18n("List of the running processes"));

	// kill button
	b_kill = new KPushButton(SmallIcon("edit-delete"), i18n("Kill"), this, "KPushButton::b_kill");
	b_kill->setSizePolicy(TQSizePolicy(TQSizePolicy::Fixed, TQSizePolicy::Preferred));
	MiscUtils::setHint(b_kill, i18n("Kill the selected process"));
	connect(b_kill, TQ_SIGNAL(clicked()), TQ_SLOT(slotKill()));
}

TQString AppObserver::getInfo() const
{
	int index = cb_processes->currentItem();
	TQMapIterator<int, Process> i = _processesMap->find(index);

	if (i == _processesMap->end())
		return TQString::null;

	return i18n("Waiting for \"%1\"").arg(i.data().command);
}

bool AppObserver::isSelectedProcessRunning() const
{
// TODO: 2.0: common code
	int index = cb_processes->currentItem();
	TQMapIterator<int, Process> i = _processesMap->find(index);

	if (i == _processesMap->end())
		return false;

	if (::kill(i.data().pid, 0)) // check if process exists
	{
		switch (errno)
		{
			case EINVAL: return false;
			case ESRCH: return false;
			case EPERM: return true;
		}
	}

	return true;
}

bool AppObserver::isValid() const
{
	if (!isSelectedProcessRunning())
	{
		KMessageBox::error(
			ks_main,
			i18n("The selected process does not exist!")
		);

		return false;
	}

	return true;
}

void AppObserver::refresh()
{
	// kdDebug() << "AppObserver::refresh()" << endl;

	b_refresh->setEnabled(false);
	cb_processes->setEnabled(false);
	b_kill->setEnabled(false);

	_buf = TQString::null;
	cb_processes->clear();
	_processesMap->clear();

	if (!_process)
	{
		_process = new TQProcess(this);
		connect(_process, TQ_SIGNAL(processExited()), TQ_SLOT(slotProcessExit()));
		connect(_process, TQ_SIGNAL(readyReadStdout()), TQ_SLOT(slotReadStdout()));
	}
	else
	{
		if (_process->isRunning())
			_process->kill();
		_process->clearArguments();
	}
	_process->addArgument("ps");
	// show all processes
	_process->addArgument("-A");
	// order: user pid command
	_process->addArgument("-o");
	_process->addArgument("user=");
	_process->addArgument("-o");
	_process->addArgument("pid=");
	_process->addArgument("-o");
	_process->addArgument("comm=");
	// sort by command
	_process->addArgument("--sort");
	_process->addArgument("comm");

	// start process
	if (!_process->start())
	{
		// error
		KMessageBox::error(
			ks_main,
			MiscUtils::HTML(i18n("Could not execute command<br><br><b>%1</b>").arg(_process->arguments().join(" ")))
		);
		cb_processes->setEnabled(false);
		cb_processes->insertItem(SmallIcon("messagebox_warning"), i18n("Error"));
		b_kill->setEnabled(false);
		b_refresh->setEnabled(true);
	}
}

void AppObserver::setWidgetsEnabled(const bool yes) const
{
	b_refresh->setEnabled(yes);
	cb_processes->setEnabled(yes);
	b_kill->setEnabled(yes);
}

// private slots

void AppObserver::slotKill()
{
	int index = cb_processes->currentItem();
	TQMapIterator<int, Process> i = _processesMap->find(index);
// FIXME: 2.0: error message
	if (i != _processesMap->end())
	{
		if (KMessageBox::warningYesNo(
			ks_main,
			MiscUtils::HTML(i18n("Are you sure you want to KILL<br><b>%1</b>?<br><br>All unsaved data will be lost!").arg(cb_processes->currentText()))
		) != KMessageBox::Yes)
			return;

		pid_t pid = i.data().pid;
		if (::kill(pid, SIGKILL))
		{
			switch (errno)
			{
				case EINVAL:
					// kdDebug() << "EINVAL" << endl;
					break;
				case ESRCH:
					KMessageBox::error(
						ks_main,
						MiscUtils::HTML(i18n("Process not found<br><b>%1</b>").arg(cb_processes->currentText()))
					);
					break;
				case EPERM:
					KMessageBox::error(
						ks_main,
						MiscUtils::HTML(i18n("No permissions to kill<br><b>%1</b>").arg(cb_processes->currentText()))
					);
					break;
			}
		}
		else
		{
			cb_processes->changeItem(
				*cb_processes->pixmap(index),
				i18n("DEAD: %1").arg(cb_processes->text(index)),
				index
			);
		}
	}
}

void AppObserver::slotProcessExit()
{
	// kdDebug() << "AppObserver::slotProcessExit()" << endl;

	int index = 0;
	int line = 0;
	Process p;
	TQStringList list = TQStringList::split(" ", _buf.simplifyWhiteSpace());
	for (TQStringList::Iterator i = list.begin(); i != list.end(); ++i)
	{
		switch (line)
		{
			// user
			case 0:
				line++; // next is pid
				p = Process();
				p.user = *i;
				break;
			// pid
			case 1:
				line++; // next is command
				p.pid = (*i).toLong();
				break;
			// command
			case 2:
				line = 0; // next is user (wrap around)
				p.command = *i;
				_processesMap->insert(index, p);
				// kdDebug() << "USER=" << p.user << " PID=" << p.pid << " COMMAND=" << p.command << endl;
				TQString text = TQString("%1 (pid %2, %3)")
					.arg(p.command)
					.arg(p.pid)
					.arg(p.user);
				cb_processes->insertItem(SmallIcon(p.command), text);
				index++;
				break;
		}
	}
	if (cb_processes->count() == 0)
	{
		cb_processes->setEnabled(false);
		cb_processes->insertItem(SmallIcon("messagebox_warning"), i18n("Error"));
		b_kill->setEnabled(false);
	}
	else
	{
		cb_processes->setEnabled(true);
		b_kill->setEnabled(true);
	}
	b_refresh->setEnabled(true);
}

void AppObserver::slotReadStdout()
{
	// kdDebug() << "AppObserver::slotReadStdout()" << endl;

	_buf.append(_process->readStdout());
}

void AppObserver::slotRefresh()
{
	refresh();
}
#include "appobserver.moc"
