/***************************************************************************
*	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 <QtCore/qglobal.h>      // for qDebug
#include <cstddef>              // for NULL
#include <kactioncollection.h>   // for KActionCollection
#include <kconfigdialog.h>       // for KConfigDialog
#include <kio/job_base.h>        // for Job
#include <klocalizedstring.h>    // for i18n
#include <kmessagebox.h>         // for questionYesNo, error, Yes
#include <kstandardaction.h>     // for preferences, quit
#include <qaction.h>             // for QAction
#include <qdatetime.h>           // for QDateTime
#include <qdebug.h>              // for QDebug
#include <qkeysequence.h>        // for QKeySequence
#include <qnamespace.h>          // for CTRL, Key_P, Key_S, Key_U, Key_W
#include <qtimer.h>              // for QTimer
#include <threadweaver/queue.h>  // for Queue
#include <unistd.h>              // for usleep

#include "common.h"              // for SignalistSingleton, KurooDBSingleton
#include "configdialog.h"        // for ConfigDialog
#include "emerge.h"              // for Emerge
#include "introdlg.h"            // for IntroDlg
#include "kuroo.h"
#include "kurooinit.h"           // for KurooInit
#include "kurooview.h"           // for KurooView
#include "log.h"                 // for Log
#include "portage.h"             // for Portage
#include "portagedb.h"           // for KurooDB
#include "settings.h"            // for KurooConfig
#include "signalist.h"           // for Signalist
#include "statusbar.h"           // for KurooStatusBar
#include "systemtray.h"          // for SystemTray

/**
* @class Kuroo
* @short Main kde window with menus, system tray icon and statusbar.
*/
Kuroo::Kuroo() : KXmlGuiWindow( nullptr ),
	kurooInit( new KurooInit( this ) ), m_view( new KurooView( this ) ),
	prefDialog( nullptr ), wizardDialog( nullptr ), m_shuttingDown( false )
{
	qDebug() << "Initializing Kuroo GUI";

	setCentralWidget( m_view );
	setupActions();
	setStatusBar( new KurooStatusBar( this ) );

	// Add system tray icon
	if ( KurooConfig::isSystrayEnabled() ) {
		systemTray = new SystemTray( this );
		connect(systemTray, &SystemTray::signalPreferences, this, &Kuroo::slotPreferences);
	}

	// Lock/unlock if kuroo is busy.
	connect( SignalistSingleton::Instance(), &Signalist::signalKurooBusy, this, &Kuroo::slotBusy );

	// when the last window is closed, the application should quit
    //TODO: check whether to connect this signal in main.cpp
	//connect( kapp, SIGNAL( lastWindowClosed() ), kapp, SLOT( quit() ) );

	// Kuroo must initialize with db first
	SignalistSingleton::Instance()->setKurooReady( false );

	// Initialize with settings in make.conf
	prefDialog = KConfigDialog::exists( i18n( "settings" ) );
	if ( !prefDialog )
		prefDialog = new ConfigDialog( m_view, i18n( "settings" ), KurooConfig::self() );

	// Zack Rusin's delayed initialization technique
	QTimer::singleShot( 0, m_view, &KurooView::slotInit );
}

/**
* If necessary wait for job to finish before terminating.
*/
Kuroo::~Kuroo()
{
	int maxLoops( 99 );
	while ( true ) {
		//if ( ThreadWeaver::Queue::instance()->isJobPending( "DBJob" ) || ThreadWeaver::Queue::instance()->isJobPending( "CachePortageJob" ) )
		if (!ThreadWeaver::Queue::instance()->isIdle())
			::usleep( 100000 ); // Sleep 100 msec
		else
			break;

		if ( maxLoops-- == 0 ) {
			KMessageBox::error( this, i18n("Kuroo is not responding. Attempting to terminate kuroo!"), i18n("Terminating") );
			break;
		}
	}
}

/**
* Build mainwindow menus and toolbar.
*/
void Kuroo::setupActions()
{
	KStandardAction::quit( this, &Kuroo::slotQuit, actionCollection() );
	KStandardAction::preferences( this, &Kuroo::slotPreferences, actionCollection() );

	auto * actionReleaseInfo = new QAction( i18n("&Release information"), this );
	actionCollection()->setDefaultShortcut( actionReleaseInfo,  QKeySequence( Qt::CTRL | Qt::Key_W ) );
	actionCollection()->addAction( QStringLiteral("information"), actionReleaseInfo );
	connect(actionReleaseInfo, &QAction::triggered, this, &Kuroo::introWizard);

	actionRefreshPortage = new QAction( i18n("&Refresh Packages"), this );
	actionCollection()->setDefaultShortcut( actionRefreshPortage, QKeySequence( Qt::CTRL | Qt::Key_P ) );
	actionCollection()->addAction( QStringLiteral("refresh_portage"), actionRefreshPortage );
	connect( actionRefreshPortage, &QAction::triggered, PortageSingleton::Instance(), &Portage::slotRefresh );

	actionRefreshUpdates = new QAction( i18n("&Refresh Updates"), this );
	actionCollection()->setDefaultShortcut( actionRefreshUpdates, QKeySequence( Qt::CTRL | Qt::Key_U ) );
	actionCollection()->addAction( QStringLiteral("refresh_updates"), actionRefreshUpdates );
	connect( actionRefreshUpdates, &QAction::triggered, PortageSingleton::Instance(), &Portage::slotRefreshUpdates );

	actionSyncPortage = new QAction( i18n("&Sync Portage"), this );
	actionCollection()->setDefaultShortcut( actionSyncPortage, QKeySequence( Qt::CTRL | Qt::Key_S ) );
	actionCollection()->addAction( QStringLiteral("sync_portage"), actionSyncPortage );
	connect(actionSyncPortage, &QAction::triggered, this, &Kuroo::slotSync);

	setupGUI( Default, QStringLiteral("kurooui.rc") );
}

