/***************************************************************************
 *	Copyright (C) 2005 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 <cstdio>             // for remove
#include <cstdlib>            // for exit
#include <KAuth/Action>       // for Action
#include <KAuth/ExecuteJob>   // for ExecuteJob
#include <kio/simplejob.h>     // for chown, SimpleJob
#include <klocalizedstring.h>  // for i18n
#include <kmessagebox.h>       // for error
#include <kprocess.h>          // for KProcess, KProcess::OnlyStdoutChannel
#include <kuser.h>             // for KUser
#include <qbytearray.h>        // for QByteArray
#include <qdebug.h>            // for QDebug
#include <qdialog.h>           // for QDialog, QDialog::Accepted
#include <qdir.h>              // for QDir, operator|, QDir::Files, QDir::No...
#include <qfile.h>             // for QFile
#include <qfileinfo.h>         // for QFileInfo, QFileInfoList
#include <qglobal.h>           // for QGlobalStatic, qDebug, foreach, qWarning
#include <qiodevice.h>         // for QIODevice, QIODevice::ReadWrite
#include <qlist.h>             // for QList
#include <qprocess.h>          // for QProcess, QProcess::ExitStatus
#include <qurl.h>              // for QUrl
#include <sys/stat.h>          // for chmod

#include "common.h"            // for KurooDBSingleton, DEBUG_LINE_INFO, Por...
#include "emerge.h"            // for Emerge
#include "etcupdate.h"         // for EtcUpdate
#include "global.h"            // for kurooDir
#include "history.h"           // for History
#include "introdlg.h"          // for IntroDlg
#include "kurooinit.h"
#include "log.h"               // for Log
#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

/**
 * @class KurooInit
 * @short KurooInit checks that kuroo environment is correctly setup.
 *
 * And launch intro wizard whenever a new version of kuroo is installed.
 * Set ownership for directories and files to portage:portage.
 * Check that user is in portage group.
 */
