/***************************************************************************
*	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 <klocalizedstring.h>         // for i18n
#include <kmessagebox.h>              // for messageBox, warningYesNo, Cancel
#include <kprocess.h>                 // for KProcess, KProcess::OnlyStdoutC...
#include <kurllabel.h>                // for KUrlLabel
#include <kuser.h>                    // for KUser
#include <qboxlayout.h>               // for QVBoxLayout
#include <qbuttongroup.h>             // for QButtonGroup
#include <qbytearray.h>               // for QByteArray
#include <qcheckbox.h>                // for QCheckBox
#include <qcombobox.h>                // for QComboBox
#include <qdebug.h>                   // for QDebug
#include <qdialogbuttonbox.h>         // for QDialogButtonBox, QDialogButton...
#include <qfile.h>                    // for QFile
#include <qfont.h>                    // for QFont
#include <qglobal.h>                  // for foreach, qCritical
#include <qgroupbox.h>                // for QGroupBox
#include <qiodevice.h>                // for QIODevice, QIODevice::ReadOnly
#include <qlabel.h>                   // for QLabel
#include <qlist.h>                    // for QList<>::iterator, QList
#include <qnamespace.h>               // for ItemIsUserCheckable, Checked
#include <qprocess.h>                 // for QProcess, QProcess::ExitStatus
#include <qpushbutton.h>              // for QPushButton
#include <qradiobutton.h>             // for QRadioButton
#include <qregexp.h>                  // for QRegExp
#include <qregularexpression.h>       // for QRegularExpressionMatch, QRegul...
#include <qtabwidget.h>               // for QTabWidget
#include <qtextbrowser.h>             // for QTextBrowser
#include <qtextstream.h>              // for QTextStream
#include <qtreewidget.h>              // for QTreeWidgetItem, QTreeWidget
#include <qtreewidgetitemiterator.h>  // for QTreeWidgetItemIterator
#include <qwidget.h>                  // for QWidget

#include "common.h"                   // for KurooDBSingleton, PortageFilesS...
#include "dependencyview.h"           // for DependencyView
#include "emerge.h"                   // for Emerge
#include "global.h"                   // for rxEmerge
#include "log.h"                      // for Log
#include "packageinspector.h"
#include "packagelistitem.h"          // for PackageListItem
#include "packageversion.h"           // for PackageVersion
#include "portage.h"                  // for Portage
#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 "versionview.h"              // for VersionView

/**
* @class PackageInspector
* @short The package Inspector dialog for viewing and editing all advanced package settings.
*
* Builds a tabbed-dialog to view all relevant info for current package.
* This dialog is used both in Packages view and Queue view.
*/
PackageInspector::PackageInspector( QWidget *parent ) : QDialog( parent ),
		m_versionSettingsChanged( false ), m_useSettingsChanged( false ),
		m_isVirginState( true ), m_category( QString() ),
		m_package( QString() ), m_hardMaskComment( QString() ),
		m_portagePackage( nullptr ), m_stabilityBefore( 0 ), m_versionBefore( QString() )
{
	/*QDialog::Swallow, 0,, i18n( "Package details" ), false, i18n( "Package details" ),
			QDialog::Ok | QDialog::Apply | QDialog::Cancel, QDialog::Apply, false ),*/
	auto *mainWidget = new QWidget(this);
	auto *mainLayout = new QVBoxLayout;
	setLayout(mainLayout);
	mainLayout->addWidget(mainWidget);
	setupUi(mainWidget);
	QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
	okButton->setDefault(true);
	okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
	connect(buttonBox, &QDialogButtonBox::accepted, this, &PackageInspector::accept);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &PackageInspector::reject);
	adjustSize();

	// Get use flag description @todo: load local description
	loadUseFlagDescription();

	// Shortcuts for browsing packages
	connect(pbPrevious, &QPushButton::clicked, this, &PackageInspector::slotPreviousPackage);
	connect(pbNext, &QPushButton::clicked, this, &PackageInspector::slotNextPackage);

	// Refresh files when changing version
	connect(cbVersionsEbuild, &QComboBox::textActivated, this, &PackageInspector::slotLoadEbuild);
	connect(cbVersionsDependencies, &QComboBox::textActivated, this, &PackageInspector::slotLoadDependencies);
	connect(cbVersionsInstalled, &QComboBox::textActivated, this, &PackageInspector::slotLoadInstalledFiles);
	connect(cbVersionsUse, &QComboBox::textActivated, this, &PackageInspector::slotLoadUseFlags);

	// Load files only if tabpage is open
	connect(inspectorTabs, &QTabWidget::currentChanged, this, &PackageInspector::slotRefreshTabs);

	m_stabilityButtonGroup = new QButtonGroup();
	m_stabilityButtonGroup->addButton(rbStable, 0);
	m_stabilityButtonGroup->addButton(rbTesting, 1);
	m_stabilityButtonGroup->addButton(rbMasked, 2);
	m_stabilityButtonGroup->addButton(rbVersionsSpecific, 3);
	// Toggle between all 4 stability version
	connect(m_stabilityButtonGroup, &QButtonGroup::idClicked, this, &PackageInspector::slotSetStability);

	// Activate specific version menu
	connect(cbVersionsSpecific, &QComboBox::textActivated, this, &PackageInspector::slotSetSpecificVersion);

	connect(infoHardMasked, &KUrlLabel::leftClickedUrl, this, &PackageInspector::slotHardMaskInfo);

	if ( KUser().isSuperUser() ) {
		m_useItemFlags = Qt::ItemIsUserCheckable;
		connect(useView, &QTreeWidget::itemClicked, this, &PackageInspector::slotSetUseFlags);
	} else {
		m_useItemFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
	}

	connect(pbUse, &QPushButton::clicked, this, &PackageInspector::slotCalculateUse);

	// Listen to Queue and World checkboxes
	connect(cbQueue, &QCheckBox::clicked, this, &PackageInspector::slotQueue);
	connect(cbWorld, &QCheckBox::clicked, this, &PackageInspector::slotWorld);
}

