/**************************************************************************
*	Copyright (C) 2004 by													*
*	karye@users.sourceforge.net												*
*	Stefan Bogner <bochi@online.ms>											*
*	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 <kconfigskeleton.h>   // for KConfigSkeleton
#include <klocalizedstring.h>  // for i18n
#include <kpagedialog.h>       // for KPageDialog, KPageDialog::Tabbed
#include <qdebug.h>            // for QDebug
#include <qdialog.h>           // for QDialog
#include <qdialogbuttonbox.h>  // for QDialogButtonBox, QDialogButtonBox::Re...
#include <qfile.h>             // for QFile
#include <qglobal.h>           // for foreach, qCritical, qDebug, qWarning
#include <qiodevice.h>         // for QIODevice, QIODevice::ReadOnly, QIODev...
#include <QList>				// for QFileInfoList
#include <qmap.h>              // for QMap, QMap<>::Iterator, QMap<>::iterator
#include <qnamespace.h>        // for WA_DeleteOnClose
#include <qpushbutton.h>       // for QPushButton
#include <QSettings>			// for QSettings to read ini-style repos.conf
#include <qtextstream.h>       // for QTextStream, operator<<, endl
#include <qwidget.h>           // for QWidget

#include "common.h"            // for DEBUG_LINE_INFO
#include "configdialog.h"
#include "log.h"               // for Log, Log::buffer_MaxLines
#include <KMessageBox>

/**
* @class ConfigDialog
* @short Kuroo preferences.
*
* Build settings widget for kuroo and make.conf.
* Parses make.conf and tries to keep user-format when saving back settings.
*/
ConfigDialog::ConfigDialog( QWidget *parent, const QString& name, KConfigSkeleton *config )
	: KConfigDialog( parent, name, config ), m_isDefault( false )
{
	QWidget::setAttribute(Qt::WA_DeleteOnClose);

	setFaceType(KPageDialog::Tabbed);

	auto *opt1 = new QDialog();
	form1.setupUi( opt1 ); //, i18n("General") );
	auto *opt2 = new QDialog();
	form2.setupUi( opt2 ); //, i18n("make.conf") );
	auto *opt3 = new QDialog();
	form3.setupUi( opt3 ); //, i18n("Etc-update warnings") );
	auto *opt4 = new QDialog();
	form4.setupUi( opt4 ); //, i18n("Housekeeping") );

	addPage( opt1, i18n("General"), QStringLiteral("kuroo"), i18n("General preferences") );
	addPage( opt2, i18n("make.conf"), QStringLiteral("kuroo_makeconf"), i18n("Edit your make.conf file") );
	addPage( opt3, i18n("Etc-update warnings"), QStringLiteral("dialog-warning"), i18n("Edit your etc-update warning file list") );
	addPage( opt4, i18n("Housekeeping"), QStringLiteral("kuroo_housekeeping"), i18n("Control automatic file cleanup and rebuilding") );

	connect(this, &ConfigDialog::settingsChanged, this, &ConfigDialog::slotSaveAll);
	connect(buttonBox()->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this, &ConfigDialog::slotDefaults);
/*
	int selected = 0;
	for(int i = 0; i < KurooConfig::filePackageKeywords().count(); ++i)
	{
		QString f = KurooConfig::filePackageKeywords()[i];
		if (f == KurooConfig::defaultFilePackageKeywords())
			selected = i;
		form1.keywords->addItem(f);
	}
	form1.keywords->setCurrentIndex(selected);

	selected = 0;
	for(int i = 0; i < KurooConfig::filePackageUserUse().count(); ++i)
	{
		QString f = KurooConfig::filePackageUserUse()[i];
		if (f == KurooConfig::defaultFilePackageUserUse())
			selected = i;
		form1.use->addItem(f);
	}
	form1.use->setCurrentIndex(selected);

	selected = 0;
	for(int i = 0; i < KurooConfig::filePackageUserMask().count(); ++i)
	{
		QString f = KurooConfig::filePackageUserMask()[i];
		if (f == KurooConfig::defaultFilePackageUserMask())
			selected = i;
		form1.mask->addItem(f);
	}
	form1.mask->setCurrentIndex(selected);

	selected = 0;
	for(int i = 0; i < KurooConfig::filePackageUserUnMask().count(); ++i)
	{
		QString f = KurooConfig::filePackageUserUnMask()[i];
		if (f == KurooConfig::defaultFilePackageUserUnMask())
			selected = i;
		form1.unmask->addItem(f);
	}
	form1.unmask->setCurrentIndex(selected);
*/
	parseMakeConf();
	QStringList repoLocations( readReposConf( parent ) );
	if ( !KurooConfig::dirPortage().isEmpty() && repoLocations.contains( KurooConfig::dirPortage() ) ) {
		KMessageBox::information( parent, i18n( "make.conf seems to contain a duplicate setting for %1 that is already covered by repos.conf", QStringLiteral("PORTDIR") ), i18n( "Warning" ) );
	} else {
		repoLocations += KurooConfig::dirPortage();
	}
	if ( !KurooConfig::dirPortageOverlay().isEmpty() ) {
		for ( auto & overlay : KurooConfig::dirPortageOverlay().split( u' ' ) ) {
			if ( repoLocations.contains( overlay ) ) {
				KMessageBox::information( parent, i18n( "make.conf seems to contain a duplicate setting for %1 that is already covered by repos.conf", QStringLiteral("PORTDIR_OVERLAY") ), i18n( "Warning" ) );
			} else {
				repoLocations += KurooConfig::dirPortageOverlay();
			}
		}
	}
	KurooConfig::setRepoLocations( repoLocations );
}