KurooInit::KurooInit( QWidget *parent )
	: QObject( parent ), wizardDialog( new IntroDlg( parent ) )
{
	qDebug() << "Initializing Kuroo Environment";
	getEnvironment();

	checkEtcFiles();

	// Run intro if new version is installed or no DirHome directory is detected.
	QDir d( kurooDir );
	if ( KurooConfig::version() != KurooConfig::hardVersion() || !d.exists() || KurooConfig::wizard() ) {
		firstTimeWizard();
	} else {
		if ( !KUser().isSuperUser() ) {
			checkUser(parent);
		}
	}

	// ::init() is set by firstTimeWizard()
	// Setup kuroo environment
	if ( KurooConfig::init() ) {
		KurooConfig::setSaveLog( false );

		// Create DirHome dir and set permissions so common user can run Kuroo
		if ( !d.exists() ) {
			KAuth::Action createKurooDir( QStringLiteral("org.gentoo.kuroo.createkuroodir") );
			createKurooDir.setHelperId( QStringLiteral("org.gentoo.kuroo") );
			KAuth::ExecuteJob *job = createKurooDir.execute();
			if ( !job->exec() ) {
				KMessageBox::error( parent, i18n("<qt>Could not create kuroo home directory.<br/>"
											"Please try again!<br/></qt>").append(job->errorText()), i18n("Initialization") );
				exit(0);
			}

			QDir::setCurrent( kurooDir );
		}
	}

	QFileInfo f( kurooDir );
	if ( f.group() != QStringLiteral("portage") ) {
		//Force it to the right ownership in case it was created as root:root previously
		KAuth::Action chownKurooDir( QStringLiteral("org.gentoo.kuroo.chownkuroodir") );
		chownKurooDir.setHelperId( QStringLiteral("org.gentoo.kuroo") );
		KAuth::ExecuteJob *job = chownKurooDir.execute();
		if ( !job->exec() ) {
			KMessageBox::error( parent, job->errorText(), i18n( "Initialization" ) );
			exit(0);
		}
	}

	// Check that backup directory exists and set correct permissions
	QString backupDir = kurooDir + QStringLiteral("backup");
	if ( !d.cd( backupDir ) ) {
		if ( !d.mkdir( backupDir ) ) {
			KMessageBox::error( parent, i18n("<qt>Could not create kuroo backup directory.<br/>"
										"You must start Kuroo with kdesu first time for a secure initialization.<br/>"
										"Please try again!</qt>"), i18n("Initialization") );
			exit(0);
		}
		else {
			KIO::chmod( QUrl::fromLocalFile( backupDir ), 0770 )->exec();
			KIO::chown( QUrl::fromLocalFile(backupDir), QStringLiteral("portage"), QStringLiteral("portage") )->exec();//portageGid->gr_name, portageUid->pw_name);
		}
	}

	KurooConfig::setVersion( KurooConfig::hardVersion() );
	KurooConfig::self()->save();

	// Initialize the log
	QString logFile = LogSingleton::Instance()->init( parent );
	if ( !logFile.isEmpty() ) {
		KIO::chmod( QUrl::fromLocalFile( logFile ), 0660 )->exec();
		KIO::chown( QUrl::fromLocalFile(logFile), QStringLiteral("portage"), QStringLiteral("portage") )->exec();//portageGid->gr_name, portageUid->pw_name );
	}

	// Initialize the database
	QString databaseFile = KurooDBSingleton::Instance()->init( this );
	QString database = kurooDir + KurooConfig::databas();
	QString dbVersion = KurooDBSingleton::Instance()->getKurooDbMeta( QStringLiteral("kurooVersion") );

	// Check for conflicting db design or new install
	if ( KurooConfig::version().section( QStringLiteral("_db"), 1, 1 ) != dbVersion ) {

		// Backup history if there's old db version
		if ( !dbVersion.isEmpty() ) {
			KurooDBSingleton::Instance()->backupDb();
			KIO::file_delete( QUrl::fromLocalFile( database ) )->exec();
			qWarning() << QStringLiteral("Database structure is changed. Deleting old version of database %1").arg( database );

			// and recreate with new structure
			KurooDBSingleton::Instance()->init( this );
		}

		KurooDBSingleton::Instance()->setKurooDbMeta( QStringLiteral("kurooVersion"), KurooConfig::version().section( QStringLiteral("_db"), 1, 1 ) );
	}

	// Give permissions to portage:portage to access the db also
	KIO::chmod( QUrl::fromLocalFile( databaseFile ), 0660 )->exec();
	KIO::chown( QUrl::fromLocalFile(databaseFile), QStringLiteral("portage"), QStringLiteral("portage") )->exec();//portageGid->gr_name, portageUid->pw_name );

    // Initialize singletons objects
	SignalistSingleton::Instance()->init( parent );
	EmergeSingleton::Instance()->init( parent );
	EtcUpdateSingleton::Instance()->init( parent );
	HistorySingleton::Instance()->init( parent );
	PortageSingleton::Instance()->init( parent );
	QueueSingleton::Instance()->init( parent );
	PortageFilesSingleton::Instance()->init( parent );

	//Load packages in case /etc/portage.* changed
	PortageFilesSingleton::Instance()->loadPackageFiles();
}

KurooInit::~KurooInit()
{
	KurooConfig::setInit( false );
}

/**
 * Run "emerge --info" to collect system info like "ACCEPT_KEYWORDS" and "CONFIG_PROTECT".
 */
void KurooInit::getEnvironment()
{
	DEBUG_LINE_INFO;
	eProc = new KProcess();
	*eProc << QStringLiteral("emerge") << QStringLiteral("--info");
	eProc->setOutputChannelMode( KProcess::OnlyStdoutChannel );
	connect(eProc, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, &KurooInit::slotEmergeInfo);
	connect(eProc, &KProcess::readyReadStandardOutput, this, &KurooInit::slotCollectOutput);
	eProc->start();
}

void KurooInit::slotCollectOutput()
{
	QByteArray line;
	while( !( line = eProc->readLine() ).isEmpty() )
		m_emergeInfoLines += QString::fromUtf8( line );

	//this was a memory leak, thanks ASAN
	delete eProc;
}

void KurooInit::slotEmergeInfo()
{
	qDebug() << "Parsing emerge --info";
	for( const QString& line : std::as_const(m_emergeInfoLines) ) {

		if ( line.startsWith( QStringLiteral("ACCEPT_KEYWORDS=") ) ) {
			QString arch = line.section( u'"', 1, 1 );

			// When testing we have two keywords, only pick one
			if( arch.contains( u'~' ) ) {
				arch = arch.section( u'~', 1, 1 );
			}

			KurooConfig::setArch( arch );
		}

		if ( line.startsWith( QStringLiteral("CONFIG_PROTECT=") ) )
			KurooConfig::setConfigProtectList( line.section( u'"', 1, 1 ) );
	}

	qDebug() << "KurooConfig::arch()=" << KurooConfig::arch();

	//KurooConfig::writeConfig();
	DEBUG_LINE_INFO;
}