PackageInspector::~PackageInspector()
= default;

/**
* Update the Inspector gui with new versions and data.
*/
void PackageInspector::updateVersionData()
{
	// Clear dropdown menus
	versionsView->clear();
	cbVersionsEbuild->clear();
	cbVersionsDependencies->clear();
	cbVersionsInstalled->clear();
	cbVersionsUse->clear();
	cbVersionsSpecific->clear();

	// Iterate sorted version list
	QString installedVersion;
	QStringList versionList, versionInstalledList;
	QStringList versionDataList = m_portagePackage->versionDataList();

	QStringList::iterator it;

	for (it = versionDataList.begin(); it != versionDataList.end(); ++it)
	{
		// Parse out version and data
		QString version = *it;
		++it;
		QString stability = *it;
		++it;
		QString size = *it;
		++it;

		// Collect installed version in "Installed files" tab
		bool isInstalled = false;
		if ( *it == u'1' ) {
			isInstalled = true;
			installedVersion = version;
			versionInstalledList.prepend( version );
		}
		else
			isInstalled = false;

		// Collect in inverse order to fill dropdown menus correctly
		versionList.prepend( version );

		// Insert version in versionview
		versionsView->insertItem( version, stability, size, isInstalled );
	}

	// Insert all versions in dropdown-menus
	cbVersionsEbuild->addItems( versionList );
	cbVersionsDependencies->addItems( versionList );
	cbVersionsUse->addItems( versionList );
	cbVersionsSpecific->addItems( versionList );

	// Set active version in Inspector dropdown menus
	if ( !versionInstalledList.isEmpty() ) {
		// Enable installed tab
		inspectorTabs->setTabEnabled( 5, true );
		cbVersionsInstalled->addItems( versionInstalledList );
		int installed = cbVersionsInstalled->findText(installedVersion);
		cbVersionsInstalled->setCurrentIndex( installed );

		int current = cbVersionsEbuild->findText(installedVersion);
		cbVersionsEbuild->setCurrentIndex( current );
		cbVersionsDependencies->setCurrentIndex( current );
		cbVersionsUse->setCurrentIndex( current );
	} else {
		// Disable installed tab
		inspectorTabs->setTabEnabled( 5, false );

		if( !m_portagePackage->emergeVersion().isEmpty() ) {
			int current =  cbVersionsEbuild->findText( m_portagePackage->emergeVersion() );
			cbVersionsEbuild->setCurrentIndex( current );
			cbVersionsDependencies->setCurrentIndex( current );
			cbVersionsUse->setCurrentIndex( current );
		}
	}

	if ( !m_portagePackage->emergeVersion().isEmpty() ) {
		int current = cbVersionsSpecific->findText( m_portagePackage->emergeVersion() );
		cbVersionsSpecific->setCurrentIndex( current );
		versionsView->usedForInstallation( m_portagePackage->emergeVersion() );
	}

	// Toggle checkboxes if package in World and Queue
	cbWorld->setChecked(m_portagePackage->isInWorld());
	cbQueue->setChecked(QueueSingleton::Instance()->isQueued(m_portagePackage->id()));
}

/**
* Add/remove package in World profile using checkbox.
*/
void PackageInspector::slotWorld()
{
	if ( cbWorld->isChecked() )
		PortageSingleton::Instance()->appendWorld( QStringList( m_category + u'/' + m_package ) );
	else
		PortageSingleton::Instance()->removeFromWorld( QStringList( m_category + u'/' + m_package ) );
}

/**
* Add/remove package in Queue using checkbox.
*/
void PackageInspector::slotQueue()
{
	if ( cbQueue->isChecked() )
		Queue::addPackageIdList( QStringList( m_portagePackage->id() ) );
	else
		Queue::removePackageIdList( QStringList( m_portagePackage->id() ) );

	// If user removes last package in Queue, disable the Inspector
	if ( m_view == VIEW_QUEUE && QueueSingleton::Instance()->size() == 1 ) {
		inspectorTabs->setDisabled( true );
		cbQueue->setDisabled( true );
		cbWorld->setDisabled( true );
	}
}