ConfigDialog::~ConfigDialog()
= default;

/**
* Reset to defaults.
*/
void ConfigDialog::slotDefaults()
{
	DEBUG_LINE_INFO;
	parseMakeConf();
	show();
}

/**
* Save settings when user press "Apply".
*/
void ConfigDialog::slotSaveAll()
{
	Log::buffer_MaxLines = KurooConfig::logLines();
	qDebug() << "Buffer changed to " << Log::buffer_MaxLines << "\n";
	DEBUG_LINE_INFO;
	qDebug() << "HACK\n";
	/*switch( activePageIndex() ) {
		// Activate the systray directly (not needing restarting kuroo)
		case 0: {
			if ( KurooConfig::isSystrayEnabled() )
				SystemTray::instance()->activate();
			else
				SystemTray::instance()->inactivate();

			SignalistSingleton::Instance()->fontChanged();
			break;
		}

		case 1:
			if ( !saveMakeConf() ) {
				parseMakeConf();
				show();
				KMessageBox::error( this, i18n( "Failed to save %1. Please run as root.", KurooConfig::fileMakeConf() ), i18n( "Saving" ));
			}
	}*/

	/*KurooConfig::setDefaultFilePackageKeywords( form1.keywords->currentText() );
	KurooConfig::setDefaultFilePackageUserUse( form1.use->currentText() );
	KurooConfig::setDefaultFilePackageUserMask( form1.mask->currentText() );
	KurooConfig::setDefaultFilePackageUserUnMask( form1.unmask->currentText() );
	*/
}

/**
* Read 'make.conf' into stringList by taking into account the different kind of extended lines.
* @return linesConcatenated
*/
auto ConfigDialog::readMakeConf() -> const QStringList
{
	QStringList linesConcatenated;
	if ( !QFile::exists( KurooConfig::fileMakeConf() ) ) {
		qCritical() << KurooConfig::fileMakeConf() << " doesn't exist. Trying /etc/portage/make.conf";
		KurooConfig::setFileMakeConf(QStringLiteral( "/etc/portage/make.conf" ));
	}
	QFile makeconf( KurooConfig::fileMakeConf() );

	if ( makeconf.open( QIODevice::ReadOnly ) ) {
		QTextStream stream( &makeconf );
		QStringList lines;

		// Collect all lines
		while ( !stream.atEnd() )
			lines += stream.readLine().simplified();
		makeconf.close();

		// Concatenate extended lines
		QString extendedLine;
		QStringList linesCommented;
		for( QString line : std::as_const(lines) ) {

			// Skip comment lines
			if ( line.isEmpty() || line.startsWith( u'#' ) ) {
				linesCommented += line;
				continue;
			}
			if ( line.endsWith( u'\\' ) ) {
				//We're re-joining multiline vars into a single line with spaces, so the
				//trailing slash to indicate multi-line isn't needed
				line.chop( 1 );
			}

			//The first time we see '=' extendedLine should be empty, and it gets cleared and
			//set to the whole line.  Subsequent encounters with '=' will join previous lines
			//with spaces, add them to linesConcatenated, and reset extendedLine to the
			//current line
			if ( line.contains( u'=' ) ) {
				linesConcatenated += extendedLine;
				extendedLine = line;
				//add the comments seen since last '=' to collected lines
				linesConcatenated += linesCommented;
				linesCommented.clear();
			} else {
				//Any non-comment lines seen since last '=' are appended with a space to the
				//extended line
				extendedLine += u' ' + line;
			}
		}

		linesConcatenated += extendedLine;
	} else {
		qCritical() << "Error reading: " << KurooConfig::fileMakeConf();
	}

	return linesConcatenated;
}

