/***************************************************************************
 *   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 "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"

#include <kactioncollection.h>
#include <kstandardaction.h>
#include <kaboutdata.h>
#include <kgenericfactory.h>
#include <kstandarddirs.h>

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

SKGScheduledPlugin::SKGScheduledPlugin(QWidget* /*iWidget*/, QObject* iParent, const QVariantList& /*iArg*/) : SKGInterfacePlugin(iParent), m_counterAdvice(0)
{
    SKGTRACEIN(10, "SKGScheduledPlugin::SKGScheduledPlugin");
}

SKGScheduledPlugin::~SKGScheduledPlugin()
{
    SKGTRACEIN(10, "SKGScheduledPlugin::~SKGScheduledPlugin");
    m_currentBankDocument = NULL;
    m_scheduleOperationAction = NULL;
}

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

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

    //Create yours actions here
    m_scheduleOperationAction = new KAction(KIcon("skrooge_schedule"), i18nc("Verb, create a scheduled operation", "Schedule"), this);
    connect(m_scheduleOperationAction, SIGNAL(triggered(bool)), this, SLOT(onScheduleOperation()));
    m_scheduleOperationAction->setShortcut(Qt::CTRL + Qt::Key_I);
    registerGlobalAction("schedule_operation", m_scheduleOperationAction);
    return true;
}

void SKGScheduledPlugin::refresh()
{
    SKGTRACEIN(10, "SKGScheduledPlugin::refresh");
    if (SKGMainPanel::getMainPanel()) {
        SKGObjectBase::SKGListSKGObjectBase selection = SKGMainPanel::getMainPanel()->getSelectedObjects();

        if (selection.count() > 0) {
            bool onOperation = (selection.at(0).getRealTable() == "operation" &&  selection.at(0).getTable() != "v_operation_consolidated");
            if (m_scheduleOperationAction) m_scheduleOperationAction->setEnabled(onOperation);
        } else {
            if (m_scheduleOperationAction) m_scheduleOperationAction->setEnabled(false);
        }

        //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;
                m_cacheAdvice.clear();

                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)
{
    if (iIndex == 0) return i18nc("Noun, the title of a section", "Scheduled operations");
    return "";
}

SKGBoardWidget* SKGScheduledPlugin::getDashboardWidget(int iIndex)
{
    if (iIndex == 0) return new SKGHtmlBoardWidget(m_currentBankDocument,
                                getDashboardWidgetTitle(iIndex),
                                KStandardDirs().findResource("data", "skrooge/html/default/scheduled_operations.html"),
                                QStringList() << "v_recurrentoperation_display");
    return NULL;
}

SKGTabPage* SKGScheduledPlugin::getWidget()
{
    SKGTRACEIN(10, "SKGScheduledPlugin::getWidget");
    return new SKGScheduledPluginWidget(m_currentBankDocument);
}

QWidget* SKGScheduledPlugin::getPreferenceWidget()
{
    SKGTRACEIN(10, "SKGScheduledPlugin::getPreferenceWidget");
    QWidget* widget = new QWidget();
    ui.setupUi(widget);
    return widget;
}

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;
}

bool SKGScheduledPlugin::isEnabled() 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;

                    if (!err) recOp.getParentOperation(operationObj);

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

                    if (!err) err = recOp.setParentOperation(operationObj);
                    if (!err) err = recOp.save();
                    if (!err) err = recOp.load();

                    if (!err) err = operationObjOrig.setAttribute("r_recurrentoperation_id", SKGServices::intToString(recOp.getID()));
                    if (!err) err = operationObjOrig.save();

                    if (!err) err = m_currentBankDocument->stepForward(i + 1);
                }
                if (!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;
        if (!err) err = operationObjOrig.duplicate(operationObjDuplicate, operationObjOrig.getDate(), true);
        if (!err) m_currentBankDocument->sendMessage(i18nc("An information message",  "A template has been created"), SKGDocument::Positive);
    }

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

    oRecurrent = recOp;

    return err;
}

void SKGScheduledPlugin::onScheduleOperation()
{
    SKGError err;
    SKGTRACEINRC(10, "SKGScheduledPlugin::onScheduleOperation", 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);
                if (!err) err = m_currentBankDocument->stepForward(i + 1);
                list.push_back(operationObj.getUniqueID());
            }

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

        //status bar
        if (!err)  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)
{
    SKGTRACEIN(10, "SKGScheduledPlugin::advice");
    SKGAdviceList output;

    //Recurrent operation with the last inserted operation having a different amount
    if (!iIgnoredAdvice.contains("skgscheduledplugin_notuptodate")) {
        SKGTRACEIN(10, "SKGScheduledPlugin::advice-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));
            QStringList autoCorrections;
            autoCorrections.push_back(i18nc("Advice on making the best (action)", "Update the operation amount (%1)", amountLastOperation));
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    //Possible recurrent operations
    if (!iIgnoredAdvice.contains("skgscheduledplugin_possibleschedule")) {
        SKGTRACEIN(10, "SKGScheduledPlugin::advice-skgscheduledplugin_possibleschedule");
        if (m_counterAdvice % 10 != 0) {
            output.append(m_cacheAdvice);
        } else {
            m_cacheAdvice.clear();
            SKGStringListList result;
            m_currentBankDocument->executeSelectSqliteOrder("SELECT op1.id, op1.t_displayname FROM v_operation_displayname 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.d_date>(SELECT date('now','-2 month')) AND op2.d_date>(SELECT date('now','-3 month')) "
                    "AND op1.t_TRANSFER='N' AND op1.r_recurrentoperation_id=0 AND op1.d_date<>'0000-00-00' AND op1.f_QUANTITY=op2.f_QUANTITY AND op1.d_date=date(op2.d_date, '+1 month') "
                    "AND NOT EXISTS (SELECT 1 FROM recurrentoperation ro, v_operation_tmp1 op2  WHERE ro.rd_operation_id=op2.id AND ro.i_period_increment=1 AND ro.t_period_unit='M' AND op1.rd_account_id=op2.rd_account_id AND op1.r_payee_id=op2.r_payee_id AND op1.f_QUANTITY=op2.f_QUANTITY AND op1.rc_unit_id=op2.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));
                QStringList autoCorrections;
                autoCorrections.push_back(i18nc("Advice on making the best (action)", "Monthly schedule the operation '%1'", name));
                ad.setAutoCorrections(autoCorrections);
                output.push_back(ad);
                m_cacheAdvice.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;
            if (!err) err = op.getSubOperations(subOps);

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

        //status bar
        if (!err) 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);
            if (!err) err = rop.setPeriodUnit(SKGRecurrentOperationObject::MONTH);
            if (!err) err = rop.setPeriodIncrement(1);
            if (!err) err = rop.setDate(op.getDate());
            if (!err) err = rop.setDate(rop.getNextDate());
            if (!err) err = rop.save(true, false);

            m_counterAdvice = 0; //To force the update
        }

        //status bar
        if (!err) 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"