/**
 * Run wizard to inform user of latest changes and collect user settings like kuroo DirHome directory,
 * and overlay location.
 * If user aborts the wizard it will be relaunched again next time.
 */
void KurooInit::firstTimeWizard()
{
	IntroDlg wizardDialog;
	qDebug() << "Running Wizard";
	if( wizardDialog.exec() == QDialog::Accepted ) {
		KurooConfig::setWizard( false );
	} else {
		exit(0); //is this the correct way to exit ?
	}
	KurooConfig::setInit( true );
}

/**
 * Control if user is in portage group.
 */
void KurooInit::checkUser( QWidget *parent )
{
	QStringList userGroups = KUser().groupNames();
	for( const QString& user : std::as_const(userGroups) ) {
		if ( user == QStringLiteral("portage") )
			return;
	}

	KMessageBox::error( parent, i18n("You don't have enough permissions to run kuroo.\nPlease add yourself into portage group!"),
						   i18n("User permissions") );
	exit(0);
}

void KurooInit::checkEtcFiles()
{
	QStringList paths;
	paths	<< QStringLiteral("FilePackageUserUnMask") << QStringLiteral("/etc/portage/package.unmask")
		<< QStringLiteral("FilePackageKeywords") << QStringLiteral("/etc/portage/package.accept_keywords")
		<< QStringLiteral("FilePackageUserMask") << QStringLiteral("/etc/portage/package.mask")
		<< QStringLiteral("FilePackageUserUse") << QStringLiteral("/etc/portage/package.use");

	for(int i = 0; i < paths.count(); ++i)
	{
		QStringList list;
		QString configOption = paths[i];
		QString path = paths[++i];
		QFileInfo fInfo(path);

		if (!fInfo.exists())
		{
			QFile file(path);
			file.open(QIODevice::ReadWrite);
			file.close();
			list << path;
		}
		else if (fInfo.isDir())
		{
			QDir dir(path);
			dir.setFilter(QDir::Files | QDir::NoSymLinks);
			QFileInfoList fiList = dir.entryInfoList();
			for (const auto& fileInfo : std::as_const(fiList))
			{
					//qDebug() << "Found" << fileInfo.absoluteFilePath();
				list << fileInfo.absoluteFilePath();
			}
			if (list.isEmpty())
			{
				QFile file(path.append(QStringLiteral("/all")));
				file.open(QIODevice::ReadWrite);
				file.close();
				list << path;
			}
		}
		else if (fInfo.isFile())
		{
			list << path;
		}

		//qDebug() << configOption << ":" << list;
		//TODO:check if default file exists.
		if (configOption == QStringLiteral("FilePackageUserUnMask"))
		{
			KurooConfig::setFilePackageUserUnMask(list);
			if (KurooConfig::defaultFilePackageUserUnMask().isEmpty()/* || !QFileInfo::exists(KurooConfig::defaultFilePackageUserUnMask())*/)
				KurooConfig::setDefaultFilePackageUserUnMask(list[0]);
		}
		if (configOption == QStringLiteral("FilePackageKeywords"))
		{
			KurooConfig::setFilePackageKeywords(list);
			if (KurooConfig::defaultFilePackageKeywords().isEmpty()/* || !QFileInfo::exists(KurooConfig::defaultFilePackageUserUnMask())*/)
				KurooConfig::setDefaultFilePackageKeywords(list[0]);
		}
		if (configOption == QStringLiteral("FilePackageUserMask"))
		{
			KurooConfig::setFilePackageUserMask(list);
			if (KurooConfig::defaultFilePackageUserMask().isEmpty()/* || !QFileInfo::exists(KurooConfig::defaultFilePackageUserUnMask())*/)
				KurooConfig::setDefaultFilePackageUserMask(list[0]);
		}
		if (configOption == QStringLiteral("FilePackageUserUse"))
		{
			KurooConfig::setFilePackageUserUse(list);
			if (KurooConfig::defaultFilePackageUserUse().isEmpty()/* || !QFileInfo::exists(KurooConfig::defaultFilePackageUserUnMask())*/)
				KurooConfig::setDefaultFilePackageUserUse(list[0]);
		}

	}
}
