/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   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, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
 * This file is a plugin for undo and redo operation.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgundoredoplugin.h"

#include <QWidget>

#include <KActionCollection>
#include <KStandardAction>
#include <KToolBarPopupAction>
#include <kaboutdata.h>
#include <kpluginfactory.h>

#include "skgtraces.h"
#include "skgerror.h"
#include "skgmainpanel.h"
#include "skgundoredoplugindockwidget.h"
#include "skgservices.h"
#include "skgundoredo_settings.h"

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGUndoRedoPluginFactory, registerPlugin<SKGUndoRedoPlugin>();)

SKGUndoRedoPlugin::SKGUndoRedoPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/) :
    SKGInterfacePlugin(iParent),
    m_undoSaveAction(NULL), m_undoAction(NULL), m_redoAction(NULL), m_undoMenu(NULL), m_redoMenu(NULL), m_currentDocument(NULL), m_dockWidget(NULL)
{
    Q_UNUSED(iWidget);
    SKGTRACEINFUNC(10);
}

SKGUndoRedoPlugin::~SKGUndoRedoPlugin()
{
    SKGTRACEINFUNC(10);
    m_currentDocument = NULL;
    m_dockWidget = NULL;
    m_undoSaveAction = NULL;
    m_undoAction = NULL;
    m_redoAction = NULL;
    m_undoMenu = NULL;
    m_redoMenu = NULL;
}

bool SKGUndoRedoPlugin::setupActions(SKGDocument* iDocument, const QStringList& iArgument)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArgument);

    m_currentDocument = iDocument;

    setComponentName("skg_undoredo", title());
    setXMLFile("skg_undoredo.rc");

    // Menu
    m_undoSaveAction = new QAction(QIcon::fromTheme("document-revert"), i18nc("Verb, action to cancel previous action", "Revert document"), this);
    connect(m_undoSaveAction, &QAction::triggered, this, &SKGUndoRedoPlugin::onUndoSave);
    actionCollection()->setDefaultShortcut(m_undoSaveAction, Qt::CTRL + Qt::ALT + Qt::Key_Z);
    registerGlobalAction("edit_undolastsave", m_undoSaveAction);

    m_undoAction = new KToolBarPopupAction(QIcon::fromTheme("edit-undo"), i18nc("Verb, action to cancel previous action", "Undo"), this);
    connect(m_undoAction, &KToolBarPopupAction::triggered, this, &SKGUndoRedoPlugin::onUndo);
    actionCollection()->setDefaultShortcut(m_undoAction, Qt::CTRL + Qt::Key_Z);
    m_undoAction->setPriority(QAction::LowPriority);
    m_undoMenu = static_cast<QMenu*>(m_undoAction->menu());
    connect(m_undoMenu, &QMenu::aboutToShow, this, &SKGUndoRedoPlugin::onShowUndoMenu);
    m_undoAction->setStickyMenu(false);
    m_undoAction->setData(1);
    registerGlobalAction("edit_undo", m_undoAction);

    m_redoAction = new KToolBarPopupAction(QIcon::fromTheme("edit-redo"), i18nc("Verb, action to redo previous cancelled action", "Redo"), this);
    connect(m_redoAction, &KToolBarPopupAction::triggered, this, &SKGUndoRedoPlugin::onRedo);
    actionCollection()->setDefaultShortcut(m_redoAction, Qt::CTRL + Qt::SHIFT + Qt::Key_Z);
    m_redoAction->setPriority(QAction::LowPriority);
    m_redoMenu = static_cast<QMenu*>(m_redoAction->menu());
    connect(m_redoMenu, &QMenu::aboutToShow, this, &SKGUndoRedoPlugin::onShowRedoMenu);
    m_redoAction->setStickyMenu(false);
    m_redoAction->setData(1);
    registerGlobalAction("edit_redo", m_redoAction);

    // Menu
    QAction* act = new QAction(QIcon::fromTheme("edit-clear-history"), i18nc("Verb, action to cancel previous action", "Clear history"), this);
    connect(act, &QAction::triggered, this, &SKGUndoRedoPlugin::onClearHistory);
    registerGlobalAction("edit_clear_history", act);

    // Dock creation
    m_dockWidget = new QDockWidget(SKGMainPanel::getMainPanel());
    m_dockWidget->setObjectName(QString::fromUtf8("skg_undoredo_docwidget"));
    m_dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
    m_dockWidget->setWindowTitle(title());
    m_dockWidget->setWidget(new SKGUndoRedoPluginDockWidget(m_currentDocument));

    // add action to control hide / display of history
    QAction* toggle = m_dockWidget->toggleViewAction();
    QAction* panelAction = actionCollection()->addAction("view_transactions");
    registerGlobalAction("view_transactions", panelAction);
    panelAction->setCheckable(true);
    panelAction->setChecked(toggle->isChecked());
    panelAction->setText(toggle->text());
    actionCollection()->setDefaultShortcut(panelAction, Qt::SHIFT + Qt::Key_F11);
    connect(panelAction, &QAction::triggered, toggle, &QAction::trigger);
    connect(toggle, &QAction::toggled, panelAction, &QAction::setChecked);

    return true;
}

