/***************************************************************************
*	Copyright (C) 2004 by karye												*
*	karye@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.,											*
*	59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.				*
***************************************************************************/

#include "common.h"              // for LogSingleton, SignalistSingleton, KUROO
#include "core/emerge.h"
#include "emerge.h"              // for Emerge, Emerge::m_rxCompletedInstalling
#include "global.h"              // for rxEmerge
#include "history.h"             // for History
#include "log.h"                 // for Log
#include "package.h"             // for EmergePackage, EmergePackageList
#include "portagedb.h"           // for KurooDB
#include "portagefiles.h"        // for PortageFiles
#include "queue.h"               // for Queue
#include "settings.h"            // for KurooConfig
#include "signalist.h"           // for Signalist
#include "statusbar.h"           // for KurooStatusBar
//#include <kdeversion.h>

#include <csignal>              // for kill, SIGCONT, SIGKILL, SIGSTOP
#include <cstddef>              // for NULL
#include <KAuth/Action>         // for Action
#include <KAuth/ExecuteJob>     // for ExecuteJob
#include <kguiitem.h>            // for KGuiItem
#include <kjob.h>                // for KJob
#include <klocalizedstring.h>    // for i18n
#include <kmessagebox.h>         // for information, questionYesNo, Yes
#include <kprocess.h>            // for KProcess, KProcess::OnlyStdoutChannel
#include <kuser.h>               // for KUser
#include <qbytearray.h>          // for QByteArray
#include <qdebug.h>              // for QDebug
#include <qglobal.h>             // for Q_UNUSED, qDebug, foreach, qWarning
#include <qlist.h>               // for QList
#include <qmetatype.h>           // for QVariantMap
#include <qobject.h>             // for operator<<, QObject
#include <qobjectdefs.h>         // for SIGNAL, emit
#include <qprocess.h>            // for QProcess, QProcess::ExitStatus, QPro...
#include <qregularexpression.h>  // for QRegularExpressionMatch, QRegularExp...
#include <qsemaphore.h>          // for QSemaphore, QSemaphoreReleaser
#include <qstring.h>             // for QString, operator+
#include <qstringlist.h>         // for QStringList
#include <qvariant.h>            // for QVariant

/**
 * @class Emerge
 * @short All Gentoo emerge command.
 *
 * Handles emerge, unmerge, check-for-updates, sync...
 */
Emerge::Emerge( QWidget* m_parent )
	: QObject( m_parent ), m_completedFlag( false ), m_importantMessagePackage( QString() ), m_packageMessage( QString() ), m_emergeOutputSemaphore( 1 )
{
	eProc = new KProcess();
	//use merged mode because emerge seems to output everything on stdout when there's any error (like a slot conflict)
	//including all the useful information
	eProc->setOutputChannelMode( KProcess::MergedChannels );
	ioRevdepRebuild = nullptr;
	eClean1 = nullptr;
	m_rxEscape.optimize();
	m_rxImportantPrefix.optimize();
	m_rxExclamationPrefix.optimize();
	m_rxCompletedInstalling.optimize();
	m_rxRegenerating.optimize();
	m_rxError.optimize();
	m_rxEmergeMessage.optimize();
}

Emerge::~Emerge()
{
	disconnect(eProc, SIGNAL(finished(int, QProcess::ExitStatus)));
	delete eProc;
	eProc = nullptr;
}

void Emerge::init( QWidget *parent )
{
	m_parent = parent;
	m_backupComplete = false;
	m_backingUp = false;
}

/**
 * Send text to stdIn.
 * @param text
 */
void Emerge::inputText( const QString& text )
{
	if ( eProc->state() == QProcess::Running ) {
		eProc->write( text.toUtf8() );
		LogSingleton::Instance()->writeLog( text, KUROO );
	}
	else
		LogSingleton::Instance()->writeLog( i18n( "Can not process input! Emerge is not running." ), ERROR );
}

/**
 * Abort the emerge process.
 * @return success
 */
auto Emerge::stop() -> bool
{
	if ( (eProc->state() == QProcess::Running && kill( eProc->processId(), SIGKILL ) ) || (m_emergeJob && m_emergeJob->kill()) ) {
		qWarning() << "Emerge process killed!";
		//delete m_emergeJob;	Forget about it here, but allow it to auto-delete itself so that we don't get a segfault in signal handling
		m_emergeJob = nullptr;
		return true;
	}
			return false;
}

/**
 * Checks if emerge is running.
 * @return true if emerging, false otherwise.
 */
auto Emerge::isRunning() const -> bool {
	qDebug() << "Emerge::isRunning eProc" << eProc << "m_emergeJob" << m_emergeJob;
	return eProc->state() == QProcess::Running || (m_emergeJob && !m_emergeJob->isSuspended());
}

/**
 * Emerge list of packages.
 * @param packageList
 */
