/*
 * Kuroo - A KDE frontend to Gentoo Portage
 * Copyright 2018  Amber Schenck galiven@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) version 3 or any later version
 * accepted by the membership of KDE e.V. (or its successor approved
 * by the membership of KDE e.V.), which shall act as a proxy
 * defined in Section 14 of version 3 of the license.
 * 
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include <cerrno>               // for errno
#include <cstddef>              // for NULL
#include <kauth/helpersupport.h>  // for isStopped, progressStep, KAUTH_HELPE...
#include <kio/simplejob.h>       // for chown, SimpleJob
#include <kprocess.h>            // for KProcess, KProcess::MergedChannels
#include <qbytearray.h>          // for QByteArray
#include <qdebug.h>              // for QDebug, operator<<
#include <qdir.h>                // for QDir
#include <qelapsedtimer.h>       // for QElapsedTimer
#include <qfile.h>               // for QFile
#include <qglobal.h>             // for qDebug, qSwap, Q_UNUSED, foreach
#include <qlist.h>
#include <qiodevice.h>           // for QIODevice, QIODevice::WriteOnly
#include <qprocess.h>            // for QProcess, qt_getEnumName, QProcess::...
#include <qstringlist.h>         // for QStringList
#include <qtextstream.h>         // for QTextStream
#include <qurl.h>                // for QUrl
#include <qvariant.h>            // for QVariant
#include <sys/stat.h>            // for chmod
#include <unistd.h>              // for sleep

#include "kuroohelper.h"
#include <qstringliteral.h>

using namespace KAuth;

auto KurooHelper::createkuroodir( const QVariantMap& args ) -> ActionReply
{
	Q_UNUSED( args );
	ActionReply reply = ActionReply::SuccessReply();
	QString kurooDir = QStringLiteral( "/var/cache/kuroo" );
	QDir d( kurooDir );
	if ( !d.exists()) {
		if ( !d.mkdir( kurooDir ) ) {
			reply = ActionReply::HelperErrorReply();
			reply.setErrorDescription( QStringLiteral("Couldn't create kuroo dir") );
		} else if ( !KIO::chmod( QUrl::fromLocalFile( kurooDir ), 0770 )-> exec() ) {
			reply = ActionReply::HelperErrorReply();
			reply.setError( errno ? errno : -1 );
			reply.setErrorDescription( QStringLiteral("Couldn't change mode of kuroo dir") );
		} else if ( !KIO::chown( QUrl::fromLocalFile( kurooDir ), QStringLiteral("portage"), QStringLiteral("portage") )->exec() ) {
			reply = ActionReply::HelperErrorReply();
			reply.setErrorDescription( QStringLiteral("Couldn't change group on kuroo dir") );
		}
	}
	return reply;
}

auto KurooHelper::chownkuroodir( const QVariantMap& args ) -> ActionReply
{
	Q_UNUSED( args );
	ActionReply reply;
	//Force it to the right ownership in case it was created as root:root previously
	if ( !KIO::chown( QUrl::fromLocalFile( QStringLiteral("/var/cache/kuroo") ), QStringLiteral("portage"), QStringLiteral("portage")  )->exec()) {
		reply = ActionReply::HelperErrorReply();
		reply.setErrorDescription( QStringLiteral("Couldn't change group on kuroo dir") );
		return reply;
	}
	reply = ActionReply::SuccessReply();
	return reply;
}

auto KurooHelper::emerge( const QVariantMap& args ) -> ActionReply
{
	// This is bloody awful
	QStringList systemEnv = args[QStringLiteral("ENV")].toStringList();
	if ( nullptr != m_emergeProc ) {
		m_emergeProc->close();
		m_emergeProc->clearProgram();
	} else {
		m_emergeProc = new KProcess();
		qDebug() << "Setting m_emergeProc environment to " << systemEnv;
		m_emergeProc->setEnvironment( systemEnv );
		//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
		m_emergeProc->setOutputChannelMode( KProcess::MergedChannels );
	}

	m_emergeProc->setProgram( QStringLiteral("emerge"), args[QStringLiteral("args")].toStringList() );

	connect(m_emergeProc, &KProcess::readyReadStandardOutput, this, &KurooHelper::slotEmergeOutput);
	qDebug() << "Starting " << m_emergeProc->program() << m_emergeProc->arguments();
	QElapsedTimer elapsed;
	elapsed.start();
	m_emergeProc->start();

	while ( QProcess::Running == m_emergeProc->state() || QProcess::Starting == m_emergeProc->state() ) {
		if ( HelperSupport::isStopped() ) {
			qDebug() << "Emerge cancelled by user";
			m_emergeProc->kill();
			return ActionReply::UserCancelledReply();
		}
		sleep( 1 );
		if ((elapsed.elapsed() / 1000 % 60) == 0)
			qDebug() << "Emerge tick" << (elapsed.elapsed() / 1000 / 60) << (elapsed.elapsed() / 1000) % 60;
	}
	qDebug() << "Emerge process is no longer running, status is now " << m_emergeProc->state();
	disconnect(m_emergeProc, &KProcess::readyReadStandardOutput, this, &KurooHelper::slotEmergeOutput);
	return ActionReply::SuccessReply();
}

void KurooHelper::slotEmergeOutput() const
{
	QByteArray data = m_emergeProc->readAllStandardOutput();
	QVariantMap retVal;
	retVal[QStringLiteral("stdout")] = data;
	KAuth::HelperSupport::progressStep( retVal );
}

auto KurooHelper::quickpkg(const QVariantMap& args) -> ActionReply
{
	// This is bloody awful
	QStringList systemEnv = args[QStringLiteral("ENV")].toStringList();
	if ( nullptr != m_emergeProc ) {
		m_emergeProc->close();
		m_emergeProc->clearProgram();
	} else {
		m_emergeProc = new KProcess();
		qDebug() << "Setting m_emergeProc environment to " << systemEnv;
		m_emergeProc->setEnvironment( systemEnv );
		//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
		m_emergeProc->setOutputChannelMode( KProcess::MergedChannels );
	}

	m_emergeProc->setProgram( QStringLiteral("quickpkg"),  args[QStringLiteral("args")].toStringList() );

	connect(m_emergeProc, &KProcess::readyReadStandardOutput, this, &KurooHelper::slotEmergeOutput);
	m_emergeProc->start();

	qDebug() << "Started " << m_emergeProc->program() << m_emergeProc->arguments();
	while ( QProcess::Running == m_emergeProc->state() || QProcess::Starting == m_emergeProc->state() ) {
		if ( HelperSupport::isStopped() ) {
			m_emergeProc->kill();
			return ActionReply::UserCancelledReply();
		}
		sleep( 10 );
	}
	qDebug() << "quickpkg process is no longer running, status is now " << m_emergeProc->state();
	disconnect(m_emergeProc, &KProcess::readyReadStandardOutput, this, &KurooHelper::slotEmergeOutput);
	return ActionReply::SuccessReply();
}

auto KurooHelper::writeworld( const QVariantMap& args ) -> ActionReply
{
	// Check is @world is writable
	// TODO: can I access KurooConfig here?
	QString worldfile = args[QStringLiteral("worldfile")].toString();
	QFile file( worldfile );
	if ( !file.open( QIODevice::WriteOnly ) ) {
		qCritical() << "Adding packages to @world. Writing: " << worldfile;
		return ActionReply::HelperErrorReply();
	}
	
	// Update @world file
	QTextStream stream( &file );
	// Declaring a local const QStringList prevents detaching and the attendant clazy warning.
	// In C++20 the range for can do this in the loop declaration https://www.kdab.com/blog-qasconst-and-stdas_const/
	const QStringList worldmap = args[QStringLiteral("worldmap")].toStringList();
	for ( const QString& key : worldmap )
		stream << key << "\n";
	file.close();
	
	return ActionReply::SuccessReply();
}


KAUTH_HELPER_MAIN("org.gentoo.kuroo", KurooHelper)