/**
* Activate Inspector with current package.
* @param portagePackage
*/
void PackageInspector::edit( PackageListItem* portagePackage, int view )
{
	m_view = view;
	m_portagePackage = portagePackage;
	m_package = m_portagePackage->name();
	m_category = m_portagePackage->category();
	updateVersionData();

	// Actions that need superuser privileges
	if ( !KUser().isSuperUser() ) {
		buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false );
		buttonBox->button(QDialogButtonBox::Apply)->setToolTip( i18n( "This functionality is not ported to KAuth. Please contribute to the porting, or run as superuser." ) );
		groupSelectStability->setEnabled( false );
		groupSelectStability->setToolTip( i18n( "This functionality is not ported to KAuth. Please contribute to the porting, or run as superuser." ) );
		//useView->setEnabled( false );
		//cbWorld->setEnabled( false );	//this has been converted to KAuth
	}
	else {
		inspectorTabs->setEnabled( true );
		cbQueue->setEnabled( true );
		cbWorld->setEnabled( true );
	}

	// Disabled editing when package is in Queue and kuroo is emerging
	if ( m_portagePackage->isQueued() && EmergeSingleton::Instance()->isRunning() ) {
		inspectorTabs->setTabEnabled( 0, false );
		inspectorTabs->setTabEnabled( 1, false );
	} else {
		inspectorTabs->setTabEnabled( 0, true );
		inspectorTabs->setTabEnabled( 1, true );
	}

	// Is it first time we load this package
	if ( m_id != m_portagePackage->id() ) {
		m_id = m_portagePackage->id();
		m_isVirginState = true;
	}
	else
		m_isVirginState = false;

	// Construct header text
	//headerFrame->setPaletteBackgroundColor( colorGroup().highlight() );
	//TODO: convert to QStringBuilder
	package->setText( QStringLiteral("<b>" //<font color=#" + GlobalSingleton::Instance()->fgHexColor()+ ">"
					"<font size=+1>") + m_package + QStringLiteral("</font>&nbsp;"
					"(") + m_category.section( u'-', 0, 0 ) + u'/' + m_category.section( u'-', 1, 1 ) + QStringLiteral(")</b>")/*"</font>"*/);
	description->setText( m_portagePackage->description() );

	showSettings();
	slotRefreshTabs();

	// Enable/disable shortcuts buttons if first or last package
	if ( m_portagePackage->isFirstPackage() )
		pbPrevious->setDisabled( true );
	else
		pbPrevious->setDisabled( false );

	if ( m_portagePackage->isLastPackage() )
		pbNext->setDisabled( true );
	else
		pbNext->setDisabled( false );

	show();
}

/**
* Ask to save settings if user has changed settings.
* If no saving the changes rollback to the latest radiobutton setting.
*/
void PackageInspector::askApplySettings()
{
	if ( m_versionSettingsChanged || m_useSettingsChanged ) {
		switch( KMessageBox::warningTwoActions( this, i18n( "<qt>Settings are changed!<br>Do you want to save them?</qt>"),
										i18n("Saving settings"), KStandardGuiItem::save(), KStandardGuiItem::cancel()) ) {
		case KMessageBox::PrimaryAction:
		case KMessageBox::Ok:
		case KMessageBox::Continue:
			slotApply();
			break;

		case KMessageBox::SecondaryAction:
		case KMessageBox::Cancel:
			rollbackSettings();
		}
	}
	m_versionSettingsChanged = false;
	m_useSettingsChanged = false;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Buttons slots
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
* Make previous package in package view current - for easier browsing.
* Ask to save settings if user has changed settings.
* If no saving the changes rollback to the latest radiobutton setting.
*/
void PackageInspector::slotPreviousPackage()
{
	askApplySettings();
	Q_EMIT signalNextPackage( true );
}

/**
* Make next package in package view current - for easier browsing.
* Ask to save settings if user has changed settings.
* If no saving the changes rollback to the latest radiobutton setting.
*/
void PackageInspector::slotNextPackage()
{
	askApplySettings();
	Q_EMIT signalNextPackage( false );
}

/**
* Save the stability setting for this package.
* @fixme: save only changed tables.
*/
void PackageInspector::slotApply()
{
	DEBUG_LINE_INFO;

	if ( !KUser().isSuperUser() ) {
		KMessageBox::information( this, i18n( "This functionality hasn't been ported to KAuth. Pleas help with the porting or run kuroo as superuser." ) );
		return;
	}

	if ( m_versionSettingsChanged ) {
		PortageFiles::savePackageKeywords();
		PortageFiles::savePackageUserMask();
		PortageFiles::savePackageUserUnMask();

		// Check if this version is in updates. If not add it! (Only for packages in @world).
		if ( PortageSingleton::Instance()->isInWorld( m_category + u'/' + m_package ) )
			PortageSingleton::Instance()->checkUpdates( m_id, versionsView->updateVersion(), versionsView->hasUpdate() );
	}

	if ( m_useSettingsChanged ) {
		// Get use flags
		QStringList useList, pretendUseList;
		QTreeWidgetItemIterator it( useView );
		while ( *it ) {
			QString useFlag = (*it)->text(0);
			pretendUseList += useFlag;
			//TODO: extract / convert to and inline static const QRegularExpression
			static const QRegularExpression leadingPlusOrTrailingStar( QStringLiteral("^\\+|\\*$") );
			if ( !useFlag.contains( QStringLiteral("^\\(|\\)$") ) )
				useList += useFlag.remove( leadingPlusOrTrailingStar );
			++it;
		}

		// Store in db and save to file
		if ( !useList.isEmpty() ) {

			//set use flags to nothing to check if a string is necessary in package.use
			KurooDBSingleton::Instance()->setPackageUse( m_id, QString() );
			PortageFiles::savePackageUse();

			//recalculate use flags
			m_pretendUseLines.clear();
			eProc = new KProcess();
			eProc->setOutputChannelMode(KProcess::OnlyStdoutChannel);
			*eProc << QStringLiteral("emerge") << QStringLiteral("--columns") << QStringLiteral("--nospinner") << QStringLiteral("--color=n") << QStringLiteral("-pv") << m_category + u'/' + m_package;
			m_useList = useList;
			connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &PackageInspector::slotParsePackageUse);
			connect(eProc, &KProcess::readyReadStandardOutput, this, &PackageInspector::slotCollectPretendOutput);
			eProc->start();
			SignalistSingleton::Instance()->setKurooBusy( true );

			if ( eProc->state() != QProcess::Running ) {
				LogSingleton::Instance()->writeLog( i18n( "\nError: Could not calculate use flag for package %1/%2.",
														m_category, m_package ), ERROR );
			}

		}
	}

	buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false );
	m_versionSettingsChanged = false;
	m_useSettingsChanged = false;
	m_isVirginState = true;
}