void Emerge::queue( const QStringList& packageList )
{
	DEBUG_LINE_INFO;
	if( KurooConfig::backupPkg() && !m_backupComplete ) {
		m_backingUp = true;
		Emerge::quickpkg( packageList );
		return;
	}
	m_backupComplete = false;
	m_blocks.clear();
	m_importantMessage.clear();
	m_unmasked.clear();
	m_lastEmergeList = packageList;
	m_etcUpdateCount = 0;

	m_emergePackageList.clear();

	if ( KUser().isSuperUser() ) {
		eProc->close();
		eProc->clearProgram();
		*eProc << QStringLiteral("emerge") << QStringLiteral("--nospinner") << QStringLiteral("--columns") << QStringLiteral("--color=n");

		// Add emerge options and packages
		for( const QString& package : packageList ) {
			*eProc << package;
		}

		connect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupQueueProc);

		eProc->start();

		if ( eProc->state() == QProcess::NotRunning ) {
			LogSingleton::Instance()->writeLog( i18n("\nError: Emerge didn't start. "), ERROR );
			cleanupQueue();

			return;
		}
	} else {
		qDebug() << "Calling Emerge KAuth Job";
		KAuth::Action emerge( QStringLiteral("org.gentoo.kuroo.emerge") );
		emerge.setHelperId( QStringLiteral("org.gentoo.kuroo") );
		emerge.setTimeout( 2147483647 );		//  MAX(int32) Hopefully ~24 days is enough
		// This is bloody awful
		emerge.addArgument( QStringLiteral("ENV"), QProcessEnvironment::systemEnvironment().toStringList() );
		emerge.addArgument( QStringLiteral("args"), QStringList( {QStringLiteral("--columns"), QStringLiteral("--color=n"), QStringLiteral("--nospinner")} ) + packageList );

		if ( !emerge.isValid()) {
			KMessageBox::error( m_parent, i18n( "Invalid action \"%1\"", emerge.name() ) );
			cleanupQueue();
			return;
		}

		m_emergeJob = emerge.execute();
		connect( m_emergeJob, &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
		connect( m_emergeJob, &KAuth::ExecuteJob::finished, this, &Emerge::slotCleanupQueueJobFinished );
		m_emergeJob->start();
	}

	SignalistSingleton::Instance()->setKurooBusy( true );
	if( KurooConfig::enableEclean() && !skipHousekeeping())
		m_doeclean = true;
	if( KurooConfig::revdepEnabled() && !skipHousekeeping())
		m_dorevdeprebuild = true;
	m_pausable = true;

	LogSingleton::Instance()->writeLog( i18n( "Emerge %1 started...", packageList.join(u' ') ), KUROO );
	qDebug() << i18n( "Emerge %1 started...", packageList.join(u' ' ) );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Installing packages in queue..." ) );
	KurooStatusBar::instance()->startTimer();
}


/**
 * Pause the eproc
 */
void Emerge::slotPause()
{
	if ( !(eProc->state() == QProcess::Running) || !m_pausable ) {
		if ( m_emergeJob && m_emergeJob->isSuspended() ) {
			qDebug() << "Suspending m_emmergeJob";
			KurooStatusBar::instance()->pauseTimers();
			QueueSingleton::Instance()->pauseEmerge();
			m_emergeJob->suspend();
			m_isPaused = true;
		} else
			LogSingleton::Instance()->writeLog( i18n( "Emerge is not running or cannot be paused..." ), ERROR );
	} else {
		qDebug() << "Pausing eProc";
		KurooStatusBar::instance()->pauseTimers();
		QueueSingleton::Instance()->pauseEmerge();
		kill( eProc->processId(), SIGSTOP );
		m_isPaused = true;
	}
}

/**
 * Unpause the eproc
 */
void Emerge::slotUnpause()
{
	if ( !m_isPaused ) {
		LogSingleton::Instance()->writeLog( i18n( "Emerge is not paused..." ), ERROR );
	}
	KurooStatusBar::instance()->setProgressStatus(QStringLiteral("Emerge"), i18n( "Installing packages in queue..." ) );
	KurooStatusBar::instance()->unpauseTimers();
	QueueSingleton::Instance()->unpauseEmerge();
	if ( m_emergeJob && m_emergeJob->isSuspended() ) {
		qDebug() << "Resuming m_emergeJob";
		m_emergeJob->resume();
	} else {
		qDebug() << "Resuming eProc";
		kill( eProc->processId(), SIGCONT );
	}
	m_isPaused = false;
}

/**
 * Quickpkg something
 * @param packageList
 * @return success
 */
auto Emerge::quickpkg( const QStringList& packageList ) -> bool
{
	bool ret = false;
	m_lastEmergeList = packageList;

	if ( KUser().isSuperUser() ) {
		eProc->close();
		eProc->clearProgram();
		*eProc << QStringLiteral("quickpkg");

		// Add the packages
		for( const QString& package : packageList ) {
			*eProc << package;
		}

		eProc->start();
		connect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);

		if ( m_backingUp )
			connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotBackupComplete);
		else
			connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupQueueProc);

		m_backingUp = false;

		if ( eProc->state() == QProcess::NotRunning ) {
			LogSingleton::Instance()->writeLog( i18n( "\nError: Quickpkg didn't start. " ), ERROR );
			cleanupQueue();
		}
		else {
			LogSingleton::Instance()->writeLog( i18n( "Quickpkg %1 started...", packageList.join(u' ') ), KUROO );
			KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Quickpkg"), i18n( "Building binary packages..." ) );
			KurooStatusBar::instance()->startTimer();
			ret = true;
		}
	} else {
		KAuth::Action quickpkg( QStringLiteral("org.gentoo.kuroo.quickpkg") );
		quickpkg.setHelperId( QStringLiteral("org.gentoo.kuroo") );
		quickpkg.setTimeout( 60*60*1000 );	// Quickpkg should manage in an hour
		// This is bloody awful
		quickpkg.addArgument( QStringLiteral("ENV"), QProcessEnvironment::systemEnvironment().toStringList() );
		quickpkg.addArgument( QStringLiteral("args"), packageList );

		m_emergeJob = quickpkg.execute();
		connect( m_emergeJob, &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
		if ( m_backingUp )
			connect( m_emergeJob, &KAuth::ExecuteJob::finished, this, &Emerge::slotBackupJobFinished );
		else
			connect( m_emergeJob, &KAuth::ExecuteJob::finished, this, &Emerge::slotCleanupQueueJobFinished );
		m_emergeJob->start();
	}

	SignalistSingleton::Instance()->setKurooBusy( true );

	return ret;
}