/**
* Disable buttons when kuroo is busy.
*/
void Kuroo::slotBusy()
{
	bool isBusy = (SignalistSingleton::Instance()->isKurooBusy() || EmergeSingleton::Instance()->isRunning());
	if ( !isBusy ) {
		// Make sure progressbar is stopped!
		KurooStatusBar::instance()->stopTimer();
	}
	actionRefreshPortage->setEnabled( !isBusy );
	actionRefreshUpdates->setEnabled( !isBusy );

	if ( EmergeSingleton::Instance()->isRunning() || SignalistSingleton::Instance()->isKurooBusy() ||
		 KurooDBSingleton::Instance()->isPortageEmpty() ) {
		actionSyncPortage->setEnabled( false );
	}
	else {
		actionSyncPortage->setEnabled( true );
	}

	// No db no fun!
	if ( !SignalistSingleton::Instance()->isKurooReady() ) {
		actionRefreshPortage->setEnabled( false );
		actionRefreshUpdates->setEnabled( false );
		actionSyncPortage->setEnabled( false );
	}
}

/**
* Launch emerge portage sync.
*/
void Kuroo::slotSync()
{
	QString timeStamp( KurooDBSingleton::Instance()->getKurooDbMeta( QStringLiteral("syncTimeStamp") ) );
	QString lastSyncDate;

	if ( !timeStamp.isEmpty() ) {
		lastSyncDate = QDateTime::fromSecsSinceEpoch( timeStamp.toUInt() ).toString();
	} else {
		lastSyncDate = QStringLiteral("never");
	}

	switch( KMessageBox::questionTwoActions( this,
		i18n( "Last sync: %1", lastSyncDate ) +
		i18n( "<qt>Do you want to synchronize portage?<br/>"
			"This will take a couple of minutes...</qt>" ), i18n( "Sync packages" ), KGuiItem( i18n("Sync"), QStringLiteral("kuroo_refresh_view") ), KStandardGuiItem::cancel() ) ) {

		case KMessageBox::PrimaryAction:
			PortageSingleton::Instance()->slotSync();
		default:
			break;
	}
}

/**
* Open kuroo preferences window.
*/
void Kuroo::slotPreferences()
{
	prefDialog = KConfigDialog::exists( i18n( "settings" ) );
	if ( !prefDialog )
		prefDialog = new ConfigDialog( m_view, i18n( "settings" ), KurooConfig::self() );
	prefDialog->show();
	prefDialog->raise();
	//prefDialog->setActiveWindow();
}

/**
* Show the wizard.
*/
void Kuroo::introWizard()
{
	if ( !wizardDialog )
		wizardDialog = new IntroDlg( this );

	wizardDialog->show();
}

/**
* Hide or minimize kuroo window when clicking in close button.
*/
auto Kuroo::queryClose() -> bool
{
	if ( !m_shuttingDown ) {
		if ( !KurooConfig::isSystrayEnabled() )
			slotQuit();
		else {
			hide();
			return false;
		}
	}

	return true;
}

/**
* Bye, bye!
*/
auto Kuroo::queryExit() -> bool
{
	DEBUG_LINE_INFO;
	//Quit from the system tray icon calls this instead of the quit action on the main window
	if ( !m_shuttingDown ) {
		slotQuit();
	}
	return true;
}

/**
* Backup emerge and merge history entries to text file.
* Wait for the backup of the log is completed before terminating.
*/
void Kuroo::slotQuit()
{
	qDebug() << "Cleaning up after ourselves";
	KurooDBSingleton::Instance()->backupDb();
	KIO::Job *backupLogJob = LogSingleton::Instance()->backupLog();
	if ( backupLogJob != nullptr )
		connect(backupLogJob, &KIO::Job::result, this, &Kuroo::slotWait);
	else
		slotWait();
}

/**
* Abort any running threads.
*/
void Kuroo::slotWait()
{
	if ( SignalistSingleton::Instance()->isKurooBusy() ) {
		switch( KMessageBox::questionTwoActions( this,
			i18n("<qt>Kuroo is busy<br/><br/>"
				"Do you want to quit?<br/>"
				"All jobs will be aborted.</qt>"), i18n("Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cont() ) ) {

			case KMessageBox::PrimaryAction: {
				ThreadWeaver::Queue::instance()->requestAbort();//AllJobsNamed( "DBJob" );
				//ThreadWeaver::Queue::instance()->abortAllJobsNamed( "CachePortageJob" );
				QTimer::singleShot( 500, this, &Kuroo::slotTerminate );
			}
			default:
				break;
		}
	}
	else
		slotTerminate();
}

/**
* Terminate kuroo.
*/
void Kuroo::slotTerminate()
{
	m_shuttingDown = true;
	close();
}