/**
* Cancel and rollback to old settings.
*/
void PackageInspector::slotCancel()
{
	DEBUG_LINE_INFO;

	rollbackSettings();
	m_versionSettingsChanged = false;
	accept();
}

void PackageInspector::slotOk()
{
	DEBUG_LINE_INFO;

	slotApply();
	accept();
}

/**
* Rollback settings to state before changed by user.
*/
void PackageInspector::rollbackSettings()
{
	if ( m_versionSettingsChanged ) {
		slotSetStability( m_stabilityBefore  );

		if ( m_stabilityBefore == 3 )
			slotSetSpecificVersion( m_versionBefore );
	}
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Package masking editing
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
* Create hardmask info link.
*/
void PackageInspector::showHardMaskInfo()
{
	const QStringList hardMaskInfo = KurooDBSingleton::Instance()->packageHardMaskInfo( m_id );

	if ( !hardMaskInfo.isEmpty() ) {
		QFont font;
		font.setBold( true );
		infoHardMasked->setFont( font );
		infoHardMasked->setHighlightedColor( Qt::red );
		infoHardMasked->setText( i18n("Click for hardmask info!") );

		//TODO: convert to QStringBuilder
		m_hardMaskComment =
				QStringLiteral("<font size=\"+2\">") + m_package + QStringLiteral("</font> "
				"(") + m_category.section( u'-', 0, 0 ) + u'/' + m_category.section( u'-', 1, 1 ) + QStringLiteral(")<br><br>") +
				hardMaskInfo.last() + QStringLiteral("<br><br>"
				"Hardmask rule: <i>\"") + hardMaskInfo.first() + QStringLiteral("\"</i>");
	}
	else
		infoHardMasked->setText( QString() );
}

/**
* Show gentoo devs reason for hardmasking this package/versions.
*/
void PackageInspector::slotHardMaskInfo()
{
	KMessageBox::information( this, m_hardMaskComment,
							i18n( "%1/%2 hardmask info!", m_category, m_package ) );
}

/**
* Stability choice for versions - enable the right radiobutton.
* Priority is: specific version >> unmask package >> untest package >> stable package.
*/
void PackageInspector::showSettings()
{
	// Get user mask specific version
	QStringList userMaskVersions = KurooDBSingleton::Instance()->packageUserMaskAtom( m_id );
	QString userMaskVersion = userMaskVersions.isEmpty() ? QString() : userMaskVersions.first();

	// Enable stability radiobutton
	if ( !userMaskVersion.isEmpty() ) {
		rbVersionsSpecific->setChecked( true );
		cbVersionsSpecific->setDisabled( false );
		int current = cbVersionsSpecific->findText( m_portagePackage->emergeVersion() );
		cbVersionsSpecific->setCurrentIndex( current );
	} else {
		if ( KurooDBSingleton::Instance()->isPackageUnMasked( m_id ) )
			rbMasked->setChecked( true );
		else
			if ( KurooDBSingleton::Instance()->isPackageUnTesting( m_id ) )
				rbTesting->setChecked( true );
			else
				rbStable->setChecked( true );
	}

	// Stability settings before user has changed it
	if ( m_isVirginState ) {
		m_stabilityBefore = m_stabilityButtonGroup->checkedId();
		m_versionBefore = userMaskVersion;
	}

	showHardMaskInfo();

	// Reset the apply button for new package
	if ( !m_versionSettingsChanged )
		buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false );
}