/**
 * Emerge pretend list of packages.
 * @param packageList
 * @return success
 */
auto Emerge::pretend( const QStringList& packageList ) -> bool
{
	m_blocks.clear();
	m_importantMessage = QString();
	m_unmasked = QString();
	m_lastEmergeList = packageList;
	m_etcUpdateCount = 0;

	m_emergePackageList.clear();
	eProc->close();
	eProc->clearProgram();

	*eProc << QStringLiteral("emerge") << QStringLiteral("--nospinner") << QStringLiteral("--color=n") << QStringLiteral("--columns") << QStringLiteral("-pv");

	// Add argument for each of the attached packages
	for( const QString& package : packageList ) {
		*eProc << package;
	}

	eProc->start();
	connect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupPretend);
	SignalistSingleton::Instance()->setKurooBusy( true );
	LogSingleton::Instance()->writeLog( i18n( "Emerge pretend %1 started...", packageList.join(u' ') ), KUROO );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Checking installation queue..." ) );
	KurooStatusBar::instance()->startProgress();
	return true;
}

/**
 * Unmerge list of packages.
 * @param packageList
 * @return success
 */
auto Emerge::unmerge( const QStringList& packageList ) -> bool
{
	m_blocks.clear();
	m_etcUpdateCount = 0;
	m_importantMessage.clear();
	m_emergePackageList.clear();

	if ( KUser().isSuperUser() ) {
		eProc->close();
		eProc->clearProgram();
		*eProc << QStringLiteral("emerge") << QStringLiteral("--unmerge") << QStringLiteral("--color=n") << QStringLiteral("--nospinner");

		// Add argument for each of the attached packages
		for( const QString& package : packageList ) {
			*eProc << package;
		}

		eProc->start();
		connect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupUnmerge);
	} else {
		KAuth::Action unmerge( QStringLiteral("org.gentoo.kuroo.emerge") );
		unmerge.setHelperId( QStringLiteral("org.gentoo.kuroo") );
		unmerge.setTimeout( 60*60*1000 );	// Unmerge should take less than an hour right?
		// This is bloody awful
		unmerge.addArgument( QStringLiteral("ENV"), QProcessEnvironment::systemEnvironment().toStringList() );
		unmerge.addArgument( QStringLiteral("args"), QStringList( {QStringLiteral("--unmerge"), QStringLiteral("--color=n"), QStringLiteral("--nospinner")} ) + packageList );

		m_emergeJob = unmerge.execute();
		connect( m_emergeJob, &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
		connect( m_emergeJob, &KJob::finished, this, &Emerge::slotEmergeFinished );
		m_emergeJob->start();
		
	}
	SignalistSingleton::Instance()->setKurooBusy( true );
	LogSingleton::Instance()->writeLog( i18n( "\nUnmerge %1 started...", packageList.join(u' ') ), KUROO );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Uninstalling packages..." ) );
	KurooStatusBar::instance()->startProgress();
	return true;
}

/**
 * Synchronize Portage tree.
 * @return success
 */
void Emerge::sync()
{
	m_blocks.clear();
	m_importantMessage = QString();
	m_etcUpdateCount = 0;
	m_emergePackageList.clear();

	if ( KUser().isSuperUser() ) {
		eProc->close();
		eProc->clearProgram();
		*eProc << QStringLiteral("emerge") << QStringLiteral("--sync") /*<< "--quiet"*/ << QStringLiteral("--color=n") << QStringLiteral("--nospinner");
		eProc->setOutputChannelMode( KProcess::OutputChannelMode::ForwardedChannels );

		eProc->start();

		connect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupSync);
	} else {
		qDebug() << "Calling Sync KAuth Job";
		KAuth::Action sync( QStringLiteral("org.gentoo.kuroo.emerge") );
		sync.setHelperId( QStringLiteral("org.gentoo.kuroo") );
		sync.setTimeout( 60*60*24*1000 );		// Sync should finish within a day
		// This is bloody awful
		sync.addArgument( QStringLiteral("ENV"), QProcessEnvironment::systemEnvironment().toStringList() );
		sync.addArgument( QStringLiteral("args"), QStringList( {QStringLiteral("--sync"), QStringLiteral("--color=n"), QStringLiteral("--nospinner")} ) );

		m_emergeJob = sync.execute();
		connect( m_emergeJob, &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
		connect( m_emergeJob, &KJob::finished, this, &Emerge::slotEmergeFinished );
		m_emergeJob->start();
	}

	SignalistSingleton::Instance()->setKurooBusy( true );
	LogSingleton::Instance()->writeLog( i18n( "Emerge synchronize Portage Tree started..." ), KUROO );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Synchronizing portage tree..." ) );
	KurooStatusBar::instance()->setTotalSteps( KurooDBSingleton::Instance()->getKurooDbMeta( QStringLiteral("syncDuration") ).toInt() );

	//slotCleanupSync( 0, QProcess::ExitStatus::NormalExit );
}

