/***************************************************************************
 *   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 <QtGui/QWidget>

#include <KActionCollection>
#include <KStandardAction>
#include <KToolBarPopupAction>
#include <KAboutData>
#include <KGenericFactory>

#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>();)
/**
 * This plugin export.
 */
K_EXPORT_PLUGIN(SKGUndoRedoPluginFactory("skg_undoredo", "skg_undoredo"))

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);
    SKGTRACEIN(10, "SKGUndoRedoPlugin::SKGUndoRedoPlugin");
}

SKGUndoRedoPlugin::~SKGUndoRedoPlugin()
{
    SKGTRACEIN(10, "SKGUndoRedoPlugin::~SKGUndoRedoPlugin");
    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)
{
    SKGTRACEIN(10, "SKGUndoRedoPlugin::setupActions");
    Q_UNUSED(iArgument);

    m_currentDocument = iDocument;

    setComponentData(KGlobal::mainComponent());
    setXMLFile("../skg_undoredo/skg_undoredo.rc");

    // Menu
    m_undoSaveAction = new KAction(KIcon("document-revert"), i18nc("Verb, action to cancel previous action", "Revert document"), this);
    connect(m_undoSaveAction, SIGNAL(triggered(bool)), this, SLOT(onUndoSave()));
    m_undoSaveAction->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Z);
    registerGlobalAction("edit_undolastsave", m_undoSaveAction);

    m_undoAction = new KToolBarPopupAction(KIcon("edit-undo"), i18nc("Verb, action to cancel previous action", "Undo"), this);
    connect(m_undoAction, SIGNAL(triggered(bool)), this, SLOT(onUndo()));
    m_undoAction->setShortcut(Qt::CTRL + Qt::Key_Z);
    m_undoAction->setPriority(QAction::LowPriority);
    m_undoMenu = static_cast<KMenu*>(m_undoAction->menu());
    connect(m_undoMenu , SIGNAL(aboutToShow()), this, SLOT(onShowUndoMenu()));
    m_undoAction->setStickyMenu(false);
    m_undoAction->setData(1);
    registerGlobalAction("edit_undo", m_undoAction);

    m_redoAction = new KToolBarPopupAction(KIcon("edit-redo"), i18nc("Verb, action to redo previous cancelled action", "Redo"), this);
    connect(m_redoAction, SIGNAL(triggered(bool)), this, SLOT(onRedo()));
    m_redoAction->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Z);
    m_redoAction->setPriority(QAction::LowPriority);
    m_redoMenu = static_cast<KMenu*>(m_redoAction->menu());
    connect(m_redoMenu , SIGNAL(aboutToShow()), this, SLOT(onShowRedoMenu()));
    m_redoAction->setStickyMenu(false);
    m_redoAction->setData(1);
    registerGlobalAction("edit_redo", m_redoAction);

    // Menu
    KAction* act = new KAction(KIcon("edit-clear-history"), i18nc("Verb, action to cancel previous action", "Clear history"), this);
    connect(act, SIGNAL(triggered(bool)), this, SLOT(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();
    KAction* panelAction = actionCollection()->addAction("view_transactions");
    registerGlobalAction("view_transactions", panelAction);
    panelAction->setCheckable(true);
    panelAction->setChecked(toggle->isChecked());
    panelAction->setText(toggle->text());
    panelAction->setShortcut(Qt::SHIFT + Qt::Key_F11);
    connect(panelAction, SIGNAL(triggered()), toggle, SLOT(trigger()));
    connect(toggle, SIGNAL(toggled(bool)), panelAction, SLOT(setChecked(bool)));

    return true;
}

void SKGUndoRedoPlugin::refresh()
{
    SKGTRACEIN(10, "SKGUndoRedoPlugin::refresh");

    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()
{
    SKGTRACEIN(10, "SKGUndoRedoPlugin::getPreferenceWidget");
    // 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)
{
    SKGTRACEIN(10, "SKGUndoRedoPlugin::advice");
    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;
    SKGTRACEINRC(10, "SKGUndoRedoPlugin::onUndoSave", 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;
    SKGTRACEINRC(10, "SKGUndoRedoPlugin::onUndoSave", 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;
    SKGTRACEINRC(10, "SKGUndoRedoPlugin::onUndo", 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" ?  KIcon("document-revert") : KIcon("edit-undo"), listTmp.at(i).at(0));
            if (act) {
                act->setData(i);
                connect(act, SIGNAL(triggered()), this, SLOT(onUndo()));
            }
        }
    }
}
void SKGUndoRedoPlugin::onRedo()
{
    SKGError err;
    SKGTRACEINRC(10, "SKGUndoRedoPlugin::onRedo", 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(KIcon("edit-redo"), listTmp.at(i).at(0));
            if (act) {
                act->setData(i);
                connect(act, SIGNAL(triggered()), this, SLOT(onRedo()));
            }
        }
    }
}

QDockWidget* SKGUndoRedoPlugin::getDockWidget()
{
    return m_dockWidget;
}
#include "skgundoredoplugin.moc"
