/***************************************************************************
 *   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
 * A skrooge plugin to manage scheduled operations
 *
 * @author Stephane MANKOWSKI
 */
#include "skgscheduledplugin.h"

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

#include "skgscheduledpluginwidget.h"
#include "skgscheduled_settings.h"
#include "skgrecurrentoperationobject.h"
#include "skgtraces.h"
#include "skgmainpanel.h"
#include "skgtransactionmng.h"
#include "skgdocumentbank.h"
#include "skgsuboperationobject.h"
#include "skghtmlboardwidget.h"

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGScheduledPluginFactory, registerPlugin<SKGScheduledPlugin>();)

SKGScheduledPlugin::SKGScheduledPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/) :
    SKGInterfacePlugin(iParent), m_currentBankDocument(NULL), m_counterAdvice(0)
{
    Q_UNUSED(iWidget);
    SKGTRACEINFUNC(10);
}

SKGScheduledPlugin::~SKGScheduledPlugin()
{
    SKGTRACEINFUNC(10);
    m_currentBankDocument = NULL;
}

bool SKGScheduledPlugin::setupActions(SKGDocument* iDocument, const QStringList& iArgument)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArgument);
    m_currentBankDocument = qobject_cast<SKGDocumentBank*>(iDocument);
    if (m_currentBankDocument == NULL) {
        return false;
    }

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

    // Create yours actions here
    QAction* actScheduleOperation = new QAction(QIcon::fromTheme(icon()), i18nc("Verb, create a scheduled operation", "Schedule"), this);
    connect(actScheduleOperation, &QAction::triggered, this, &SKGScheduledPlugin::onScheduleOperation);
    actionCollection()->setDefaultShortcut(actScheduleOperation, Qt::CTRL + Qt::Key_I);
    registerGlobalAction("schedule_operation", actScheduleOperation, QStringList() << "operation", 1, -1, 410);
    return true;
}

void SKGScheduledPlugin::refresh()
{
    SKGTRACEINFUNC(10);
    // Automatic insert
    if (m_currentBankDocument && m_currentBankDocument->getDatabase() != NULL) {
        QString doc_id = m_currentBankDocument->getUniqueIdentifier();
        if (m_docUniqueIdentifier != doc_id && m_currentBankDocument->getParameter("SKG_DB_BANK_VERSION") >= "0.5") {
            m_docUniqueIdentifier = doc_id;

            SKGError err;
            // Read Setting
            bool check_on_open = skgscheduled_settings::check_on_open();

            if (check_on_open) {
                SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Insert recurrent operations"), err);
                int nbi = 0;
                err = SKGRecurrentOperationObject::process(m_currentBankDocument, nbi);
            }
            // Display error
            SKGMainPanel::displayErrorMessage(err);
        }
    }
}

int SKGScheduledPlugin::getNbDashboardWidgets()
{
    return 1;
}

QString SKGScheduledPlugin::getDashboardWidgetTitle(int iIndex)
{
    Q_UNUSED(iIndex);
    return i18nc("Noun, the title of a section", "Scheduled operations");
}

SKGBoardWidget* SKGScheduledPlugin::getDashboardWidget(int iIndex)
{
    return new SKGHtmlBoardWidget(m_currentBankDocument,
                                  getDashboardWidgetTitle(iIndex),
                                  QStandardPaths::locate(QStandardPaths::GenericDataLocation, "skrooge/html/default/scheduled_operations.html"),
                                  QStringList() << "v_recurrentoperation_display");
}

SKGTabPage* SKGScheduledPlugin::getWidget()
{
    SKGTRACEINFUNC(10);
    return new SKGScheduledPluginWidget(m_currentBankDocument);
}

QWidget* SKGScheduledPlugin::getPreferenceWidget()
{
    SKGTRACEINFUNC(10);
    QWidget* w = new QWidget();
    ui.setupUi(w);
    return w;
}

KConfigSkeleton* SKGScheduledPlugin::getPreferenceSkeleton()
{
    return skgscheduled_settings::self();
}