void Emerge::slotEmergeOutput( const QVariantMap& data ) {
	QByteArray baline = data[QStringLiteral("stdout")].toByteArray();
	QList<QByteArray> lines = baline.split( '\n' );
	for ( const QByteArray& baline : std::as_const(lines) ) {
		QString line = QString::fromUtf8( baline );
		processEmergeOutputLine( line );
	}
}

void Emerge::slotEmergeFinished( KJob* job ) {
	DEBUG_LINE_INFO;
	if ( job->error() != 0 )
		qDebug() << "slotEmergeFnished job errors:" << job->errorString() << job->errorText();
	disconnect( dynamic_cast<KAuth::ExecuteJob*>(job), &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
	disconnect( job, &KAuth::ExecuteJob::finished, this, &Emerge::slotEmergeFinished );
	qDebug() << "deleting m_emergeJob";
	//delete m_emergeJob;	Forget about it here, but allow it to auto-delete itself so that we don't get a segfault in signal handling
	m_emergeJob = nullptr;
	cleanup();
}

/**
 * Check for updates of @world and system.
 * @return success
 */
auto Emerge::checkUpdates() -> bool
{
	m_blocks.clear();
	m_importantMessage = QString();
	m_etcUpdateCount = 0;
	m_emergePackageList.clear();

	eProc->close();
	eProc->clearProgram();
	*eProc << QStringLiteral("emerge") << QStringLiteral("-pvu") << QStringLiteral("--color=n") << QStringLiteral("--nospinner") << QStringLiteral("--columns");

	// Add deep if checked in gui
	if ( KurooConfig::updateDeep() )
		*eProc << QStringLiteral("-D");

	if( KurooConfig::updateNewUse() )
		*eProc << QStringLiteral("-N");

	if( KurooConfig::updateBuilddeps() )
		*eProc << QStringLiteral("--with-bdeps=y");

	*eProc << QStringLiteral("@world");

	eProc->start();
	if (eProc->state() == QProcess::NotRunning)
		return false;
	connect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupCheckUpdates);
	SignalistSingleton::Instance()->scanUpdatesStarted();
	LogSingleton::Instance()->writeLog( i18n( "Emerge check package updates started..." ), KUROO );
	//KurooStatusBar::instance()->startProgress();
	return true;
}

/**
 * Parse emerge process output for messages and packages.
 */
void Emerge::slotEProcOutput()
{
	// The semaphore didn't fix the lockup problem with emerging lots of tiny packages
	if ( ! m_emergeOutputSemaphore.tryAcquire() )
		return;
	// release on this function going out of scope
	QSemaphoreReleaser releaser( m_emergeOutputSemaphore );
	while ( eProc->canReadLine() ) {
		QByteArray baline = eProc->readLine();
		QString line = QString::fromUtf8(baline.chopped( 1 ));
		processEmergeOutputLine( line );
	}
}

