/***************************************************************************
 *   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 defines classes SKGImportExportManager.
*
* @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgimportexportmanager.h"
#include "skgtraces.h"
#include "skgservices.h"
#include "skgbankincludes.h"
#include "skgobjectbase.h"
#include "skgruleobject.h"
#include "skgpayeeobject.h"
#include "skgrecurrentoperationobject.h"
#include "skgimportplugin.h"

#include <klocale.h>
#include <ksavefile.h>
#include <kservicetypetrader.h>

#include <QRegExp>
#include <QFileInfo>
#include <QTextCodec>
#include <QCryptographicHash>
#include <QTemporaryFile>

SKGImportExportManager::SKGImportExportManager(SKGDocumentBank* iDocument,
        const KUrl& iFileName)
    : QObject(), m_document(iDocument), m_fileName(iFileName),
      m_defaultAccount(NULL), m_defaultUnit(NULL),
      m_importPlugin(NULL), m_exportPlugin(NULL)
{
    setAutomaticValidation(true);
    setAutomaticApplyRules(false);
}

SKGImportExportManager::~SKGImportExportManager()
{
    setDefaultAccount(NULL);
    setDefaultUnit(NULL);
    m_document = NULL;
    m_defaultAccount = NULL;
    m_defaultUnit = NULL;
    m_importPlugin = NULL;
    m_exportPlugin = NULL;

    if (!m_localFileName.isEmpty() && m_localFileName != getFileName().toLocalFile()) QFile(m_localFileName).remove();
}

QString SKGImportExportManager::getFileNameExtension() const
{
    return QFileInfo(getFileName().path()).suffix().toUpper();
}

KUrl SKGImportExportManager::getFileName() const
{
    return m_fileName;
}

QString SKGImportExportManager::getLocalFileName(bool iDownload)
{
    if (m_localFileName.isEmpty()) {
        if (getFileName().isLocalFile()) m_localFileName = getFileName().toLocalFile();
        else {
            if (iDownload) SKGServices::download(KUrl(getFileName()), m_localFileName);
            else {
                QTemporaryFile tmpFile;
                tmpFile.setAutoRemove(false);
                tmpFile.open();
                m_localFileName = tmpFile.fileName();
            }
        }
    }
    return m_localFileName;
}

SKGError SKGImportExportManager::setDefaultAccount(SKGAccountObject* iAccount)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGImportExportManager::setDefaultAccount", err);
    delete m_defaultAccount;
    m_defaultAccount = NULL;
    if (iAccount) m_defaultAccount = new SKGAccountObject(*iAccount);
    return err;
}

SKGError SKGImportExportManager::getDefaultAccount(SKGAccountObject& oAccount)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGImportExportManager::getDefaultAccount", err);
    if (m_defaultAccount == NULL && m_document) {
        QFileInfo fInfo(getLocalFileName());
        QStringList items = fInfo.baseName().split('@');
        QString bankname;
        QString number;
        QString name = items.at(0);
        if (items.count() == 2) bankname = items.at(1);
        else bankname = name;
        name.replace('_', ' ');
        name.replace('-', ' ');
        QRegExp rx("(\\d{6,})");
        if (rx.indexIn(name) != -1) {
            number = rx.cap(1);
            if (name != number) name = name.remove(number).trimmed();
        }

        //Searching if an account exist
        QString whereClause = "t_name='" % name % '\'';
        if (!number.isEmpty()) whereClause += " OR t_number='" % number % "'";
        foreach(const QString & val, name.split(' ')) {
            whereClause += " OR t_number='" % val % "'";
        }
        if (!whereClause.isEmpty()) {
            SKGObjectBase::SKGListSKGObjectBase listAccount;
            err = m_document->getObjects("v_account", whereClause, listAccount);
            if (!err) {
                if (listAccount.count() == 1) {
                    //Yes ! Only one account found
                    SKGAccountObject account(listAccount.at(0));
                    m_defaultAccount = new SKGAccountObject(account);
                    err = m_document->sendMessage(i18nc("An information message",  "Using account '%1' for import", account.getName()));
                } else if (listAccount.count() > 1) {
                    err = m_document->sendMessage(i18nc("An information message",  "More than one possible account found."));
                }
            }
        }

        //If better account not found, then we must create one
        if (m_defaultAccount == NULL) {
            SKGAccountObject account;
            SKGBankObject bank(m_document);
            if (!err) err = bank.setName(bankname);
            if (!err && bank.load().isFailed()) err = bank.save(false);    //Save only
            if (!err) err = bank.addAccount(account);
            if (!err) err = account.setName(name);
            if (!err) err = account.setNumber(number);
            if (!err && account.load().isFailed()) err = account.save(false);    //Save only
            if (!err) m_defaultAccount = new SKGAccountObject(account);
            if (!err) err = m_document->sendMessage(i18nc("An information message",  "Default account '%1' created for import", name));
        }
    }

    if (m_defaultAccount != NULL)  oAccount = *m_defaultAccount;

    return err;
}

SKGError SKGImportExportManager::setDefaultUnit(SKGUnitObject* iUnit)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGImportExportManager::setDefaultUnit", err);
    delete m_defaultUnit;
    m_defaultUnit = NULL;
    if (iUnit) m_defaultUnit = new SKGUnitObject(*iUnit);
    return err;
}

SKGError SKGImportExportManager::getDefaultUnit(SKGUnitObject& oUnit, const QDate* iDate)
{
    SKGError err;
    SKGTRACEINRC(10, "SKGImportExportManager::getDefaultUnit", err);
    if (m_document && (m_defaultUnit == NULL || iDate)) {
        if (m_defaultUnit == NULL) {
            delete m_defaultUnit;
            m_defaultUnit = NULL;
        }

        //Do we have to found the best unit for a date ?
        QString wc = "t_type IN ('1', '2', 'C')";
        if (iDate) {
            //Yes
            wc += " AND d_MINDATE<'" % SKGServices::dateToSqlString(QDateTime(*iDate)) % '\'';
        }

        //Check if a unit exist
        SKGObjectBase::SKGListSKGObjectBase listUnits;
        err = m_document->getObjects("v_unit", wc % " ORDER BY ABS(f_CURRENTAMOUNT-1) ASC" , listUnits);
        if (!err) {
            if (listUnits.count() == 0) {
                //Not found, we have to create one
                QDateTime now = QDateTime::currentDateTime();

                SKGUnitObject unit(m_document);
                QString name = i18nc("Noun",  "Unit for import");
                err = unit.setName(name);
                if (unit.load().isFailed()) {
                    if (!err) err = unit.setSymbol(name);
                    if (!err) err = unit.save(false);    //Save only

                    SKGUnitValueObject unitval;
                    if (!err) err = unit.addUnitValue(unitval);
                    if (!err) err = unitval.setQuantity(1);
                    if (!err) err = unitval.setDate(QDate(1970, 1, 1));
                    if (!err) err = unitval.save(false, false);    //Save only without reload

                    if (!err) err = m_document->sendMessage(i18nc("An information message",  "Default unit '%1' created for import", name));
                }

                if (!err) m_defaultUnit = new SKGUnitObject(unit);
            } else {
                //Found, we can use it
                m_defaultUnit = new SKGUnitObject((SKGUnitObject) listUnits.at(0));
            }
        }
    }

    if (m_defaultUnit != NULL) {
        oUnit = *m_defaultUnit;
    }

    return err;
}

void SKGImportExportManager::setAutomaticValidation(bool iValidation)
{
    m_automaticValidationOfImportedOperation = iValidation;
}

bool SKGImportExportManager::automaticValidation() const
{
    return m_automaticValidationOfImportedOperation;
}

void SKGImportExportManager::setAutomaticApplyRules(bool iValidation)
{
    m_automaticApplyRulesOfImportedOperation = iValidation;
}

bool SKGImportExportManager::automaticApplyRules() const
{
    return m_automaticApplyRulesOfImportedOperation;
}

void SKGImportExportManager::setSinceLastImportDate(bool iSinceLast)
{
    m_since_last_import = iSinceLast;
}

bool SKGImportExportManager::sinceLastImportDate() const
{
    return m_since_last_import;
}

SKGError SKGImportExportManager::finalizeImportation()
{
    SKGError err;
    SKGTRACEINRC(2, "SKGImportExportManager::finalizeImportation", err);
    if (m_document && !getImportParameters().contains("donotfinalize")) {
        //Count the number of operations imported but already existing
        QString wc = "v_operation.t_imported='T' AND v_operation.t_import_id<>'' AND exists (SELECT 1 from v_operation op2 WHERE op2.t_imported!='T' AND op2.rd_account_id=v_operation.rd_account_id AND op2.t_import_id=v_operation.t_import_id AND ABS(op2.f_CURRENTAMOUNT-v_operation.f_CURRENTAMOUNT)<" % SKGServices::doubleToString(EPSILON) % ')';
        int nb;
        err = m_document->getNbObjects("v_operation", wc, nb);
        if (!err && nb) {
            err = m_document->sendMessage(i18np("one operation not imported because it already exists", "%1 operations not imported because they already exists", nb), SKGDocument::Warning);

            //Remove operations imported for nothing
            if (!err) err = m_document->executeSqliteOrder("DELETE from operation WHERE id IN (SELECT id from v_operation WHERE " % wc % ')');
        }

        //Delete operations before the last import
        if (m_since_last_import && !err) {
            SKGTRACEINRC(2, "SKGImportExportManager::finalizeImportation-deletebeforelastimport", err);
            err = m_document->executeSqliteOrder("DELETE FROM operation WHERE operation.t_imported='T' AND "
                                                 "EXISTS(SELECT 1 FROM operation o INDEXED BY idx_operation_rd_account_id_t_imported WHERE o.rd_account_id=operation.rd_account_id AND o.t_imported IN ('Y', 'P') AND o.d_date>operation.d_date)");
        }

        //For performances
        if (!err) err = m_document->executeSqliteOrder("ANALYZE");

        //Apply rules
        if (!err && m_automaticApplyRulesOfImportedOperation) {
            SKGTRACEINRC(2, "SKGImportExportManager::finalizeImportation-applyrules", err);
            //Get rules
            SKGObjectBase::SKGListSKGObjectBase rules;
            err = m_document->getObjects("v_rule", "1=1 ORDER BY f_sortorder", rules);

            int nb = rules.count();
            err = m_document->beginTransaction("#INTERNAL#" % i18nc("Progression step", "Finalize import"), nb);
            for (int i = 0; !err && i < nb; ++i) {
                SKGRuleObject rule(rules.at(i));
                err = rule.execute(SKGRuleObject::IMPORTING);
                if (!err) err = m_document->stepForward(i + 1);
            }
            if (!err) err = m_document->endTransaction(true);
            else m_document->endTransaction(false);

            //A failure in rule will not block the transaction.
            //A warning message is sent
            if (err) err = m_document->sendMessage(i18nc("Warning message", "Error during execution of rules:\n%1", err.getFullMessageWithHistorical()), SKGDocument::Error);
        }

        //Change imported status
        if (!err) {
            SKGTRACEINRC(2, "SKGImportExportManager::finalizeImportation-setfinalstatus", err);
            err = m_document->executeSqliteOrder(QString("UPDATE operation SET t_imported='") %
                                                 (m_automaticValidationOfImportedOperation ? "Y" : "P") %
                                                 "' WHERE t_imported='T'");
        }
    }
    return err;
}

void SKGImportExportManager::setImportParameters(const QMap< QString, QString >& iParameters)
{
    SKGImportPlugin* plugin = getImportPlugin();
    if (plugin) plugin->setImportParameters(iParameters);
}

QMap< QString, QString > SKGImportExportManager::getImportParameters()
{
    QMap< QString, QString > output;
    SKGImportPlugin* plugin = getImportPlugin();
    if (plugin) output = plugin->getImportParameters();
    return output;
}

QString SKGImportExportManager::getImportMimeTypeFilter()
{
    QMap<QString, QString> tmp;
    KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("SKG IMPORT/Plugin"));
    int nb = offers.count();
    for (int i = 0; i < nb; ++i) {
        KService::Ptr service = offers.at(i);
        KPluginLoader loader(service->library());
        KPluginFactory* factory = loader.factory();
        if (factory) {
            SKGImportPlugin* pluginInterface = factory->create<SKGImportPlugin> (0);
            if (pluginInterface && pluginInterface->isImportPossible()) {
                QString mime = pluginInterface->getMimeTypeFilter();
                if (!mime.isEmpty()) {
                    QStringList lines = SKGServices::splitCSVLine(mime, '\n');
                    int nblines = lines.count();
                    for (int l = 0; l < nblines; ++l) {
                        QStringList items = SKGServices::splitCSVLine(lines.at(l), '|');
                        tmp[items.at(1)] = items.at(0);
                    }
                }
            }
        }
    }

    QStringList descriptions = tmp.keys();
    qSort(descriptions);
    QStringList regexps = tmp.values();

    QString output = regexps.join(" ") % '|' % i18nc("A file format", "All supported formats");
    nb = descriptions.count();
    for (int i = 0; i < nb; ++i) {
        if (!output.isEmpty()) output += '\n';
        output += tmp[descriptions.at(i)] % '|' % descriptions.at(i);
    }

    return output;
}

QString SKGImportExportManager::getExportMimeTypeFilter()
{
    QMap<QString, QString> tmp;
    KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("SKG IMPORT/Plugin"));
    int nb = offers.count();
    for (int i = 0; i < nb; ++i) {
        KService::Ptr service = offers.at(i);
        KPluginLoader loader(service->library());
        KPluginFactory* factory = loader.factory();
        if (factory) {
            SKGImportPlugin* pluginInterface = factory->create<SKGImportPlugin> (0);
            if (pluginInterface && pluginInterface->isExportPossible()) {
                QString mime = pluginInterface->getMimeTypeFilter();
                if (!mime.isEmpty()) {
                    QStringList lines = SKGServices::splitCSVLine(mime, '\n');
                    int nblines = lines.count();
                    for (int l = 0; l < nblines; ++l) {
                        QStringList items = SKGServices::splitCSVLine(lines.at(l), '|');
                        tmp[items.at(1)] = items.at(0);
                    }
                }
            }
        }
    }

    QStringList descriptions = tmp.keys();
    qSort(descriptions);
    QStringList regexps = tmp.values();

    QString output = regexps.join(" ") % '|' % i18nc("A file format", "All supported formats");
    nb = descriptions.count();
    for (int i = 0; i < nb; ++i) {
        if (!output.isEmpty()) output += '\n';
        output += tmp[descriptions.at(i)] % '|' % descriptions.at(i);
    }

    return output;
}


SKGImportPlugin* SKGImportExportManager::getImportPlugin()
{
    if (!m_importPlugin) {
        KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("SKG IMPORT/Plugin"));
        int nb = offers.count();
        for (int i = 0; !m_importPlugin && i < nb; ++i) {
            KService::Ptr service = offers.at(i);
            QString id = service->property("X-Krunner-ID", QVariant::String).toString();
            KPluginLoader loader(service->library());
            KPluginFactory* factory = loader.factory();
            if (factory) {
                SKGImportPlugin* pluginInterface = factory->create<SKGImportPlugin> (this);
                if (pluginInterface && pluginInterface->isImportPossible()) {
                    //Import
                    m_importPlugin = pluginInterface;
                }
            } else {
                if (m_document) m_document->sendMessage(i18nc("An information message",  "Loading plugin %1 failed because the factory could not be found in %2", id, service->library()), SKGDocument::Error);
            }
        }
    }
    return m_importPlugin;
}

SKGImportPlugin* SKGImportExportManager::getExportPlugin()
{
    if (!m_exportPlugin) {
        KService::List offers = KServiceTypeTrader::self()->query(QLatin1String("SKG IMPORT/Plugin"));
        int nb = offers.count();
        for (int i = 0; !m_exportPlugin && i < nb; ++i) {
            KService::Ptr service = offers.at(i);
            QString id = service->property("X-Krunner-ID", QVariant::String).toString();
            KPluginLoader loader(service->library());
            KPluginFactory* factory = loader.factory();
            if (factory) {
                SKGImportPlugin* pluginInterface = factory->create<SKGImportPlugin> (this);
                if (pluginInterface && pluginInterface->isExportPossible()) {
                    //Import
                    m_exportPlugin = pluginInterface;
                }
            } else {
                if (m_document) m_document->sendMessage(i18nc("An information message",  "Loading plugin %1 failed because the factory could not be found in %2", id, service->library()), SKGDocument::Error);
            }
        }
    }
    return m_exportPlugin;
}

SKGError SKGImportExportManager::importFile()
{
    SKGError err;
    SKGTRACEINRC(2, "SKGImportExportManager::importFile", err);
    if (m_document) {
        SKGBEGINPROGRESSTRANSACTION(*m_document, i18nc("Noun, name of the user action", "Import with codec %1", m_codec), err, 3);

        err = m_document->executeSqliteOrder("ANALYZE");

        if (!err) err = m_document->stepForward(1);

        if (!err) {
            //Search plugins
            bool fileTreated = false;
            SKGImportPlugin* pluginInterface = getImportPlugin();
            if (pluginInterface) {
                //Import
                fileTreated = true;
                SKGTRACEL(2) << "Input filename=" << m_fileName.prettyUrl() << endl;
                SKGTRACEL(2) << "Input local filename=" << getLocalFileName() << endl;
                err = pluginInterface->importFile();
            }

            if (!err && !fileTreated)err.setReturnCode(ERR_NOTIMPL).setMessage(i18nc("Error message",  "This import mode is not yet implemented"));
        }
        if (!err) err = m_document->stepForward(2);

        if (!err) err = finalizeImportation();
        if (!err) err = m_document->stepForward(3);
    }

    return err;
}

SKGError SKGImportExportManager::exportFile()
{
    SKGError err;
    SKGTRACEINRC(2, "SKGImportExportManager::exportFile", err);
    if (m_document) {
        err = m_document->executeSqliteOrder("ANALYZE");
        if (!err) {
            //Search plugins
            bool fileTreated = false;
            SKGImportPlugin* pluginInterface = getExportPlugin();
            if (pluginInterface) {
                //Import
                fileTreated = true;
                SKGTRACEL(2) << "Input filename=" << m_fileName.prettyUrl() << endl;
                SKGTRACEL(2) << "Input local filename=" << getLocalFileName(false) << endl;
                err = pluginInterface->exportFile();
                if (!err) err = SKGServices::upload(KUrl::fromLocalFile(getLocalFileName(false)), m_fileName);
            }

            if (!err && !fileTreated) err.setReturnCode(ERR_NOTIMPL).setMessage(i18nc("Error message",  "This export mode is not yet implemented"));
        }
    }

    return err;
}

SKGError SKGImportExportManager::cleanBankImport()
{
    SKGError err;
    SKGTRACEINRC(2, "SKGImportExportManager::cleanBankImport", err);

    //Begin transaction
    if (m_document) {
        err = m_document->beginTransaction("#INTERNAL#" % i18nc("Progression step", "Clean import"), 3);
        if (!err) {
            //Step 1 Clean operations without mode and with comment with double space
            SKGObjectBase::SKGListSKGObjectBase operations;
            if (!err) err = m_document->getObjects("operation", "t_imported!='N' and t_mode='' and t_comment like '%  %'", operations);

            int nb = operations.count();
            for (int i = 0; !err && i < nb ; ++i) {
                SKGOperationObject op(operations[i]);

                //Comment is like this: <TYPE>  <INFO>
                //Example: RETRAIT DAB             20/01/08 11H44 013330 LCL GAILLAC 000497
                QRegExp rx("(.+) {2,}(.+)");
                QString comment = op.getComment();
                if (rx.indexIn(comment) != -1) {
                    //Get parameters
                    QString mode = rx.cap(1);
                    QString info = rx.cap(2);

                    //Modify
                    err = op.setComment(info.trimmed());
                    if (!err)  err = op.setMode(mode.trimmed());
                    if (!err)  err = op.save(true, false);    //No reload
                }
            }

            //Step 1 done
            if (!err) err = m_document->stepForward(1);

            //Step 2 Clean operations without mode and with comment
            if (!err) err = m_document->getObjects("operation", "t_imported!='N' and t_mode='' and t_comment!=''", operations);

            nb = operations.count();
            for (int i = 0; !err && i < nb ; ++i) {
                SKGOperationObject op(operations[i]);

                //Comment is like this: <TYPE> <INFO>
                //Example: RETRAIT DAB 14-05-16607-482390
                QRegExp rx("(\\S+) +(.+)");
                QString comment = op.getComment();
                if (rx.indexIn(comment) != -1) {
                    //Get parameters
                    QString mode = rx.cap(1);
                    QString info = rx.cap(2);

                    //Modify
                    err = op.setComment(info.trimmed());
                    if (!err)  err = op.setMode(mode.trimmed());
                    if (!err)  err = op.save(true, false);    //No reload
                }
            }

            //Step 2 done
            if (!err) err = m_document->stepForward(2);

            //Step 3 Clean cheque without number
            if (!err) err = m_document->getObjects("operation", "t_imported!='N' and i_number=0 and lower(t_mode)='cheque'", operations);

            nb = operations.count();
            for (int i = 0; !err && i < nb ; ++i) {
                SKGOperationObject op(operations[i]);

                //Comment is like this: <TYPE>  <INFO>
                //Example: RETRAIT DAB             20/01/08 11H44 013330 LCL GAILLAC 000497
                QRegExp rx("(\\d+)");
                QString comment = op.getComment();
                if (rx.indexIn(comment) != -1) {
                    //Get parameters
                    int number = SKGServices::stringToInt(rx.cap(1));

                    //Modify
                    err = op.setNumber(number);
                    if (!err)  err = op.save(true, false);    //No reload
                }
            }

            //Step 3 done
            if (!err) err = m_document->stepForward(3);
        }

        if (!err) err = m_document->endTransaction(true);
        else  m_document->endTransaction(false);
    }

    return err;
}

SKGError SKGImportExportManager::anonymize()
{
    SKGError err;
    SKGTRACEINRC(2, "SKGImportExportManager::anonymize", err);
    if (m_document) {
        if (m_document->isFileModified()) {
            err = SKGError(ERR_ABORT, i18nc("An information message",  "The document must be saved to be anonymized."));
        } else {
            {
                QStringList sqlOrders;
                sqlOrders << "UPDATE bank SET t_bank_number='', t_name='bank_'||id"
                          << "UPDATE account SET t_number='', t_agency_number='', t_agency_address='', t_comment='', t_name='account_'||id"
                          << "UPDATE category SET t_name='category_'||id"
                          << "UPDATE payee SET t_address='', t_name='payee_'||id"
                          << "UPDATE refund SET t_comment='', t_name='tracker_'||id"
                          << "UPDATE operation SET t_comment=''"
                          << "UPDATE suboperation SET t_comment='', f_value=f_value%1234.56"
                          << "DELETE FROM parameters WHERE t_name NOT LIKE 'SKG_%' OR t_name='SKG_PASSWORD'"
                          << "DELETE FROM doctransactionitem"; //For better performances

                int nb = sqlOrders.count();
                SKGBEGINPROGRESSTRANSACTION(*m_document, "##INTERNAL##" % i18nc("Progression step", "Anonymize"), err, nb);
                for (int i = 0; !err && i < nb; ++i) {
                    err = m_document->executeSqliteOrder(sqlOrders.at(i));
                    if (!err) err = m_document->stepForward(i + 1);
                }
            }

            if (!err) err = m_document->removeAllTransactions();
        }
    }
    return err;
}

SKGError SKGImportExportManager::findAndGroupTransfers(int& oNbOperationsMerged, bool iOnCurrentlyImport)
{
    SKGError err;
    SKGTRACEINRC(2, "SKGImportExportManager::findAndGroupTransfers", err);

    oNbOperationsMerged = 0;

    //Begin transaction
    if (m_document) {
        err = m_document->beginTransaction("#INTERNAL#" % i18nc("Progression step", "Find and group transfers"), 3);
        if (!err) {
            if (!err) err = m_document->executeSqliteOrder("ANALYZE");
            //Look for operations with
            //  Same units
            //  Same dates
            //  Null i_group_id
            //  Different accounts
            //  Oposite amounts
            SKGStringListList listTmp;
            if (!err) err = m_document->executeSelectSqliteOrder(
                                    "SELECT A.id, B.id FROM v_operation_tmp1 A, v_operation_tmp1 B WHERE +A.d_date=B.d_date AND A.rc_unit_id=B.rc_unit_id AND A.id<=B.id AND A.rd_account_id!=B.rd_account_id AND ABS(A.f_QUANTITY+B.f_QUANTITY)<" % SKGServices::doubleToString(EPSILON) % " AND A.i_group_id=0 AND B.i_group_id=0 AND A.f_QUANTITY!=0" %
                                    (iOnCurrentlyImport ? " AND A.t_imported='T' AND B.t_imported='T'" : ""),
                                    listTmp);
            //Step 1 done
            if (!err) err = m_document->stepForward(1);

            SKGStringListList listTmp2;
            //+A.i_group_id=0 AND +B.i_group_id=0 is for avoiding to use bad index
            if (!err) err = m_document->executeSelectSqliteOrder(
                                    "SELECT A.id, B.id FROM v_operation A, v_operation_tmp1 B, parameters P WHERE +P.t_uuid_parent=B.id||'-operation' AND A.rc_unit_id!=B.rc_unit_id AND P.t_name='SKG_OP_ORIGINAL_AMOUNT' AND A.d_date=B.d_date AND A.rd_account_id!=B.rd_account_id AND ABS(A.f_CURRENTAMOUNT+CAST(P.t_value AS REAL))<" % SKGServices::doubleToString(EPSILON) % " AND +A.i_group_id=0 AND +B.i_group_id=0 AND A.f_CURRENTAMOUNT!=0" %
                                    (iOnCurrentlyImport ? " AND A.t_imported='T' AND B.t_imported='T'" : ""),
                                    listTmp2);

            //Step 2 done
            if (!err) err = m_document->stepForward(2);
            listTmp2.removeAt(0);    //Remove header
            listTmp += listTmp2;

            //Group
            {
                oNbOperationsMerged = listTmp.count();
                if (!err) err = m_document->beginTransaction("#INTERNAL#" % i18nc("Progression step", "Find and group transfers"), oNbOperationsMerged - 1);
                for (int i = 1; !err && i < oNbOperationsMerged ; ++i) { //First line ignored because of it's header
                    SKGOperationObject op1(m_document, SKGServices::stringToInt(listTmp.at(i).at(0)));
                    SKGOperationObject op2(m_document, SKGServices::stringToInt(listTmp.at(i).at(1)));

                    if (!op1.isInGroup() && !op2.isInGroup()) {
                        err = op2.setGroupOperation(op1);
                        if (!err) err = op2.save(true, false);    //No reload
                    }
                    if (!err) err = m_document->stepForward(i);
                }
                if (!err) err = m_document->endTransaction(true);
                else  m_document->endTransaction(false);
            }
            oNbOperationsMerged = (oNbOperationsMerged - 1) * 2;

            //Step 3 done
            if (!err) err = m_document->stepForward(3);

        }
        if (!err) err = m_document->endTransaction(true);
        else  m_document->endTransaction(false);
    }

    return err;
}

#include "skgimportexportmanager.moc"
