/*
 * This file is part of DesktopBSD, see the README file.
 *
 * Author: Peter Hofer <ph@desktopbsd.net>
 *         (C) 2004, 2005
 *
 * Copyright: BSD, see the COPYING file included in this distribution
 *
 */

#include <pkgupgradecontroller.h>
#include <dbsdglobal.h>
#include <dialogs.h>
#include <packagesettings.h>
#include <pkgdbupdateengine.h>
#include <unixprocess.h>
#include <frmtextbrowser.h>
#include <translation.h>
#include <pkgupgradeengine.h>

/* Qt */
#include <qframe.h>
#include <qprogressbar.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qtimer.h>
#include <qpixmap.h>
#include <qpushbutton.h>

/* KDE/KParts */
#include <kparts/part.h>
#include <klibloader.h>
#include <kde_terminal_interface.h>
#include <kprocess.h>

#include "frmpkgupgradecontroller.h"

FrmPkgUpgradeController::FrmPkgUpgradeController(QWidget* parent, const char* name, WFlags fl)
	:	uiFrmPkgUpgradeController(parent, name,  fl),
		installEngine(NULL),
		upgradeEngine(NULL),
		currentMessageType(None),
		status(Undefined),
		program(NoProgram)
{
	KLibFactory* factory = KLibLoader::self()->factory("libkonsolepart");
	if(factory == NULL) {
		Dialogs::warnDlg(tr("Error: No Konsole installed!"));;
		return;
	}

	terminalPart = static_cast<KParts::Part*>(factory->create(frTerminal, NULL, "QObject", "KParts::ReadOnlyPart"));
	terminalInterface = static_cast<ExtTerminalInterface*>(terminalPart->qt_cast("ExtTerminalInterface"));

	if(terminalInterface == NULL) {
		Dialogs::warnDlg(tr("Error: Can't access the extended Konsole interface.\n\n"
								  "Make sure you are using KDE 3.2 or later."));
		return;
	}

	terminalInterface->setAutoStartShell(false);
	terminalInterface->setAutoDestroy(false);

	frTerminalLayout->addWidget(terminalPart->widget(), 0, 0);

	controller = new PkgUpgradeController(this);
	connect(terminalPart, SIGNAL(receivedData(const QString &)), controller, SLOT(outputReceived(const QString &)));
	connect(terminalPart, SIGNAL(processExited(KProcess *)), SLOT(processExited(KProcess *)));
	connect(controller, SIGNAL(informationMessage(const QString &)), SLOT(informationMessage(const QString &)));
	connect(controller, SIGNAL(progressMessage(const QString &)), SLOT(progressMessage(const QString &)));
	connect(controller, SIGNAL(warningMessage(const QString &)), SLOT(warningMessage(const QString &)));
	connect(controller, SIGNAL(userInputRequired(const QString &)), SLOT(userInputRequired(const QString &)));
	connect(controller, SIGNAL(operationProgressChanged(int)), SLOT(operationProgressChanged(int)));
	connect(controller, SIGNAL(currentPackageChanged(const QString &)), lblCurrentPackage, SLOT(setText(const QString &)));
	connect(controller, SIGNAL(overallProgressChanged(int)), this, SLOT(overallProgressChanged(int)));

	frTerminal->hide();

	DBSDIconLoader *il = DBSDGlobal::getIconLoader();
	btnShowHideTerminal->setIconSet(il->loadIcon("view_detailed", DBSDIconLoader::Button));
	btnCancelClose->setIconSet(il->loadIcon("button_cancel", DBSDIconLoader::Button));
	pmNone = il->getBlankPixmap(16, 16);
	pmProgress = il->loadIcon("ok", DBSDIconLoader::Size16x16);
	pmWarning = il->loadIcon("warning", DBSDIconLoader::Size16x16);
	pmInfo = il->loadIcon("info", DBSDIconLoader::Size16x16);
	pmUserInput = il->loadIcon("key_enter", DBSDIconLoader::Size16x16);
	pmClose = il->loadIcon("fileclose", DBSDIconLoader::Button);
	lblPreviousMessageIcon->setPixmap(pmNone);
	lblCurrentMessageIcon->setPixmap(il->loadIcon("1rightarrow", DBSDIconLoader::Size16x16));

	busyIndicatorTimer = new QTimer(this);
	connect(busyIndicatorTimer, SIGNAL(timeout()), SLOT(moveBusyIndicator()));
	enableBusyIndicatorTimer(true);

	connect(btnFixPkgdb, SIGNAL(clicked()), SLOT(fixPkgdb()));
	connect(btnCancelClose, SIGNAL(clicked()), SLOT(close()));
}