void Emerge::processEmergeOutputLine( QString& line ) {
	int logDone = 0;

	////////////////////////////////////////////////////////////////////////////////
	// Cleanup emerge output - remove damn escape sequences
	////////////////////////////////////////////////////////////////////////////////
	line.remove( m_rxEscape );

	if ( line.isEmpty() )
		return;

	////////////////////////////////////////////////////////////////////////////
	// Parse out package and info
	////////////////////////////////////////////////////////////////////////////
	QRegularExpressionMatch match = KurooGlobal::rxEmerge->match( line );
	if ( match.hasMatch() ) {
		EmergePackage emergePackage;
		emergePackage.updateFlags = match.captured(1);
		emergePackage.package = match.captured(2);
		emergePackage.category = match.captured(3);
		emergePackage.name = match.captured(4);
		emergePackage.version = match.captured(5);
		emergePackage.installedVersion = match.captured(6);
		emergePackage.useFlags = match.captured(7).simplified();
		emergePackage.size = match.captured(8);
		//qDebug() << emergePackage.size;
		//Fix bug #3163827 order of queue items
		m_emergePackageList.prepend( emergePackage );
	}

	////////////////////////////////////////////////////////////////////////
	// Parse emerge output for correct log output
	////////////////////////////////////////////////////////////////////////
	QString lineLower = line.toLower();
	if ( lineLower.contains( m_rxImportantPrefix ) ) {

		if ( lineLower.contains( m_rxCompletedInstalling ) ) {
			m_completedFlag = true;
// 				m_importantMessagePackage = line.section( "Completed installing ", 1, 1 ).section( " ", 0, 0 ) + ":<br>";
		}
		else if ( lineLower.contains( m_rxRegenerating ) )
			m_completedFlag = false;

		if ( lineLower.contains( m_rxError ) ) {
			LogSingleton::Instance()->writeLog( line, ERROR );
			logDone++;
		}
		else
// 			if ( lineLower.contains( "etc-update" ) ) {
// 				LogSingleton::Instance()->writeLog( line, ERROR );
// 				logDone++;
//			}
// 			else
		if ( lineLower.contains( m_rxExclamationPrefix ) ) {
			LogSingleton::Instance()->writeLog( line, ERROR );
// 				m_importantMessage += line + "<br>";
			logDone++;
		}
		else if ( logDone == 0 && lineLower.contains( m_rxEmergeMessage ) ) {
			LogSingleton::Instance()->writeLog( line, EMERGE );
			logDone++;
		}

	}
	else if ( lineLower.contains( QStringLiteral("please tell me") ) ) {
		LogSingleton::Instance()->writeLog( line, EMERGE );
		logDone++;
	}
	else if ( lineLower.contains( QStringLiteral("root access required") ) ) {
		LogSingleton::Instance()->writeLog( i18n( "Root access required!" ), ERROR );
		logDone++;
	}
	else if ( lineLower.contains( QStringLiteral("no ebuilds to satisfy") ) ) {
		QString missingPackage = line.section(QStringLiteral("no ebuilds to satisfy "), 1, 1);
		LogSingleton::Instance()->writeLog( i18n( "There is no ebuilds to satisfy %1", missingPackage ), ERROR );
		logDone++;
	}
	else if ( lineLower.contains( QStringLiteral(" (masked by: ") ) )
		m_unmasked = line.section( QStringLiteral("- "), 1 ).section( u')', 0 );
	else if ( !m_unmasked.isEmpty() && line.startsWith( QStringLiteral("# ") ) )
		m_importantMessage += line.section( QStringLiteral("# "), 1, 1 ) + QStringLiteral("<br/>");


	// Save to kuroo.log for debugging
	LogSingleton::Instance()->writeLog( line, TOLOG );

	// Collect blocking lines
	if ( line.contains( QStringLiteral("is blocking") ) )
		m_blocks += line.section( QStringLiteral("[blocks B	 ]"), 1, 1 ).replace( u'>', QStringLiteral("&gt;") ).replace( u'<', QStringLiteral("&lt;") );

	// Collect output line if user want full log verbose
	if ( logDone == 0 )
		LogSingleton::Instance()->writeLog( line, EMERGE );

	countEtcUpdates( lineLower );
}

/**
 * Return einfo and ewarnings collected during emerge of the package.
 * @return message
 */
auto Emerge::packageMessage() -> const QString
{
	QString message = m_packageMessage;
	m_packageMessage = QString();
	return message;
}


//////////////////////////////////////////////////////////////////////////////
// Post emerge stuff
//////////////////////////////////////////////////////////////////////////////

/**
 * Cleanup when emerge process is finished
 * Stop statusbar progress and set kuroo to ready.
 * Present eventual messages collected during emerge to the user.
 */