/**
* Apply stability settings from radiobuttons.
* @param rbStability the selected radiobutton
*/
void PackageInspector::slotSetStability( int rbStability )
{
	switch ( rbStability ) {

		// User wants only stable package
	case 0 :
		cbVersionsSpecific->setDisabled( true );

		// Clear package from package.keywords, package.unmask and package.mask
		KurooDBSingleton::Instance()->clearPackageUnTesting( m_id );
		KurooDBSingleton::Instance()->clearPackageUnMasked( m_id );
		KurooDBSingleton::Instance()->clearPackageUserMasked( m_id );
		KurooDBSingleton::Instance()->clearPackageAvailable( m_id );

		m_portagePackage->resetDetailedInfo();
		Q_EMIT signalPackageChanged();
		break;

		// User wants only testing package
	case 1 :
		cbVersionsSpecific->setDisabled( true );

		// Clear package from package.unmask and package.mask
		KurooDBSingleton::Instance()->clearPackageUnMasked( m_id );
		KurooDBSingleton::Instance()->clearPackageUserMasked( m_id );

		KurooDBSingleton::Instance()->setPackageUnTesting( m_id );
		m_portagePackage->resetDetailedInfo();
		Q_EMIT signalPackageChanged();
		break;

		// User wants only hardmasked package
	case 2 :
		cbVersionsSpecific->setDisabled( true );

		// Clear package from package.keywords and package.mask
		KurooDBSingleton::Instance()->clearPackageUserMasked( m_id );

		KurooDBSingleton::Instance()->setPackageUnTesting( m_id );
		KurooDBSingleton::Instance()->setPackageUnMasked( m_id );
		m_portagePackage->resetDetailedInfo();
		Q_EMIT signalPackageChanged();
		break;

		// User wants only specific version
	case 3 :
		cbVersionsSpecific->setDisabled( false );
		break;

	default:
		qDebug() << LINE_INFO << "This switch needs another case!!";
		break;
	}
	PortageFiles::savePackageUserUnMask();
	PortageFiles::savePackageKeywords();

	buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true );
	m_versionSettingsChanged = true;
}

/**
* User has selected a specific version to unmask and wants no higher version.
* @param version
*/
void PackageInspector::slotSetSpecificVersion( const QString& version )
{
	buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true );
	m_versionSettingsChanged = true;

	KurooDBSingleton::Instance()->setPackageUnTesting( m_id );
	KurooDBSingleton::Instance()->setPackageUserMasked( m_id );
	KurooDBSingleton::Instance()->setPackageUnMasked( m_id, version );

	m_portagePackage->resetDetailedInfo();
	Q_EMIT signalPackageChanged();
}

/**
* Toggle use flag state to add or remove.
* @param useItem
*/
void PackageInspector::slotSetUseFlags( QTreeWidgetItem* useItem/*, int column */)
{
	if ( !useItem ) //is it possible ?
		return;

	QString use = useItem->text( 0 );

	// Break if no checkbox
	if ( use.contains(u'(') )
		return;

	static const QRegularExpression leadingPlus(QStringLiteral("^\\+"));
	static const QRegularExpression leadingMinus(QStringLiteral("^\\-"));
	switch ( useItem->checkState(0) ) {
	case ( Qt::Unchecked ) :
		if ( useItem->text(0).startsWith( u'+' ) )
			useItem->setText( 0, use.replace( leadingPlus, QStringLiteral("-") ) );
		if ( !useItem->text(0).startsWith( u'-' ) )
			useItem->setText( 0, use.insert( 0, u'-' ) );
		break;
	case ( Qt::Checked ) :
		if ( useItem->text(0).startsWith( u'-' ) )
			useItem->setText( 0, use.replace( leadingMinus, QStringLiteral("+") ) );
		if ( !useItem->text(0).startsWith( u'+' ) )
			useItem->setText( 0, use.insert( 0, u'+' ) );
		break;
	case ( Qt::PartiallyChecked ) :
		break;
	}

	buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true );
	m_useSettingsChanged = true;
}


//////////////////////////////////////////////////////////////////////////////////////////////
// Viewing package files
//////////////////////////////////////////////////////////////////////////////////////////////

/**
* Load the files for this package and this version.
*/
void PackageInspector::slotRefreshTabs()
{
	slotLoadUseFlags( cbVersionsUse->currentText() );
	slotLoadEbuild( cbVersionsEbuild->currentText() );
	slotLoadDependencies( cbVersionsDependencies->currentText() );
	slotLoadInstalledFiles( cbVersionsInstalled->currentText() );
	loadChangeLog();
}

/**
* Load internal map with use flag description. @todo: /usr/portage/profiles/use.local.desc
*/
void PackageInspector::loadUseFlagDescription()
{
	//TODO: only read use.desc once, not every time
	for ( const QString &repoDir : KurooConfig::repoLocations() ) {
		QString useFile( repoDir + QStringLiteral("/profiles/use.desc") );
		QFile f( useFile );

		if ( f.exists() ) {
			if ( f.open( QIODevice::ReadOnly ) ) {
				QTextStream stream( &f );

				while ( !stream.atEnd() ) {
					QString line = stream.readLine();
					// this used to have a check  && !line.contains( QRegExp(QStringLiteral( "^alpha|^amd64|^arm|^hppa|^ia64|^mips|^ppc|^ppc64|^ppc-macos|^s390|^sh|^sparc|^x86") ) )
					// but nothing in use.desc starts with an arch like that
					if ( !line.startsWith( u'#' ) && !line.isEmpty() ) {
						QString use = line.section( QStringLiteral(" - "), 0, 0 );
						QString useDescription = line.section( use + QStringLiteral(" - "), 1, 1 );
						// QMap.insert is last-in-wins
						m_useMap.insert( use, useDescription );
					}
				}
				f.close();
			}
			else
				qCritical() << "Loading use flag description. Reading: " << useFile;
		}
	}
	
	if ( m_useMap.isEmpty() ) {
		KMessageBox::error( this, i18n( "Couldn't read any use flag descriptions from use.desc" ) );
	}
}