void FrmPkgUpgradeController::closeEvent(QCloseEvent *e) {
	if(status == Running)
	{
		e->ignore();
		
		if(controller == NULL)
		{
			Dialogs::infoDlg(tr("The process is still running.\n"
									  "\n"
									  "Please check the detail view."));
		}
		else
		{
			bool cancel = Dialogs::question(tr("Canceling the installation or upgrade of packages is\n"
														  "DANGEROUS and can cause damage to installed software and\n"
														  "the package database! NEVER do this when a package\n"
														  "is currently being installed, uninstalled or registered!\n"
														  "\n"
														  "Do you really want to proceed?"));
	
			if(cancel)
			{
				status = Canceled;
				controller->killAll();
			}
		}
	} else
		e->accept();
}


void FrmPkgUpgradeController::processExited(KProcess *process) {
	if(status != Canceled && program == Portupgrade && installEngine != NULL)
	{
		UnixProcess *process = installEngine->getProcess();
		if(process != NULL)
		{
			program = Portinstall;
			overallProgressChanged(0);
			controller->processChanged();
			
			startProcess(*process);
			delete process;

			return;
		}
	}
	
	if(status != Canceled)
		status = process->normalExit() ? FinishedNormal : FinishedAborted;
	
	btnCancelClose->setText(tr("Close"));
	btnCancelClose->setIconSet(pmClose);
	
	if(program == Portupgrade || program == Portinstall)
	{
		/* Delete now to free resources immediately, including PkgUpgradeNotifier
		 * which needs to be deleted when all operations are completed.
		 */
		delete controller;
		controller = NULL;
	
		pbTotalProgress->setProgress(100);
		pbCurrentMessage->hide();
		enableBusyIndicatorTimer(false);
	
		if(status == Canceled)
		{
			message(Warning, tr("- Canceled. Click \"Details\" for more information."));
	
			Dialogs::infoDlg(tr("All operations were canceled.\n"
									"\n"
									"There are probably files left from an unfinished build.\n"
									"Please consider running the Cleaning Wizard to remove\n"
									"these files from your system."));
	
			setResult(Rejected);
		}
		else
		{
			message(Information, tr("- All operations finished. Click \"Details\" for more information."));
	
			exitCode = process->exitStatus();
			if(status == FinishedAborted || exitCode != 0)
			{
				setResult(Rejected);
				
				btnFixPkgdb->setEnabled(true);
	
				const QString failureMsg = tr("<p><b>Installation/upgrade of some packages failed.</b></p>"

														"<p>You should click <i>Details</i> and check the output for messages"
														" that indicate what error(s) occured. Please also include this"
														" information when seeking help in our forums or mailing lists.</p>"

														"<p><b>Possible reasons:</b></p>"
														"<ul>"
														"<li> The <i>package database</i> might contain conflicts. If this is"
														" the case, please click the 'Fix package database' button. </li>"
														"<li> Some installed packages might be <i>required in a newer version</i>"
														" than the ones installed. Try upgrading packages required by the"
														" software you are trying to install or upgrade. </li>"

														"%1"

														"</ul>"

														"<p>Click <i>Close</i> when you are done.</p>");
				
				const QString binReasons = tr("<li> Binary packages for the latest version of that software might be"
														" <i>unavailable</i>. Usually, it takes a few days for new versions to"
														" become available. </li>"

														"<li> Redistribution of certain packages in binary form is <i>restricted</i>."
														" This applies especially to packages that use patented algorithms"
														" (MP3 encoding, for example) and software that is only free for"
														" \"non-commercial\" use. Other packages that require such packages"
														" might also not be available in binary form. </li>");
				
				Dialogs::warnDlg(failureMsg.arg((PackageSettings().getPackageUse() == 2) ? binReasons : ""));
			}
			else
				setResult(Accepted);
		}
	}
	else if(program == PkgDb)
	{
		if(status == Canceled)
			Dialogs::infoDlg(tr("<p>Fixing the package database was canceled.</p>"
									  "<p>Click <i>Close</i> to proceed.</p>"));
		else
			Dialogs::infoDlg(tr("<p>Fixing the package database complete.</p>"
									  "<p>Click <i>Close</i> to proceed or <i>Fix package database</i>"
									  " if there are unresolved problems left.</p>"));
	}
	
	program = NoProgram;
}