void Emerge::cleanup()
{
	qDebug() << "cleaning up !";
	KurooStatusBar::instance()->stopTimer();
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Done." ) );
	qDebug() << "Emerge::cleanup setting busy false";
	SignalistSingleton::Instance()->setKurooBusy( false );
	Queue::addPackageList( m_emergePackageList );

	if ( m_etcUpdateCount ) {
		KMessageBox::information( m_parent, i18n( "There are %1 config-protected files that need merging", m_etcUpdateCount ), i18n( "Config protect" ) );
	}

	if ( !m_unmasked.isEmpty() ) {
		if ( KUser().isSuperUser() ) {
			askUnmaskPackage( m_unmasked );
		}
		else {
			KMessageBox::information( m_parent, i18n( "Unmasking packages hasn't yet been ported to deferred authorization; you can use that functionality if Kuroo is invoked as superuser.  Have a look at core/emerge.cpp#998 to contribute a patch." ), i18n( "Auto-unmasking packages" ) );
		}
	}

	/* if m_doclean then perform an eclean */
	if( m_doeclean && KurooConfig::ecleanDistfiles() ) {
		//TODO: convert to QStringBuilder
		QString ecleanCOMMAND;
		if (!eClean1)
			eClean1 = new KProcess();
		eClean1->close();
		eClean1->clearProgram();

		*eClean1 << QStringLiteral("eclean");
		ecleanCOMMAND = QStringLiteral("eclean ");
		if( !KurooConfig::ecleanTimeLimit().isEmpty() ) {
			*eClean1 << QStringLiteral("-t") << KurooConfig::ecleanTimeLimit();
			ecleanCOMMAND += QStringLiteral("-t");
			ecleanCOMMAND += KurooConfig::ecleanTimeLimit();
			ecleanCOMMAND += u' ';
		}
		if( KurooConfig::ecleanDestructive() )
		{
			*eClean1 << QStringLiteral("--destructive");
			ecleanCOMMAND += QStringLiteral("--destructive ");
		}
		*eClean1 << QStringLiteral("--nocolor");
		ecleanCOMMAND += QStringLiteral("--nocolor ");

		if( KurooConfig::ecleanFetchRestrict() && KurooConfig::ecleanDestructive())
		{
			*eClean1 << QStringLiteral("--fetch-restricted");
			ecleanCOMMAND += QStringLiteral("--fetch-restricted ");
		}
		*eClean1 << QStringLiteral("distfiles ");
		if( KurooConfig::ecleanSizeLimit().isEmpty() )
		{
			*eClean1 << QStringLiteral("-s") << KurooConfig::ecleanSizeLimit();
			ecleanCOMMAND += QStringLiteral("-s") + KurooConfig::ecleanSizeLimit() + u' ';
		}
		ecleanCOMMAND += QStringLiteral("distfiles");
		qDebug() << "ECLEAN COMMAND: " << ecleanCOMMAND;

		eClean1->setOutputChannelMode(KProcess::OnlyStdoutChannel);

		connect(eClean1, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(eClean1, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotEmergeDistfilesComplete);

		eClean1->start();

		SignalistSingleton::Instance()->setKurooBusy( true );
		LogSingleton::Instance()->writeLog( i18n( "\nEclean of distfiles started" ), KUROO );
		KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Cleaning distfiles..." ) );
		KurooStatusBar::instance()->startProgress();
		m_doeclean = false;
	} else if( m_dorevdeprebuild ) {
		if (!ioRevdepRebuild)
			ioRevdepRebuild = new KProcess();
		ioRevdepRebuild->close();
		ioRevdepRebuild->clearProgram();
		*ioRevdepRebuild << QStringLiteral("revdep-rebuild");
		*ioRevdepRebuild << QStringLiteral("--no-color");
		*ioRevdepRebuild << QStringLiteral("--ignore");

		connect(ioRevdepRebuild, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(ioRevdepRebuild, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotRevdepRebuildComplete);

		ioRevdepRebuild->setOutputChannelMode(KProcess::OnlyStdoutChannel);
		ioRevdepRebuild->start();

		SignalistSingleton::Instance()->setKurooBusy( true );
		LogSingleton::Instance()->writeLog( i18n( "\nRevdep-rebuild Running..." ), KUROO );
		KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Running revdep-rebuild..." ) );
		KurooStatusBar::instance()->startProgress();
		m_dorevdeprebuild = false;
	} /* end revdeprebuild section */
}

/**
 * Revdep-rebuild Complete
 */
void Emerge::slotRevdepRebuildComplete()
{
	disconnect(ioRevdepRebuild, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(ioRevdepRebuild, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotRevdepRebuildComplete);
	LogSingleton::Instance()->writeLog( i18n( "\nRevdep-rebuildcomplete" ), KUROO );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Done" ) );
	KurooStatusBar::instance()->stopTimer();
	SignalistSingleton::Instance()->setKurooBusy( false );
	delete ioRevdepRebuild; // cleanup that memory
}

/**
 * Run an eclean for packages if necessary
 */
void Emerge::slotEmergeDistfilesComplete()
{
	disconnect(eClean1, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eClean1, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotEmergeDistfilesComplete);
	LogSingleton::Instance()->writeLog( i18n( "\nEclean of distfiles complete" ), KUROO );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Done" ) );
	KurooStatusBar::instance()->stopTimer();
	SignalistSingleton::Instance()->setKurooBusy( false );
	delete eClean1; // cleanup that memory

	if( KurooConfig::ecleanDistfiles() )
	{
		//QTextCodec *codec = QTextCodec::codecForName("utf8");
		eClean2 = new KProcess();
		//eClean2->setUseShell( true, "/bin/bash" );
		//eClean2->setComm( KProcess::Communication( KProcess::Stdout | KProcess::MergedStderr | KProcess::Stdin ) );
		eClean2->close();
		*eClean2 << QStringLiteral("eclean");
		if( KurooConfig::ecleanTimeLimit().isEmpty() )
		{
			*eClean2 << QStringLiteral("-t") << KurooConfig::ecleanTimeLimit();
		}

		*eClean2 << QStringLiteral("--nocolor") << QStringLiteral("packages");

		eClean2->start( /*KProcess::OwnGroup, true*/ );
		connect(eClean2, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(eClean2, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotEClean2Complete);
		SignalistSingleton::Instance()->setKurooBusy( true );
		LogSingleton::Instance()->writeLog( i18n( "\nEclean of packages started" ), KUROO );
		KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Cleaning distfiles..." ) );
		KurooStatusBar::instance()->startProgress();
		return;
	}
	if( m_dorevdeprebuild ) {
		//QTextCodec *revdepcodec = QTextCodec::codecForName("utf8");
		ioRevdepRebuild = new KProcess();
		//ioRevdepRebuild->setUseShell( false, NULL );
		//ioRevdepRebuild->setComm( KProcess::Communication( KProcess::Stdout | KProcess::MergedStderr | KProcess::Stdin ) );
		ioRevdepRebuild->close();
		*ioRevdepRebuild << QStringLiteral("revdep-rebuild");
		*ioRevdepRebuild << QStringLiteral("--no-color");
		*ioRevdepRebuild << QStringLiteral("--ignore");
		ioRevdepRebuild->start();

		connect(ioRevdepRebuild, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(ioRevdepRebuild, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotRevdepRebuildComplete);
		SignalistSingleton::Instance()->setKurooBusy( true );
		LogSingleton::Instance()->writeLog( i18n( "\nRevdep-rebuild Running..." ), KUROO );
		KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Running revdep-rebuild..." ) );
		KurooStatusBar::instance()->startProgress();
		m_dorevdeprebuild = false;
	} /* end revdeprebuild section */
}

void Emerge::slotEClean2Complete(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)

	disconnect(eClean2, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eClean2, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotEClean2Complete);
	LogSingleton::Instance()->writeLog( i18n( "\nEcleaning packages complete" ), KUROO );
	KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Ready" ) );
	KurooStatusBar::instance()->stopTimer();
	SignalistSingleton::Instance()->setKurooBusy( false );
	delete eClean2;
	if( m_dorevdeprebuild )
	{
		//QTextCodec *revdepcodec = QTextCodec::codecForName("utf8");
		ioRevdepRebuild = new KProcess();
		//ioRevdepRebuild->setUseShell( false, NULL );
		//ioRevdepRebuild->setComm( KProcess::Communication( KProcess::Stdout | KProcess::MergedStderr | KProcess::Stdin ) );
		ioRevdepRebuild->close();
		*ioRevdepRebuild << QStringLiteral("revdep-rebuild");
		*ioRevdepRebuild << QStringLiteral("--no-color");
		*ioRevdepRebuild << QStringLiteral("--ignore");
		ioRevdepRebuild->start();
		connect(ioRevdepRebuild, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
		connect(ioRevdepRebuild, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotRevdepRebuildComplete);
		SignalistSingleton::Instance()->setKurooBusy( true );
		LogSingleton::Instance()->writeLog( i18n( "\nRevdep-rebuild Running..." ), KUROO );
		KurooStatusBar::instance()->setProgressStatus( QStringLiteral("Emerge"), i18n( "Running revdep-rebuild..." ) );
		KurooStatusBar::instance()->startProgress();
		m_dorevdeprebuild = false;
	} /* end revdeprebuild section */
}

void Emerge::slotBackupJobFinished( KJob* job ) {
	disconnect( dynamic_cast<KAuth::ExecuteJob*>(job), &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
	disconnect( job, &KAuth::ExecuteJob::finished, this, &Emerge::slotBackupJobFinished );
	History::updateStatistics();
	m_backupComplete = true;
	Emerge::queue( m_lastEmergeList );
	//delete m_emergeJob;	Forget about it here, but allow it to auto-delete itself so that we don't get a segfault in signal handling
	m_emergeJob = nullptr;
}

/**
 * Disconnect signals and signal termination to main thread.
 */
void Emerge::slotBackupComplete(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)

	disconnect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotBackupComplete);
	History::updateStatistics();
	m_backupComplete = true;
	Emerge::queue(m_lastEmergeList);
}

void Emerge::slotCleanupQueueJobFinished( KJob* job ) {
	DEBUG_LINE_INFO;
	qDebug() << job->error() << job->errorString() << job->errorText();
	if (job->error() != 0 ) {
		KMessageBox::error( m_parent, i18n("Error %1: \"%2\" \"%3\" running KAuth job", job->error(), job->errorString(), job->errorText() ) );
	}
	disconnect( dynamic_cast<KAuth::ExecuteJob*>(job), &KAuth::ExecuteJob::newData, this, &Emerge::slotEmergeOutput );
	disconnect( job, &KAuth::ExecuteJob::finished, this, &Emerge::slotCleanupQueueJobFinished );

	//delete m_emergeJob;	Forget about it here, but allow it to auto-delete itself so that we don't get a segfault in signal handling
	m_emergeJob = nullptr;
	cleanupQueue();
}

/**
 * Disconnect signals and signal termination to main thread.
 */
void Emerge::slotCleanupQueueProc(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)
	DEBUG_LINE_INFO;
	disconnect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupQueueProc);

	cleanupQueue();
}

void Emerge::cleanupQueue()
{
	cleanup();
	History::updateStatistics();
	m_pausable = false;
	Q_EMIT signalEmergeComplete();
}

/**
 * Disconnect signals and signal termination to main thread.
 */
void Emerge::slotCleanupPretend(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)

	disconnect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupPretend);
	cleanup();
}