/**
* View use flags for selected version. @TODO load local use descriptions, handle '^+' in package-default flags
* @param version
*/
void PackageInspector::slotLoadUseFlags( const QString& version )
{
	// 	useView->setDisabled( true );

	if ( inspectorTabs->currentIndex() == 1 ) {
		QStringList useList;

		QMap<QString, PackageVersion*> map = m_portagePackage->versionMap();
		if (map.contains(version))
			useList = map.value(version)->useflags();

		// Get user set package use flags
		QStringList packageUseList = KurooDBSingleton::Instance()->packageUse( m_id ).split(u' ');

		useView->clear();
		QStringList tmpUseList;
		for( const QString& use : std::as_const(useList) ) {

			// Seems some use flags are duplicated, filter them out
			if( tmpUseList.contains( use ) ) {
				continue;
			}
			tmpUseList += use;
		

			QString lines;
			if (m_useMap.contains(use))
				lines = m_useMap.value(use);

			// Split long description into multiple lines
			QStringList description;
			if ( lines.length() <= 90 ) {
				description += lines;
			} else {
				while ( lines.length() > 90 ) {
					int pos = lines.left(90).lastIndexOf(u' ');
					QString line = lines.left( pos + 1 );
					lines = lines.right( lines.length() - line.length() );
					description += line;
				}
				description += lines;
			}

			// Add use flag in use view
			auto* useItem = new QTreeWidgetItem( useView );
			useItem->setFlags( m_useItemFlags ); //Type::CheckBox
			//useItem->setMultiLinesEnabled( true );
			useItem->setText( 0, use );
			useItem->setText( 1, description.join(u'\n') );
		}
	}
	useView->resizeColumnToContents(0);
}

/**
* Get this package changelog.
*/
void PackageInspector::loadChangeLog()
{
	//TODO: convert to QStringBuilder, QRegularExpression, and inline static const
	changelogBrowser->clear();
	if ( inspectorTabs->currentIndex() == 2 ) {
		QString fileName = KurooDBSingleton::Instance()->packagePath( m_id ) + u'/' + m_category + u'/' + m_package + QStringLiteral("/ChangeLog");
		QFile file( fileName );

		if ( file.open( QIODevice::ReadOnly ) ) {
			QTextStream stream( &file );
			QString textLines;
			QRegExp rx(QStringLiteral("#(\\d*)\\b"));
			while ( !stream.atEnd() ) {
				QString line = stream.readLine();

				// Make bugs links to http://bugs.gentoo.org
				if ( rx.indexIn( line ) > -1 ) {
					line.replace( rx.cap(0), QStringLiteral("<a href=\"http://bugs.gentoo.org/show_bug.cgi?id=%1\">%2</a>").arg( rx.cap(1), rx.cap(0) ) );
				}

				textLines += line + QStringLiteral("<br>");
			}
			file.close();
			changelogBrowser->setText( textLines );
		}
		else {
			qCritical() << "Loading changelog. Reading: " << fileName;
			changelogBrowser->setText( QStringLiteral("<font color=darkRed><b>") + i18n( "No ChangeLog found." ) + QStringLiteral("</b></font>") );
		}
	}
}

/**
* Get ebuild for selected version.
* @param version
*/
void PackageInspector::slotLoadEbuild( const QString& version )
{
	//TODO: convert to QStringBuilder
	ebuildBrowser->clear();
	if ( inspectorTabs->currentIndex() == 3 ) {
		QString fileName = KurooDBSingleton::Instance()->packagePath( m_id ) +
						u'/' + m_category + u'/' + m_package + u'/' + m_package + u'-' + version + QStringLiteral(".ebuild");
		QFile file( fileName );

		if ( file.open( QIODevice::ReadOnly ) ) {
			QTextStream stream( &file );
			QString textLines;
			while ( !stream.atEnd() ) {
				textLines += stream.readLine() + QStringLiteral("<br>");
			}
			file.close();
			ebuildBrowser->setText( textLines );
		}
		else {
			qCritical() << "Loading ebuild. Reading: " << fileName;
			ebuildBrowser->setText( QStringLiteral("<font color=darkRed><b>") + i18n( "No ebuild found." ) + QStringLiteral("</b></font>") );
		}
	}
}

