/***************************************************************************
 *   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.h>

#include <kactioncollection.h>
#include <kstandardaction.h>
#include <ktoolbarpopupaction.h>
#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(nullptr), m_undoAction(nullptr), m_redoAction(nullptr), m_undoMenu(nullptr), m_redoMenu(nullptr), m_currentDocument(nullptr), m_dockWidget(nullptr)
{
    Q_UNUSED(iWidget);
    SKGTRACEINFUNC(10);
}

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

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

    m_currentDocument = iDocument;

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

    // Menu
    m_undoSaveAction = new QAction(SKGServices::fromTheme(QStringLiteral("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(QStringLiteral("edit_undolastsave"), m_undoSaveAction);

    m_undoAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("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(QStringLiteral("edit_undo"), m_undoAction);

    m_redoAction = new KToolBarPopupAction(SKGServices::fromTheme(QStringLiteral("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(QStringLiteral("edit_redo"), m_redoAction);

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

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

    // add action to control hide / display of history
    QAction* toggle = m_dockWidget->toggleViewAction();
    QAction* panelAction = actionCollection()->addAction(QStringLiteral("view_transactions"));
    registerGlobalAction(QStringLiteral("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_dockWidget->widget() == nullptr) {
        auto w = new SKGUndoRedoPluginDockWidget(m_currentDocument);
        connect(w, &SKGUndoRedoPluginDockWidget::selectionChanged, SKGMainPanel::getMainPanel(), &SKGMainPanel::refresh);
        m_dockWidget->setWidget(w);
    }

    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 = QStringLiteral("");
        }
        if (m_undoAction) {
            m_undoAction->setStatusTip(message);
        }

        m_currentDocument->getTransactionToProcess(SKGDocument::REDO, &name);
        message = i18nc("Verb",  "Redo operation '%1'.", name);
        if (name.isEmpty()) {
            message = QStringLiteral("");
        }
        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(QStringLiteral("SKG_UNDO_MAX_DEPTH"))));
        pref.writeEntry("cleanHistoryOnSave", (m_currentDocument->getParameter(QStringLiteral("SKG_UNDO_CLEAN_AFTER_SAVE")) == QStringLiteral("Y")));
    }

    // Create widget
    auto 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() ? QStringLiteral("Y") : QStringLiteral("N"));

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

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

QString SKGUndoRedoPlugin::icon() const
{
    return QStringLiteral("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(QStringLiteral("skgundoredoplugin_too_big"))) {
        // Get nb transaction
        int nbObject = m_currentDocument->getNbTransaction();
        int priority = qMin(10, nbObject / 50);
        if (priority > 0) {
            SKGAdvice ad;
            ad.setUUID(QStringLiteral("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(QStringLiteral("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 == QStringLiteral("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(
            QStringLiteral("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) == QStringLiteral("Y") ?  SKGServices::fromTheme(QStringLiteral("document-revert")) : SKGServices::fromTheme(QStringLiteral("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(
            QStringLiteral("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(SKGServices::fromTheme(QStringLiteral("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>