/**
 * Disconnect signals and signal termination to main thread.
 * @param proc
 */
void Emerge::slotCleanupUnmerge(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)

	disconnect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupUnmerge);
	cleanup();
}

/**
 * Disconnect signals and signal termination to main thread.
 */
void Emerge::slotCleanupSync(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)

	disconnect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupSync);
	cleanup();
}

/**
 * Disconnect signals and signal termination to main thread.
 */
void Emerge::slotCleanupCheckUpdates(int exitCode, QProcess::ExitStatus status)
{
	Q_UNUSED(exitCode)
	Q_UNUSED(status)

	disconnect(eProc, &KProcess::readyReadStandardOutput, this, &Emerge::slotEProcOutput);
	disconnect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &Emerge::slotCleanupCheckUpdates);

	KurooStatusBar::instance()->stopTimer();
	//KurooStatusBar::instance()->setProgressStatus( "Emerge", i18n( "Done." ) );
	SignalistSingleton::Instance()->scanUpdatesComplete();

	//TODO: convert to QStringBuilder
	if ( !m_blocks.isEmpty() ) {
		if ( !m_importantMessage.isEmpty() )
			m_importantMessage += QStringLiteral("<br/>");
		m_importantMessage += m_blocks.join(QStringLiteral("<br/>"));
	}

	/*if ( !m_importantMessage.isEmpty() )
	Message::instance()->prompt( i18n("Important"), i18n("Please check log for more information!"), m_importantMessage );*/
}