/**
* Get dependencies for selected version.
* @param version
*/
void PackageInspector::slotLoadDependencies( const QString& version )
{
	//TODO: convert to QStringBuilder
	//WARN: This won't work for anything but /usr/portage for now!
	dependencyView->clear();
	if ( inspectorTabs->currentIndex() == 4 ) {
		//package path ought to be /usr/portage
		QString fileName = KurooDBSingleton::Instance()->packagePath( m_id ) +
						QStringLiteral("/metadata/cache/") + m_category + u'/' + m_package + u'-' + version;
		QFile file( fileName );

		if ( file.open( QIODevice::ReadOnly ) ) {
			QTextStream stream( &file );
			QString textLines;
			int lineCount( 0 );

			// 			if ( KurooConfig::portageVersion21() )
			// 				while ( !stream.atEnd() ) {
			// 					QString line = stream.readLine();
			// 					if ( line.contains( "DEPEND=" ) && !line.endsWith( "DEPEND=" ) )
			// 						textLines += line + " ";
			// 				}
			// 			else
			//WARN: Portage seems to have down-graded to the older style flat cache file for the cache in the repository (/usr/portage).  Other caches (layman) may behave differently
			while ( !stream.atEnd() ) {
				QString line = stream.readLine();
				if ( lineCount++ > 13 )
					break;
				if ( !line.isEmpty() ) {
					switch (lineCount) {
						case 1:  textLines += QStringLiteral("DEPEND= ") + line + u' ';
							break;
						case 2:  textLines += QStringLiteral("RDEPEND= ") + line + u' ';
							break;
						case 13: textLines += QStringLiteral("PDEPEND= ") + line + u' ';
							break;
						default:
							qDebug() << LINE_INFO << "This switch needs an extra case!";
							break;
						}
					}
			
			}
			file.close();

			// Make sure all words are space-separated
			textLines.replace( QStringLiteral("DEPEND="), QStringLiteral("DEPEND= ") );
			textLines = textLines.simplified();

			//WARN: this seems excessively implementation-specific
			// Remove bootstrap and all it's duplicate
			textLines.remove( QStringLiteral("!bootstrap? ( sys-devel/patch )") );

			dependencyView->insertDependAtoms( textLines.split(u' ') );
		}
		else {
			qCritical() << "Loading dependencies. Reading: " << fileName;
			/*dependencyBrowser->setText( "<font color=darkRed><b>" + i18n("No dependencies found.") +
												"</b></font>" );*/
		}
	}
}