void SKGUndoRedoPlugin::refresh()
{
    SKGTRACEINFUNC(10);

    if (m_currentDocument) {
        bool undoPossible = (m_currentDocument->getNbTransaction(SKGDocument::UNDO) > 0);
        if (m_undoSaveAction) {
            m_undoSaveAction->setEnabled(undoPossible);
        }
        if (m_undoAction) {
            m_undoAction->setEnabled(undoPossible);
        }
        if (m_redoAction) {
            m_redoAction->setEnabled(m_currentDocument->getNbTransaction(SKGDocument::REDO) > 0);
        }

        // Refresh undo redo
        QString name;
        m_currentDocument->getTransactionToProcess(SKGDocument::UNDO, &name);
        QString message = i18nc("Verb",  "Undo operation '%1'.", name);
        if (name.isEmpty()) {
            message = "";
        }
        if (m_undoAction) {
            m_undoAction->setStatusTip(message);
        }

        m_currentDocument->getTransactionToProcess(SKGDocument::REDO, &name);
        message = i18nc("Verb",  "Redo operation '%1'.", name);
        if (name.isEmpty()) {
            message = "";
        }
        if (m_redoAction) {
            m_redoAction->setStatusTip(message);
        }
    }
}


QWidget* SKGUndoRedoPlugin::getPreferenceWidget()
{
    SKGTRACEINFUNC(10);
    // Read Setting
    if (m_currentDocument) {
        KSharedConfigPtr config = KSharedConfig::openConfig();
        KConfigGroup pref = config->group("skg_undoredo");
        pref.writeEntry("maxNumberOfUndo", SKGServices::stringToInt(m_currentDocument->getParameter("SKG_UNDO_MAX_DEPTH")));
        pref.writeEntry("cleanHistoryOnSave", (m_currentDocument->getParameter("SKG_UNDO_CLEAN_AFTER_SAVE") == "Y"));
    }

    // Create widget
    QWidget* w = new QWidget();
    ui.setupUi(w);
    return w;
}

KConfigSkeleton* SKGUndoRedoPlugin::getPreferenceSkeleton()
{
    return skgundoredo_settings::self();
}

SKGError SKGUndoRedoPlugin::savePreferences() const
{
    SKGError err;
    if (m_currentDocument) {
        // Read Setting
        QString max = SKGServices::intToString(skgundoredo_settings::maxNumberOfUndo());
        QString clean = (skgundoredo_settings::cleanHistoryOnSave() ? "Y" : "N");

        // Save setting in document
        if (max != m_currentDocument->getParameter("SKG_UNDO_MAX_DEPTH")) {
            err = m_currentDocument->setParameter("SKG_UNDO_MAX_DEPTH", max);
        }
        if (clean != m_currentDocument->getParameter("SKG_UNDO_CLEAN_AFTER_SAVE")) {
            err = m_currentDocument->setParameter("SKG_UNDO_CLEAN_AFTER_SAVE", clean);
        }
    }
    return err;
}

QString SKGUndoRedoPlugin::title() const
{
    return i18nc("Noun", "History");
}

QString SKGUndoRedoPlugin::icon() const
{
    return "edit-undo";
}

QString SKGUndoRedoPlugin::toolTip() const
{
    return i18nc("Noun", "History");
}

QStringList SKGUndoRedoPlugin::tips() const
{
    QStringList output;
    output.push_back(i18nc("Description of a tips", "<p>... you can undo and redo your modifications.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... you can modify the maximum size of the undo/redo stack in the settings.</p>"));
    return output;
}

int SKGUndoRedoPlugin::getOrder() const
{
    return 4;
}