/**
* Parse /etc/make.conf.
*/
void ConfigDialog::parseMakeConf()
{
	DEBUG_LINE_INFO;
	auto linesConcatenated = readMakeConf();
	if ( !linesConcatenated.isEmpty() ) {
		// Clear old entries
		// The type of the value in m_configHash is 'function void (const QString&)*'
		for( void (*value)(const QString&) : m_configHash ) {
			if ( nullptr != value )
				value( QString() );
		}

		// Parse the lines
		for( const QString& line : linesConcatenated ) {

			// Skip comment lines
			if ( line.isEmpty() || line.startsWith( u'#' ) )
				continue;

			QRegularExpressionMatch match = m_rxLine.match( line );
			if ( match.hasMatch() ) {
				QString name = match.captured( 1 );
				if ( m_configHash.contains( name ) ) {
					if ( nullptr != m_configHash.value( name ) ) {
						m_configHash[name]( match.captured( 2 ) );
						qDebug() << "Parsed" << name << "as" << match.captured( 2 ) << "from make.conf";
					} else {
						qWarning() << "Parsing make.conf: found" << name << "but can't parse it";
					}
					continue;
				}
			}
		}
	}
}

const QStringList ConfigDialog::readReposConf( QWidget* parent )
{
	QFileInfoList pathsToCheck, pathsToRead;
	pathsToCheck.push_back( QFileInfo( QStringLiteral( "/etc/portage/repos.conf" ) ) );
	while ( !pathsToCheck.isEmpty() ) {
		const QFileInfo& pathToCheck = pathsToCheck.takeFirst();
		if ( pathToCheck.exists() ) {
			if ( pathToCheck.isFile() ) {
				pathsToRead.push_back( pathToCheck );
			} else if ( pathToCheck.isDir() ) {
				QDir dirToCheck( pathToCheck.filePath() );
				//qDebug() << LINE_INFO << "reading dir" << dirToCheck;
				QFileInfoList entries = dirToCheck.entryInfoList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot );
				//qDebug() << LINE_INFO << "Adding" << entries << "to paths to check";
				pathsToCheck += entries;
			}
		}
	}
	QStringList repoLocations;
	if ( pathsToRead.isEmpty() ) {
		KMessageBox::error( parent, i18n( "Couldn't find any files under /etc/portage/repos.conf to read" ), i18n( "Error reading repos.conf" ) );
	} else {
		while ( !pathsToRead.isEmpty() ) {
			QSettings repoConf( pathsToRead.takeFirst().filePath(), QSettings::IniFormat );
			const QStringList childGroups = repoConf.childGroups();
			for ( const QString& childGroup : childGroups ) {
				if ( QStringLiteral( "DEFAULT" ) != childGroup ) {
					repoLocations.append( repoConf.value( childGroup + QStringLiteral( "/location" ) ).toString() );
				}
			}
		}
		repoLocations.removeDuplicates();
		repoLocations.removeAll( QString() );
		qDebug() << LINE_INFO << "Found" << repoLocations.join( u',' ) << "repos";
		if ( repoLocations.isEmpty() ) {
			KMessageBox::error( parent, i18n( "Did not find any INI files with location keys in repos.conf" ), i18n( "Error reading repos.conf" ) );
		}
	}
	return repoLocations;
}


void ConfigDialog::handleELogClasses( const QString& value ) {
	/* this is kind of messy, but it gets the job done */
	if( value.contains( QStringLiteral("warn") ) )
		KurooConfig::setElogWarn( true );
	if( value.contains( QStringLiteral("error") ) )
		KurooConfig::setElogError( true );
	if( value.contains( QStringLiteral("info") ) )
		KurooConfig::setElogInfo( true );
	if( value.contains( QStringLiteral("log") ) )
		KurooConfig::setElogLog( true );
	if( value.contains( QStringLiteral("qa") ) )
		KurooConfig::setElogQa( true );
}