QString SKGScheduledPlugin::title() const
{
    return i18nc("Noun", "Scheduled operations");
}

QString SKGScheduledPlugin::icon() const
{
    return "chronometer";
}

QString SKGScheduledPlugin::toolTip() const
{
    return i18nc("Noun", "Operations scheduled management");
}

int SKGScheduledPlugin::getOrder() const
{
    return 20;
}

QStringList SKGScheduledPlugin::tips() const
{
    QStringList output;
    output.push_back(i18nc("Description of a tips", "<p>... you can schedule operations or templates.</p>"));
    return output;
}

bool SKGScheduledPlugin::isInPagesChooser() const
{
    return true;
}

SKGError SKGScheduledPlugin::savePreferences() const
{
    SKGError err;
    if (m_currentBankDocument) {
        // Read Setting
        if (skgscheduled_settings::create_template()) {
            SKGObjectBase::SKGListSKGObjectBase recurrents;
            err = m_currentBankDocument->getObjects("v_recurrentoperation", "(select count(1) from operation where operation.id=rd_operation_id and t_template='N')=1", recurrents);
            int nb = recurrents.count();
            if (nb) {
                SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Conversion schedule"), err, nb);
                for (int i = 0; !err && i < nb; ++i) {
                    // Migration of existing schedule in template mode
                    SKGRecurrentOperationObject recOp(recurrents.at(i));
                    SKGOperationObject operationObj;

                    IFOK(err) recOp.getParentOperation(operationObj);

                    SKGOperationObject operationObjOrig = operationObj;
                    IFOKDO(err, operationObjOrig.duplicate(operationObj , operationObjOrig.getDate(), true))

                    IFOKDO(err, recOp.setParentOperation(operationObj))
                    IFOKDO(err, recOp.save())
                    IFOKDO(err, recOp.load())

                    IFOKDO(err, operationObjOrig.setAttribute("r_recurrentoperation_id", SKGServices::intToString(recOp.getID())))
                    IFOKDO(err, operationObjOrig.save())

                    IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
                }
                IFOK(err) m_currentBankDocument->sendMessage(i18nc("An information message",  "All scheduled operations have been converted in template"));
            }
        }
    }
    return err;
}

SKGError SKGScheduledPlugin::scheduleOperation(const SKGOperationObject& iOperation, SKGRecurrentOperationObject& oRecurrent) const
{
    SKGError err;
    SKGOperationObject operationObjDuplicate = iOperation;
    bool isTemplate = operationObjDuplicate.isTemplate();

    SKGOperationObject operationObjOrig;
    if (!isTemplate && skgscheduled_settings::create_template()) {
        // The selected operation is not a template and settings is set to create one
        operationObjOrig = operationObjDuplicate;
        IFOKDO(err, operationObjOrig.duplicate(operationObjDuplicate, operationObjOrig.getDate(), true))
        IFOK(err) m_currentBankDocument->sendMessage(i18nc("An information message",  "A template has been created"), SKGDocument::Positive);
    }

    SKGRecurrentOperationObject recOp;
    err = operationObjDuplicate.addRecurrentOperation(recOp);
    IFOKDO(err, recOp.warnEnabled(skgscheduled_settings::remind_me()))
    IFOKDO(err, recOp.setWarnDays(skgscheduled_settings::remind_me_days()))
    IFOKDO(err, recOp.autoWriteEnabled(skgscheduled_settings::auto_write()))
    IFOKDO(err, recOp.setAutoWriteDays(skgscheduled_settings::auto_write_days()))
    IFOKDO(err, recOp.timeLimit(skgscheduled_settings::nb_times()))
    IFOKDO(err, recOp.setTimeLimit(skgscheduled_settings::nb_times_val()))
    IFOKDO(err, recOp.setPeriodIncrement(skgscheduled_settings::once_every()))
    IFOKDO(err, recOp.setPeriodUnit(static_cast<SKGRecurrentOperationObject::PeriodUnit>(SKGServices::stringToInt(skgscheduled_settings::once_every_unit()))))
    if (!err && !isTemplate) {
        err = recOp.setDate(recOp.getNextDate());
    }
    IFOKDO(err, recOp.save())
    if (!isTemplate && skgscheduled_settings::create_template()) {
        IFOKDO(err, recOp.load())
        IFOKDO(err, operationObjOrig.setAttribute("r_recurrentoperation_id", SKGServices::intToString(recOp.getID())))
        IFOKDO(err, operationObjOrig.save())
    }

    oRecurrent = recOp;

    return err;
}