/**
 * Check if package is masked, if so ask to unmask.
 * @param packageKeyword	concatenated string with package and mask
 */
void Emerge::askUnmaskPackage( const QString& packageKeyword )
{
	QString package = packageKeyword.section( QStringLiteral("(masked by: "), 0, 0 );
	QString keyword = ( packageKeyword.section( QStringLiteral("(masked by: "), 1, 1) ).section( QStringLiteral(" keyword"), 0, 0 );

	//TODO: convert to QStringBuilder
	if ( packageKeyword.contains( QStringLiteral("missing keyword") ) ) {
		m_importantMessage += i18n( "%1 is not available on your architecture %2!<br/><br/>", package, KurooConfig::arch() );
		m_importantMessage += i18n( "<b>missing keyword</b> means that the application has not been tested on your architecture yet.<br/>"
				"Ask the architecture porting team to test the package or test it for them and report your "
				"findings on Gentoo bugzilla website." );
		KMessageBox::information( m_parent, QStringLiteral("<qt>") + m_importantMessage + QStringLiteral("</qt>"), i18n( "Missing Keyword" ) );
	}
	else if ( keyword.contains( QStringLiteral("-*") ) ) {
		m_importantMessage += i18n( "%1 is not available on your architecture %2!<br/><br/>", package, KurooConfig::arch() );
		m_importantMessage += i18n( "<br/><b>-* keyword</b> means that the application does not work on your architecture.<br/>"
					"If you believe the package does work file a bug at Gentoo bugzilla website." );
		KMessageBox::information( m_parent, QStringLiteral("<qt>") + m_importantMessage + QStringLiteral("</qt>"), i18n( "-* Keyword" ) );
	}
	else if ( !keyword.contains( KurooConfig::arch() ) && keyword.contains( QStringLiteral("package.mask") ) ) {
		LogSingleton::Instance()->writeLog( i18n( "Please add package to \"package.unmask\"." ), ERROR );

		if (KMessageBox::questionTwoActions( m_parent,
						i18n( "<qt>Cannot emerge masked package!<br/>Do you want to unmask <b>%1</b>?</qt>", package ),
						i18n( "Information" ), KGuiItem( i18n( "Unmask" ) ), KStandardGuiItem::cancel() ) == KMessageBox::PrimaryAction) {
			//TODO: Port to KAuth
			KurooDBSingleton::Instance()->setPackageUnMasked( KurooDBSingleton::Instance()->packageId( package ) );
			PortageFiles::savePackageUserUnMask();
			disconnect( PortageFilesSingleton::Instance(), &PortageFiles::signalPortageFilesChanged, this, &Emerge::slotTryEmerge );
			connect( PortageFilesSingleton::Instance(), &PortageFiles::signalPortageFilesChanged, this, &Emerge::slotTryEmerge );
		}
	}
	else {
		LogSingleton::Instance()->writeLog( i18n( "Please add package to \"package.accept_keywords\"." ), ERROR );

		if (KMessageBox::questionTwoActions( m_parent,
						i18n( "<qt>Cannot emerge testing package!<br/>Do you want to unmask <b>%1</b>?</qt>", package ),
						i18n( "Information" ), KGuiItem( i18n( "Unmask" ) ), KStandardGuiItem::cancel() ) == KMessageBox::PrimaryAction) {
			//TODO: Port to KAuth
			KurooDBSingleton::Instance()->setPackageUnTesting( KurooDBSingleton::Instance()->packageId( package ) );
			PortageFiles::savePackageKeywords();
			disconnect( PortageFilesSingleton::Instance(), &PortageFiles::signalPortageFilesChanged, this, &Emerge::slotTryEmerge );
			connect( PortageFilesSingleton::Instance(), &PortageFiles::signalPortageFilesChanged, this, &Emerge::slotTryEmerge );
		}
	}
}

/**
 * After package is auto-unmasked try remerging the list again to find next package to unmask.
 */
void Emerge::slotTryEmerge()
{
	pretend( m_lastEmergeList );
	disconnect( PortageFilesSingleton::Instance(), &PortageFiles::signalPortageFilesChanged, this, &Emerge::slotTryEmerge );
}

/**
 * count etc-files to merge.
 * @param line line
 * @return success
 */
auto Emerge::countEtcUpdates( const QString& line ) -> bool
{
	// count etc-files to merge, the string to match will be either
	//"config file '%s' needs updating. or
	//"%d config files in '%s' need updating.
	if ( line.contains( QStringLiteral("needs updating") ) ) {
		m_etcUpdateCount = 1;
		return true;
	}
	if ( line.contains( QStringLiteral(" need updating") ) ) {
		QString tmp = line.section( QStringLiteral("config file"), 0, 0 );
		QRegularExpressionMatch match = m_rxDigits.match( tmp );
		if ( match.hasMatch() )
			m_etcUpdateCount += ( match.captured(1) ).toInt();

		return true;
	}
			return false;
}