/**
* Get list of installed files for selected version.
* @param version
*/
void PackageInspector::slotLoadInstalledFiles( const QString& version )
{
	installedFilesBrowser->clear();
	if ( !version.isEmpty() && inspectorTabs->currentIndex() == 5 ) {
		QString filename = KurooConfig::dirDbPkg() + u'/' + m_category + u'/' + m_package + u'-' + version + QStringLiteral("/CONTENTS");
		QFile file( filename );
		QString textLines;
		if ( file.open( QIODevice::ReadOnly ) ) {
			QTextStream stream( &file );
			while ( !stream.atEnd() ) {
				QString line = stream.readLine();
				if ( line.startsWith( QStringLiteral("obj") ) ) {
					textLines += line.section( QStringLiteral("obj "), 1, 1 ).section( u' ', 0, 0 ) + u'\n';
				}
			}
			file.close();
			installedFilesBrowser->setText( textLines );
		}
		else {
			qCritical() << "Loading installed files list. Reading: " << filename;
			installedFilesBrowser->setText( QStringLiteral("<font color=darkRed><b>") + i18n( "No installed files found." ) + QStringLiteral("</b></font>") );
		}
	}
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Use calculation
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
* Run emerge pretend to get use active use flags.
*/
void PackageInspector::slotCalculateUse()
{
	m_pretendUseLines.clear();
	eProc = new KProcess();
	eProc->setOutputChannelMode(KProcess::OnlyStdoutChannel);
	*eProc << QStringLiteral("emerge") << QStringLiteral("--columns") << QStringLiteral("--nospinner") << QStringLiteral("--color=n") << QStringLiteral("-pv") << m_category + u'/' + m_package;

	connect(eProc, static_cast<void(KProcess::*)(int,QProcess::ExitStatus)>(&KProcess::finished), this, &PackageInspector::slotParsePackageUse);
	connect(eProc, &KProcess::readyReadStandardOutput, this, &PackageInspector::slotCollectPretendOutput);
	eProc->start();

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

	if ( eProc->state() != QProcess::Starting && eProc->state() != QProcess::Running) {
		LogSingleton::Instance()->writeLog( i18n( "\nError: Could not calculate use flag for package %1/%2.",
												m_category, m_package ), ERROR );
		slotParsePackageUse();
	}
	else
		setDisabled( true );
}

/**
* Collect emerge pretend output in m_pretendUseLines.
*/
void PackageInspector::slotCollectPretendOutput()
{
	QString line;
	while( !(line = QString::fromUtf8(eProc->readLine()) ).isEmpty() )
		m_pretendUseLines += line;
}

/**
* Parse emerge pretend output for all use flags, and return a List.
*/
void PackageInspector::slotParseTempUse()
{
	SignalistSingleton::Instance()->setKurooBusy( false );
	delete eProc;
	eProc = nullptr;

	const QRegularExpression* rxPretend = KurooGlobal::rxEmerge;

	// 	qDebug() << "m_pretendUseLines=" << m_pretendUseLines;

	QStringList tmpUseList;
	for ( const QString& pretend : std::as_const(m_pretendUseLines) ) {
		if ( pretend.contains( m_category + u'/' + m_package ) ) {
			QRegularExpressionMatch match = rxPretend->match( pretend );
			if ( match.hasMatch() ) {
				QString use = match.captured(7).simplified();
				tmpUseList = use.split(u' ');
			}
		}
	}
	// 	qDebug() << "tmpUseList=" << tmpUseList;

	useView->clear();
	if ( tmpUseList.isEmpty() ) {
		auto* item = new QTreeWidgetItem( useView );
		item->setText( 0, i18n("Use flags could not be calculated. Please check log for more information") );
		for ( const QString& pretend : std::as_const(m_pretendUseLines) )
			LogSingleton::Instance()->writeLog( pretend, ERROR );
		return;
	}

	//recalculated use, now needs to check if a line in package.use is needed
	//do it better: check if a word is needed in package.use
	static const QRegularExpression plusStarPercent(QStringLiteral("/b\\+|\\*|\\%"));
	QStringList useList = m_useList.join(QStringLiteral(", ")).remove( plusStarPercent ).split(QStringLiteral(", "));
	for ( QString aux : std::as_const(tmpUseList) ){
		//removes all * since it's not a characted admitted in use flags
		aux = aux.remove( plusStarPercent );
		for ( QString aux2 : std::as_const(m_useList) ) {
			aux2 = aux2.remove( plusStarPercent );
			if (aux == aux2 )
				useList = QRegExp(QStringLiteral("^(?!").append( aux ).append(u')') ).capturedTexts();
		}
	}
	//end of better
	// 	qDebug() << "useList=" << useList;

	QString checkUse = useList.join(QStringLiteral(", "));
	if ( !checkUse.remove(QStringLiteral(", ")).remove(u' ').isEmpty() ) {
		KurooDBSingleton::Instance()->setPackageUse( m_id, useList.join(u' ') );
		PortageFiles::savePackageUse();
	}
}

/**
* Parse emerge pretend output for all use flags.
* @param eProc
*/
void PackageInspector::slotParsePackageUse()
{
	setDisabled( false );

	SignalistSingleton::Instance()->setKurooBusy( false );
	delete eProc;
	eProc = nullptr;

	const QRegularExpression* rxPretend = KurooGlobal::rxEmerge;

	// 	qDebug() << "m_pretendUseLines=" << m_pretendUseLines;

	QStringList pretendUseList;
	for( const QString& pretend : std::as_const(m_pretendUseLines) ) {
		if ( pretend.contains( m_category + u'/' + m_package ) ) {
			QRegularExpressionMatch match = rxPretend->match( pretend );
			if ( match.hasMatch() ) {
				QString use = match.captured(7).simplified();
				pretendUseList = use.split(u' ');
			}
		}
	}

	// 	qDebug() << "pretendUseList=" << pretendUseList;

	useView->clear();
	if ( pretendUseList.isEmpty() ) {
		auto* useItem = new QTreeWidgetItem( useView );
		useItem->setText( 0, i18n("Use flags could not be calculated. Please check log for more information") );
		for ( const QString& pretend : std::as_const(m_pretendUseLines) )
			LogSingleton::Instance()->writeLog( pretend, ERROR );
		return;
	}

	// Get user set package use flags
	for ( QString pretend : std::as_const(pretendUseList) ) {
		QString lines;
		static const QRegularExpression leadingPlusMinusParenPlusParenMinusTrailingStarParenStarParen(QStringLiteral("^\\+|^-|^\\(-|^\\(\\+|\\*$|\\*\\)$|\\)$"));
		QString useFlag = pretend.remove( leadingPlusMinusParenPlusParenMinusTrailingStarParenStarParen );
		if (m_useMap.contains(useFlag))
			lines = m_useMap.value(useFlag);

		/*QMap<QString, QString>::iterator itMap = m_useMap.find( useFlag );
		if ( itMap != m_useMap.end() )
			lines = itMap.value();*/

		// Split long description into multiple lines
		QStringList description;
		if ( lines.length() <= 90 )
			description += lines;
		else {
			while ( lines.length() > 90 ) {
				int pos = lines.left(90).lastIndexOf(u' ');
				QString line = lines.left( pos + 1 );
				lines = lines.right( lines.length() - line.length() );
				description += line;
			}
			description += lines;
		}

		// Set CheckBox state
		if ( pretend.startsWith( u'+' ) || !pretend.startsWith( u'-' ) ) {
			auto* useItem = new QTreeWidgetItem( useView );
			useItem->setText( 0, pretend );
			useItem->setFlags( m_useItemFlags );
			//useItem->setMultiLinesEnabled( true );
			useItem->setText( 1, description.join(u'\n') );
			useItem->setCheckState( 0, Qt::Checked );
		}
		else
			if ( pretend.startsWith( u'-' ) ) {
				auto* useItem = new QTreeWidgetItem( useView );
				useItem->setText( 0, pretend );
				useItem->setFlags( m_useItemFlags );
				//useItem->setMultiLinesEnabled( true );
				useItem->setText( 1, description.join(u'\n') );
				useItem->setCheckState( 0, Qt::Unchecked );
			}
			else {
				auto* useItem = new QTreeWidgetItem( useView );
				useItem->setText( 0, pretend );
				//useItem->setMultiLinesEnabled( true );
				useItem->setText( 1, description.join(u'\n') );
			}
	}
	useView->resizeColumnToContents(0);

	if ( KUser().isSuperUser() || EmergeSingleton::Instance()->isRunning() || SignalistSingleton::Instance()->isKurooBusy())
		useView->setDisabled( false );
}