void FrmPkgUpgradeController::fixPkgdb()
{
	/* Show terminal if not already open */
	btnShowHideTerminal->setOn(true);

	/* Display instructions */
	frmTextBrowser  *bdlg = new frmTextBrowser(this);
				
  /*
	* Try to cut locale information down until we find
	* a valid localized file.
	* 
	* pkgdb-fix.de_AT -> pkgdb-fix.de -> pkgdb-fix
	*
   */
	QString  path = "/usr/local/share/doc/desktopbsd/pkgdb-fix",
	locale = '.' + Translation::getLocale();
						
	while(!locale.isEmpty() && !QFileInfo(path + locale).exists())
	{
		int length = locale.length();
	
		if(length >= 3)
			locale.truncate(length - 3);
		else
			locale = "";
	}
					
	bdlg->setDisplaySource(path + locale);
	bdlg->setCaption(tr("Database fix instructions"));
	bdlg->exec();				
	delete bdlg;

	
	PkgDBUpdateEngine  dbUpdateEngine;
	dbUpdateEngine.setUpdate(false);
	dbUpdateEngine.setFixMethod(PkgDBUpdateEngine::Interactive);
	UnixProcess *p = dbUpdateEngine.getProcess();
	if(p != NULL)
	{
		program = PkgDb;
		startProcess(*p);
		delete p;
	}
}


void FrmPkgUpgradeController::moveBusyIndicator() {
	pbCurrentMessage->setProgress(pbCurrentMessage->progress() + 1);
}


void FrmPkgUpgradeController::enableBusyIndicatorTimer(bool enable) {
	if(enable)
		busyIndicatorTimer->start(23, false);
	else
		busyIndicatorTimer->stop();
}


void FrmPkgUpgradeController::progressMessage(const QString & m) {
	message(Progress, m);
}


void FrmPkgUpgradeController::warningMessage(const QString & m) {
	message(Warning, m);
}


void FrmPkgUpgradeController::informationMessage(const QString & m) {
	message(Information, m);
}


void FrmPkgUpgradeController::userInputRequired(const QString & m) {
	btnShowHideTerminal->setOn(true);
	message(UserInput, m);
}


void FrmPkgUpgradeController::message(MessageType type, const QString & string) {
	lblSecondPreviousMessageIcon->setPixmap(*lblPreviousMessageIcon->pixmap());
	lblSecondPreviousMessage->setText(lblPreviousMessage->text());

	lblPreviousMessage->setText(lblCurrentMessage->text());
	if(currentMessageType == Information)
		lblPreviousMessageIcon->setPixmap(pmInfo);
	else if(currentMessageType == Warning)
		lblPreviousMessageIcon->setPixmap(pmWarning);
	else if(currentMessageType == Progress)
		lblPreviousMessageIcon->setPixmap(pmProgress);
	else if(currentMessageType == UserInput)
		lblPreviousMessageIcon->setPixmap(pmUserInput);
	else
		lblPreviousMessageIcon->setPixmap(pmNone);

	currentMessageType = type;
	lblCurrentMessage->setText(string);

	pbCurrentMessage->setTotalSteps(0);
	pbCurrentMessage->setPercentageVisible(false);
	enableBusyIndicatorTimer(true);
}


void FrmPkgUpgradeController::overallProgressChanged(int percent)
{
	if(installEngine != NULL && upgradeEngine != NULL)
	{
		// First 50% for portupgrade, second 50% for portinstall
		percent /= 2;
		
		if(program == Portinstall)
		{
			percent += 50;
		}
	}
		
	pbTotalProgress->setProgress(percent);
}


void FrmPkgUpgradeController::operationProgressChanged(int percent) {
	pbCurrentMessage->show();
	enableBusyIndicatorTimer(false);
	pbCurrentMessage->setTotalSteps(100);
	pbCurrentMessage->setPercentageVisible(true);
	pbCurrentMessage->setProgress(percent);
}


void FrmPkgUpgradeController::polish() {
	uiFrmPkgUpgradeController::polish();
	adjustSize();

	UnixProcess *process = NULL;
	
	if(process == NULL && upgradeEngine != NULL)
	{
		program = Portupgrade;
		process = upgradeEngine->getProcess();
	}
	
	if(process == NULL && installEngine != NULL)
	{
		program = Portinstall;
		process = installEngine->getProcess();
	}
	
	if(process != NULL)
	{
		startProcess(*process);
		delete process;
	}
	else
	{
		program = NoProgram;
		Dialogs::warnDlg("Error: No install/upgrade engines set!");
	}
}


void FrmPkgUpgradeController::startProcess(const UnixProcess & process)
{
	status = Running;
	
	QStringList args = process.arguments(true);
	
	QStrList slArgs;	
	for (QStringList::Iterator it = args.begin(); it != args.end(); ++it)
		slArgs.append((*it).latin1());
	
	terminalInterface->startProgram(args.first(), slArgs);
}


PkgUpgradeController *FrmPkgUpgradeController::getController()
{
	return controller;
}


FrmPkgUpgradeController::Status FrmPkgUpgradeController::getStatus()
{
	return status;
}

void FrmPkgUpgradeController::setInstallEngine(PkgUpgradeEngine * engine)
{
	installEngine = engine;
}

void FrmPkgUpgradeController::setUpgradeEngine(PkgUpgradeEngine * engine)
{
	upgradeEngine = engine;
}