void SKGScheduledPlugin::onScheduleOperation()
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err);

    // Get Selection
    if (SKGMainPanel::getMainPanel()) {
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();
        int nb = selection.count();
        if (nb && m_currentBankDocument) {
            QStringList list;
            SKGBEGINPROGRESSTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Operation schedule"), err, nb);
            for (int i = 0; !err && i < nb; ++i) {
                SKGOperationObject operationObj(selection.at(i));
                SKGRecurrentOperationObject rop;
                err = scheduleOperation(operationObj, rop);

                // Send message
                IFOKDO(err, m_currentBankDocument->sendMessage(i18nc("An information to the user", "The operation '%1' has been scheduled", operationObj.getDisplayName()), SKGDocument::Hidden));

                IFOKDO(err, m_currentBankDocument->stepForward(i + 1))
                list.push_back(operationObj.getUniqueID());
            }

            IFOK(err) {
                // Open the scheduled operation
                SKGMainPanel::getMainPanel()->openPage("skg://skrooge_scheduled_plugin/?selection=" % SKGServices::encodeForUrl(SKGServices::stringsToCsv(list)));
            }
        }

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

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

SKGAdviceList SKGScheduledPlugin::advice(const QStringList& iIgnoredAdvice)
{
    SKGTRACEINFUNC(10);
    SKGAdviceList output;

    // Recurrent operation with the last inserted operation having a different amount
    if (!iIgnoredAdvice.contains("skgscheduledplugin_notuptodate")) {
        SKGStringListList result;
        m_currentBankDocument->executeSelectSqliteOrder("SELECT r.id, r.rd_operation_id, r.f_CURRENTAMOUNT, r2.f_CURRENTAMOUNT FROM v_recurrentoperation_display r INNER JOIN (SELECT MAX(d_date), f_CURRENTAMOUNT, r_recurrentoperation_id FROM v_operation_display GROUP BY r_recurrentoperation_id) r2 WHERE r2.r_recurrentoperation_id=r.id AND ABS(r.f_CURRENTAMOUNT-r2.f_CURRENTAMOUNT)>" % SKGServices::doubleToString(EPSILON), result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QStringList line = result.at(i);
            int idRecu = SKGServices::stringToInt(line.at(0));
            QString idOper = line.at(1);
            QString amountRecu = line.at(2);
            QString amountLastOperation = line.at(3);

            SKGRecurrentOperationObject recu(m_currentBankDocument, idRecu);
            QString name = recu.getDisplayName();

            SKGAdvice ad;
            ad.setUUID("skgscheduledplugin_notuptodate|" % idOper % ';' % amountLastOperation);
            ad.setPriority(4);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Scheduled operation '%1' not uptodate", name));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "The scheduled operation '%1' does not have the amount of the last inserted operation (%2)", name, amountLastOperation));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = i18nc("Advice on making the best (action)", "Update the next scheduled operation amount (%1)", amountLastOperation);
                a.IconName = "system-run";
                a.IsRecommended = true;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Recurrent operation with the last inserted operation having a different date
    if (!iIgnoredAdvice.contains("skgscheduledplugin_newdate")) {
        SKGStringListList result;
        m_currentBankDocument->executeSelectSqliteOrder("SELECT r.id, r.d_date, "
                "date(r2.d_date, '+'||((CASE r.t_period_unit WHEN 'W' THEN 7  ELSE 1 END)*r.i_period_increment)||' '||(CASE r.t_period_unit WHEN 'M' THEN 'month' WHEN 'Y' THEN 'year' ELSE 'day' END)) "
                "FROM v_recurrentoperation_display r "
                "INNER JOIN (SELECT MAX(d_date) as d_date, r_recurrentoperation_id FROM v_operation_display GROUP BY r_recurrentoperation_id) r2 "
                "WHERE r2.r_recurrentoperation_id=r.id AND r.d_PREVIOUS!=r2.d_date", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QStringList line = result.at(i);
            int idRecu = SKGServices::stringToInt(line.at(0));
            QString currentDate = line.at(1);
            QString newDate = line.at(2);
            if (SKGServices::stringToTime(newDate).date() > QDate::currentDate() && SKGServices::stringToTime(newDate).date() != SKGServices::stringToTime(currentDate).date()) {
                SKGRecurrentOperationObject recu(m_currentBankDocument, idRecu);
                QString name = recu.getDisplayName();

                SKGAdvice ad;
                ad.setUUID("skgscheduledplugin_newdate|" % line.at(0) % ';' % newDate);
                ad.setPriority(4);
                ad.setShortMessage(i18nc("Advice on making the best (short)", "Scheduled operation '%1' not uptodate", name));
                ad.setLongMessage(i18nc("Advice on making the best (long)", "The scheduled operation '%1' does not have the date aligned with the last inserted operation (%2)", name, currentDate));
                QList<SKGAdvice::SKGAdviceAction> autoCorrections;
                {
                    SKGAdvice::SKGAdviceAction a;
                    a.Title = i18nc("Advice on making the best (action)", "Update the next scheduled operation date (%1)", newDate);
                    a.IconName = "system-run";
                    a.IsRecommended = true;
                    autoCorrections.push_back(a);
                }
                ad.setAutoCorrections(autoCorrections);
                output.push_back(ad);
            }
        }
    }

    // Possible recurrent operations
    if (!iIgnoredAdvice.contains("skgscheduledplugin_possibleschedule")) {
        SKGStringListList result;

        m_currentBankDocument->executeSelectSqliteOrder("SELECT op1.id, op1.t_displayname FROM v_operation_displayname op1 WHERE op1.id||'#'||op1.f_QUANTITY IN (SELECT op1.id||'#'||op2.f_QUANTITY FROM operation op1, v_operation_tmp1 op2 "
                "WHERE op1.rd_account_id=op2.rd_account_id AND op1.r_payee_id=op2.r_payee_id AND op1.rc_unit_id=op2.rc_unit_id "
                "AND op1.r_recurrentoperation_id=0 AND op1.d_date<>'0000-00-00' AND op1.d_date=date(op2.d_date, '+1 month') "
                "AND op1.d_date>(SELECT date('now','-2 month')) AND op2.d_date>(SELECT date('now','-3 month'))) "
                "AND op1.t_TRANSFER='N' "
                "AND NOT EXISTS (SELECT 1 FROM recurrentoperation ro, v_operation_tmp1 rop  WHERE ro.rd_operation_id=rop.id AND ro.i_period_increment=1 AND ro.t_period_unit='M' AND op1.rd_account_id=rop.rd_account_id AND op1.r_payee_id=rop.r_payee_id AND op1.f_QUANTITY=rop.f_QUANTITY AND op1.rc_unit_id=rop.rc_unit_id)", result);
        int nb = result.count();
        for (int i = 1; i < nb; ++i) {  // Ignore header
            // Get parameters
            QStringList line = result.at(i);
            QString id = line.at(0);
            QString name = line.at(1);

            SKGAdvice ad;
            ad.setUUID("skgscheduledplugin_possibleschedule|" % id);
            ad.setPriority(4);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Possible schedule '%1'", name));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "The operation '%1' seems to be regularly created and could be scheduled", name));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = i18nc("Advice on making the best (action)", "Monthly schedule the operation '%1'", name);
                a.IconName = icon();
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }
    m_counterAdvice++;
    return output;
}

SKGError SKGScheduledPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
{
    SKGError err;
    if (m_currentBankDocument && iAdviceIdentifier.startsWith(QLatin1String("skgscheduledplugin_notuptodate|"))) {
        // Get parameters
        QString parameters = iAdviceIdentifier.right(iAdviceIdentifier.length() - 31);
        int pos = parameters.indexOf(';');
        int idOper = SKGServices::stringToInt(parameters.left(pos));
        double amount = SKGServices::stringToDouble(parameters.right(parameters.length() - 1 - pos));

        // Update the operation
        {
            SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Update scheduled operation"), err);
            SKGOperationObject op(m_currentBankDocument, idOper);
            SKGObjectBase::SKGListSKGObjectBase subOps;
            IFOKDO(err, op.getSubOperations(subOps))

            if (subOps.count() == 1) {
                // Change the quantity of the sub operation
                SKGSubOperationObject so1(subOps.at(0));
                IFOKDO(err, so1.setQuantity(amount))
                IFOKDO(err, so1.save())
            } else if (subOps.count() >= 1) {
                // Add a split
                SKGSubOperationObject so1;
                IFOKDO(err, op.addSubOperation(so1))
                IFOKDO(err, so1.setQuantity(amount - op.getCurrentAmount()))
                IFOKDO(err, so1.save())
            }

            // Send message
            IFOKDO(err, op.getDocument()->sendMessage(i18nc("An information to the user", "The amount of the scheduled operation of '%1' have been updated", op.getDisplayName()), SKGDocument::Hidden));
        }

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Scheduled operation updated.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message", "Update failed"));
        }

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

        return err;
    } else if (m_currentBankDocument && iAdviceIdentifier.startsWith(QLatin1String("skgscheduledplugin_newdate|"))) {
        // Get parameters
        QString parameters = iAdviceIdentifier.right(iAdviceIdentifier.length() - 27);
        int pos = parameters.indexOf(';');
        int id = SKGServices::stringToInt(parameters.left(pos));
        QString newDate = parameters.right(parameters.length() - 1 - pos);

        // Update the operation
        {
            SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Update scheduled operation"), err);
            SKGRecurrentOperationObject rop(m_currentBankDocument, id);
            IFOKDO(err, rop.setDate(SKGServices::stringToTime(newDate).date()))
            IFOKDO(err, rop.save())

            // Send message
            IFOKDO(err, rop.getDocument()->sendMessage(i18nc("An information to the user", "The date of the scheduled operation of '%1' have been updated", rop.getDisplayName()), SKGDocument::Hidden));
        }

        // status bar
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Scheduled operation updated.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message", "Update failed"));
        }

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

        return err;
    } else if (m_currentBankDocument && iAdviceIdentifier.startsWith(QLatin1String("skgscheduledplugin_possibleschedule|"))) {
        // Get parameters
        int idOper = SKGServices::stringToInt(iAdviceIdentifier.right(iAdviceIdentifier.length() - 36));

        // Update the operation
        {
            SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Scheduled operation"), err);
            SKGOperationObject op(m_currentBankDocument, idOper);
            SKGRecurrentOperationObject rop;
            err = scheduleOperation(op, rop);
            IFOKDO(err, rop.setPeriodUnit(SKGRecurrentOperationObject::MONTH))
            IFOKDO(err, rop.setPeriodIncrement(1))
            IFOKDO(err, rop.setDate(op.getDate()))
            IFOKDO(err, rop.setDate(rop.getNextDate()))
            IFOKDO(err, rop.save())

            // Send message
            IFOKDO(err, rop.getDocument()->sendMessage(i18nc("An information to the user", "The scheduled operation of '%1' have been added", rop.getDisplayName()), SKGDocument::Hidden));

            m_counterAdvice = 0;  // To force the update
        }

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

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

        return err;
    }


    return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
}

#include <skgscheduledplugin.moc>