void ConfigDialog::handleELogSystem( const QString& value ) {
	/* this is also kindof messy, got a better way? */
	if( value.contains( QStringLiteral("save")) ) 
		KurooConfig::setElogSysSave( true );
	if( value.contains( QStringLiteral("mail")) ) 
		KurooConfig::setElogSysMail( true );
	if( value.contains( QStringLiteral("syslog")) ) 
		KurooConfig::setElogSysSyslog( true );
	if( value.contains( QStringLiteral("custom")) ) 
		KurooConfig::setElogSysCustom( true );
	if( value.contains( QStringLiteral("save_summary") ) )
		KurooConfig::setElogSysSaveSum( true );
	if( value.contains( QStringLiteral("mail_summary") ) )
		KurooConfig::setElogSysMailSum( true );
}

/**
* Save back /etc/make.conf.
* @return success
*/
auto ConfigDialog::saveMakeConf() -> bool
{
	auto linesConcatenated = readMakeConf();
	if ( linesConcatenated.isEmpty() ) {
		return false;
	}

	QMap<QString, QString> keywords;

	// Collect all keywords
	for ( const auto& line : linesConcatenated ) {

		if ( line.contains( m_rxAnyConfigVars ) ) {

			QRegularExpressionMatch match = m_rxLine.match( line );
			if ( match.hasMatch() ) {
				keywords[ match.captured(1) ] = match.captured(2);
			} else {
				qWarning() << QStringLiteral("Parsing %1: can not match keyword %2.").arg( KurooConfig::fileMakeConf(), match.captured(1) );
			}
		}
	}

	// Update keywords from settings
	keywords[ QStringLiteral("ACCEPT_KEYWORDS") ] = KurooConfig::acceptKeywords();

	//Fix a BUG which stores AUTOCLEAN content translated to /etc/make.conf which should not be..
	if (KurooConfig::autoClean()) {
		keywords[ QStringLiteral("AUTOCLEAN") ] = QStringLiteral("yes");
	}
	else {
		keywords[ QStringLiteral("AUTOCLEAN") ] = QStringLiteral("no");
	}

	keywords[ QStringLiteral("CHOST") ] = KurooConfig::chost();
	keywords[ QStringLiteral("CFLAGS") ] = KurooConfig::cFlags();
	keywords[ QStringLiteral("CXXFLAGS") ] = KurooConfig::cXXFlags();
	keywords[ QStringLiteral("MAKEOPTS") ] = KurooConfig::makeOpts();
	keywords[ QStringLiteral("USE") ] = KurooConfig::use();
	keywords[ QStringLiteral("USE_ORDER") ] = KurooConfig::useOrder();
	keywords[ QStringLiteral("CONFIG_PROTECT") ] = KurooConfig::configProtect();
	keywords[ QStringLiteral("CONFIG_PROTECT_MASK") ] = KurooConfig::configProtectMask();
	keywords[ QStringLiteral("FEATURES") ] = KurooConfig::features();

	//Fix a BUG which stores NOCOLOR content translated to /etc/make.conf which should not be..
	if (KurooConfig::noColor()) {
		keywords[ QStringLiteral("NOCOLOR") ] = QStringLiteral("true");
	} else {
		keywords[ QStringLiteral("NOCOLOR") ] = QStringLiteral("false");
	}

	keywords[ QStringLiteral("ROOT") ] = KurooConfig::root();
	keywords[ QStringLiteral("PORTDIR") ] = KurooConfig::dirPortage();
	keywords[ QStringLiteral("PORTDIR_OVERLAY") ] = KurooConfig::dirPortageOverlay();
	keywords[ QStringLiteral("DISTDIR") ] = KurooConfig::dirDist();
	keywords[ QStringLiteral("RPMDIR") ] = KurooConfig::dirRpm();
	keywords[ QStringLiteral("PKG_TMPDIR") ] = KurooConfig::dirPkgTmp();
	keywords[ QStringLiteral("PKGDIR") ] = KurooConfig::dirPkg();
	keywords[ QStringLiteral("PORT_LOGDIR") ] = KurooConfig::dirPortLog();
	keywords[ QStringLiteral("PORTAGE_BINHOST") ] = KurooConfig::portageBinHost();
	keywords[ QStringLiteral("PORTAGE_NICENESS") ] = KurooConfig::portageNiceness();
	keywords[ QStringLiteral("PORTAGE_TMPDIR") ] = KurooConfig::dirPortageTmp();
	keywords[ QStringLiteral("BUILD_PREFIX") ] = KurooConfig::buildPrefix();
	keywords[ QStringLiteral("CBUILD") ] = KurooConfig::cBuild();
	keywords[ QStringLiteral("CCACHE_SIZE") ] = KurooConfig::cCacheSize();
	keywords[ QStringLiteral("CLEAN_DELAY") ] = KurooConfig::cleanDelay();
	keywords[ QStringLiteral("DEBUGBUILD") ] = KurooConfig::debugBuild();
	keywords[ QStringLiteral("FETCHCOMMAND") ] = KurooConfig::fetchCommand();
	keywords[ QStringLiteral("RESUMECOMMAND") ] = KurooConfig::resumeCommand();
	keywords[ QStringLiteral("RSYNC_EXCLUDEFROM") ] = KurooConfig::rsyncExcludeFrom();
	keywords[ QStringLiteral("HTTP_PROXY") ] = KurooConfig::httpProxy();
	keywords[ QStringLiteral("FTP_PROXY") ] = KurooConfig::ftpProxy();
	keywords[ QStringLiteral("GENTOO_MIRRORS") ] = KurooConfig::gentooMirrors();
	keywords[ QStringLiteral("RSYNC_PROXY") ] = KurooConfig::rsyncProxy();
	keywords[ QStringLiteral("RSYNC_RETRIES") ] = KurooConfig::rsyncRetries();
	keywords[ QStringLiteral("RSYNC_RATELIMIT") ] = KurooConfig::rsyncRateLimit();
	keywords[ QStringLiteral("RSYNC_TIMEOUT") ] = KurooConfig::rsyncTimeOut();

	QString elog_value;
	if( KurooConfig::elogWarn() ) elog_value.append(QStringLiteral("warn"));
	if( KurooConfig::elogError() ) elog_value.append(QStringLiteral("error"));
	if( KurooConfig::elogLog() ) elog_value.append(QStringLiteral("log"));
	if( KurooConfig::elogInfo() ) elog_value.append(QStringLiteral("info"));
	if( KurooConfig::elogQa() ) elog_value.append(QStringLiteral("qa"));
	keywords[ QStringLiteral("PORTAGE_ELOG_CLASSES") ] = elog_value;

	QString elog_sys;
	if( KurooConfig::elogSysSave() ) elog_sys.append(QStringLiteral("save"));
	if( KurooConfig::elogSysMail() ) elog_sys.append(QStringLiteral("mail"));
	if( KurooConfig::elogSysSyslog() ) elog_sys.append(QStringLiteral("syslog"));
	if( KurooConfig::elogSysCustom() ) elog_sys.append(QStringLiteral("custom"));
	if( KurooConfig::elogSysSaveSum() ) elog_sys.append(QStringLiteral("save_summary"));
	if( KurooConfig::elogSysMailSum() ) elog_sys.append(QStringLiteral("mail_summary"));
	keywords[ QStringLiteral("PORTAGE_ELOG_SYSTEM") ] = elog_sys;

	keywords[ QStringLiteral("PORTAGE_ELOG_COMMAND") ] = KurooConfig::elogCustomCmd();
	keywords[ QStringLiteral("PORTAGE_ELOG_MAILURI") ] = KurooConfig::elogMailURI();
	keywords[ QStringLiteral("PORTAGE_ELOG_MAILFROM") ] = KurooConfig::elogMailFromURI();
	keywords[ QStringLiteral("PORTAGE_ELOG_MAILSUBJECT") ] = KurooConfig::elogSubject();
	keywords[ QStringLiteral("SYNC") ] = KurooConfig::sync();

	// Write back everything
	QFile file( KurooConfig::fileMakeConf() );
	if ( file.open( QIODevice::WriteOnly ) ) {
		QTextStream stream( &file );

		bool top( true );
		for ( const auto& line : linesConcatenated ) {

			// Skip first empty lines
			if ( top && line.isEmpty() ) {
				continue;
			}
			top = false;

			if ( line.contains( m_rxAnyConfigVars ) ) {

				QRegularExpressionMatch match = m_rxLine.match( line );
				if ( match.hasMatch() ) {
					QString keyword = match.captured(1);
					if ( !keywords[ keyword ].isEmpty() ) {
						stream << keyword << "=\"" << keywords[ keyword ] << "\"\n";
					}
					keywords.take( keyword );
				}
			} else {
				stream << line << Qt::endl;
			}
		}

		// Add the rest (new) entries into make.conf
		for ( QMap<QString, QString>::Iterator it = keywords.begin(), end = keywords.end(); it != end; ++it ) {
			if ( !it.value().isEmpty() ) {
				stream << it.key() << "=\"" << it.value() << "\"\n";
			}
		}

		file.close();
		return true;
	}
	qCritical() << QStringLiteral( "Writing: %1" ).arg( KurooConfig::fileMakeConf() );
	return false;
}