SKGAdviceList SKGUndoRedoPlugin::advice(const QStringList& iIgnoredAdvice)
{
    SKGTRACEINFUNC(10);
    SKGAdviceList output;
    if (!iIgnoredAdvice.contains("skgundoredoplugin_too_big")) {
        // Get nb transaction
        int nbObject = m_currentDocument->getNbTransaction();
        int priority = qMin(10, nbObject / 50);
        if (priority > 0) {
            SKGAdvice ad;
            ad.setUUID("skgundoredoplugin_too_big");
            ad.setPriority(priority);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "History is too large"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "You can improve performances by reducing your history size in settings."));
            QStringList autoCorrections;
            autoCorrections.push_back("skg://edit_clear_history");
            autoCorrections.push_back(i18nc("Advice on making the best (action)", "Open settings panel"));
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    return output;
}

SKGError SKGUndoRedoPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
{
    SKGError err;
    if (m_currentDocument && iAdviceIdentifier == "skgundoredoplugin_too_big") {
        SKGMainPanel::getMainPanel()->optionsPreferences(this->objectName());
        return err;
    }
    return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
}

void SKGUndoRedoPlugin::onClearHistory()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    if (m_currentDocument && SKGMainPanel::getMainPanel()) {
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
        err = m_currentDocument->removeAllTransactions();
        QApplication::restoreOverrideCursor();

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Message for successful user action", "Clear history successfully done.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message", "Clear history failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGUndoRedoPlugin::onUndoSave()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    if (m_currentDocument && SKGMainPanel::getMainPanel()) {
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
        err = m_currentDocument->undoRedoTransaction(SKGDocument::UNDOLASTSAVE);
        QApplication::restoreOverrideCursor();

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Undo successfully done.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Undo failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGUndoRedoPlugin::onUndo()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    if (m_currentDocument && SKGMainPanel::getMainPanel()) {
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
        int pos = static_cast<QAction*>(sender())->data().toInt();
        for (int i = 1 ; !err && i <= pos; ++i) {
            err = m_currentDocument->undoRedoTransaction(SKGDocument::UNDO);
        }
        QApplication::restoreOverrideCursor();

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Undo successfully done.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Undo failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGUndoRedoPlugin::onShowUndoMenu()
{
    if (m_undoMenu && m_currentDocument) {
        m_undoMenu->clear();
        SKGStringListList listTmp;
        m_currentDocument->executeSelectSqliteOrder(
            "SELECT t_name, t_savestep FROM doctransaction WHERE t_mode='U' ORDER BY d_date DESC LIMIT 7",
            listTmp);
        int nb = listTmp.count();
        for (int i = 1; i < nb; ++i) {
            QAction* act = m_undoMenu->addAction(listTmp.at(i).at(1) == "Y" ?  QIcon::fromTheme("document-revert") : QIcon::fromTheme("edit-undo"), listTmp.at(i).at(0));
            if (act) {
                act->setData(i);
                connect(act, &QAction::triggered, this, &SKGUndoRedoPlugin::onUndo);
            }
        }
    }
}

void SKGUndoRedoPlugin::onRedo()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);
    if (m_currentDocument && SKGMainPanel::getMainPanel()) {
        QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
        int pos = static_cast<QAction*>(sender())->data().toInt();
        for (int i = 1 ; !err && i <= pos; ++i) {
            err = m_currentDocument->undoRedoTransaction(SKGDocument::REDO);
        }
        QApplication::restoreOverrideCursor();

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Redo successfully done.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message",  "Redo failed"));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGUndoRedoPlugin::onShowRedoMenu()
{
    if (m_redoMenu && m_currentDocument) {
        m_redoMenu->clear();
        SKGStringListList listTmp;
        m_currentDocument->executeSelectSqliteOrder(
            "SELECT t_name FROM doctransaction WHERE t_mode='R' ORDER BY d_date ASC LIMIT 7",
            listTmp);
        int nb = listTmp.count();
        for (int i = 1; i < nb; ++i) {
            QAction* act = m_redoMenu->addAction(QIcon::fromTheme("edit-redo"), listTmp.at(i).at(0));
            if (act) {
                act->setData(i);
                connect(act, &QAction::triggered, this, &SKGUndoRedoPlugin::onRedo);
            }
        }
    }
}

QDockWidget* SKGUndoRedoPlugin::getDockWidget()
{
    return m_dockWidget;
}

#include <skgundoredoplugin.moc>
