/***************************************************************************
 *   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 Skrooge plugin for for QIF import / export.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgimportpluginqif.h"

#include <klocalizedstring.h>

#include <kpluginfactory.h>

#include <qcryptographichash.h>
#include <qfileinfo.h>
#include <qsavefile.h>

#include "skgtraces.h"
#include "skgservices.h"
#include "skgbankincludes.h"
#include "skgimportexportmanager.h"

/**
* Opening balance string
 */
#define OPENINGBALANCE "Opening Balance"

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGImportPluginQifFactory, registerPlugin<SKGImportPluginQif>();)

SKGImportPluginQif::SKGImportPluginQif(QObject* iImporter , const QVariantList& iArg)
    : SKGImportPlugin(iImporter)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArg);

    m_parameters["date_format"] = "";
}

SKGImportPluginQif::~SKGImportPluginQif()
{
}

bool SKGImportPluginQif::isImportPossible()
{
    SKGTRACEINFUNC(10);
    return isExportPossible();
}

SKGError SKGImportPluginQif::importFile()
{
    if (!m_importer) {
        return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
    }

    SKGError err;
    SKGTRACEINFUNCRC(2, err);

    // Info for QIF format:
    // http://mb-net.net/Debian/src/gnucash/gnucash-2.2.6/src/import-export/qif-import/file-format.txt
    // http://web.intuit.com/support/quicken/docs/d_qif.html

    // Begin transaction
    err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "QIF"), 3);
    IFOK(err) {
        // Create account if needed
        QDateTime now = QDateTime::currentDateTime();
        QString postFix = SKGServices::dateToSqlString(now);

        // Step 1 done
        IFOKDO(err, m_importer->getDocument()->stepForward(1))

        // Open file
        QFile file(m_importer->getLocalFileName());
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Open file '%1' failed", m_importer->getFileName().toDisplayString()));
        } else {
            QTextStream stream(&file);
            if (!m_importer->getCodec().isEmpty()) {
                stream.setCodec(m_importer->getCodec().toLatin1().constData());
            }

            // load file in memory
            QStringList lines;
            QStringList dates;
            bool inWrongSection = false;
            bool inPriceSection = false;
            while (!stream.atEnd()) {
                // Read line
                // Check line if line is empty or is a commented
                QString line = stream.readLine().trimmed();
                if (!line.isEmpty() && line[0] != '#') {
                    lines.push_back(line);
                    // Manage !Account section
                    if (line.startsWith(QLatin1String("!"))) {
                        inWrongSection = false;
                        inPriceSection = false;
                    }

                    if (QString::compare(line, "!account", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:cat", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:class", Qt::CaseInsensitive) == 0) {
                        inWrongSection = true;
                    } else if (QString::compare(line, "!type:prices", Qt::CaseInsensitive) == 0) {
                        inPriceSection = true;
                    }

                    // We try to find automatically the date format
                    if (!inWrongSection && line[0] == 'D') {
                        dates.push_back(line.right(line.length() - 1));
                    } else if (inPriceSection) {
                        QStringList vals = SKGServices::splitCSVLine(line, ',');
                        if (vals.count() == 3) {
                            dates.push_back(vals.at(2));
                        }
                    }
                }
            }

            // close file
            file.close();

            // Select dateformat
            QString dateFormat = m_parameters["date_format"];
            if (dateFormat.isEmpty()) {
                dateFormat = SKGServices::getDateFormat(dates);    // Automatic detection
            }
            if (dateFormat.isEmpty()) {
                err.setReturnCode(ERR_FAIL).setMessage(i18nc("Error message",  "Date format not supported"));
            }
            IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message",  "Import of '%1' with code '%2' and date format '%3'", m_importer->getFileName().toDisplayString(), m_importer->getCodec(), dateFormat)))

            // Step 2 done
            IFOKDO(err, m_importer->getDocument()->stepForward(2))

            // Treat all lines
            IFOK(err) {
                SKGAccountObject* account = NULL;
                SKGOperationObject currentOperation;
                SKGOperationObject payement;
                SKGPayeeObject currentPayee;
                SKGSubOperationObject currentSubOperation;
                QDate currentOperationDate;
                QString transferAccount;
                QString stringForHash;
                QString currentUnitForInvestment;
                QChar inSection = 'B';
                bool currentOperationInitialized = false;
                bool latestSubCatMustBeRemoved = false;
                bool investmentAccount = false;
                bool div = false;
                bool automaticAccount = true;
                int quantityFactor = 1;
                double currentUnitPrice = 1;
                double checkOperationAmount = 0;
                double checkSuboperationsAmount = 0;
                bool openingbalancecreated = false;

                int nb = lines.size();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import operations"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    QString line = lines.at(i);
                    QString val;
                    QChar op = line[0];
                    if (line.length() > 1) {
                        val = line.right(line.length() - 1).trimmed();
                    }

                    // Manage !Account section
                    if (QString::compare(line, "!type:bank", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:cash", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:ccard", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:oth a", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:oth l", Qt::CaseInsensitive) == 0 ||
                        QString::compare(line, "!type:invst", Qt::CaseInsensitive) == 0) {
                        inSection = 'B';
                        openingbalancecreated = false;
                        investmentAccount = (QString::compare(val, "type:invst", Qt::CaseInsensitive) == 0);

                        // Set type of account
                        if (account == NULL) {
                            SKGAccountObject defAccount;
                            err = m_importer->getDefaultAccount(defAccount);
                            IFOKDO(err, defAccount.addOperation(currentOperation, true))
                            IFOK(err) account = new SKGAccountObject(defAccount);
                        }

                        if (!err && account) {
                            err = account->setType(QString::compare(line, "!type:bank", Qt::CaseInsensitive) == 0 ? SKGAccountObject::CURRENT :
                                                   (QString::compare(line, "!type:ccard", Qt::CaseInsensitive) == 0 ? SKGAccountObject::CREDITCARD :
                                                    (QString::compare(line, "!type:invst", Qt::CaseInsensitive) == 0 ? SKGAccountObject::INVESTMENT :
                                                     (QString::compare(line, "!type:oth a", Qt::CaseInsensitive) == 0 ? SKGAccountObject::ASSETS : SKGAccountObject::OTHER))));
                            IFOKDO(err, account->save())
                        }
                    } else if (QString::compare(line, "!account", Qt::CaseInsensitive) == 0) {
                        inSection = 'A';
                        openingbalancecreated = false;
                        automaticAccount = false;
                    } else if (QString::compare(line, "!type:cat", Qt::CaseInsensitive) == 0) {
                        inSection = 'C';
                        openingbalancecreated = false;
                        IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message",  "Categories found and imported")))
                    } else if (QString::compare(line, "!type:prices", Qt::CaseInsensitive) == 0) {
                        inSection = 'U';
                        openingbalancecreated = false;
                        IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message",  "Units prices found and imported")))
                    } else if (line.at(0) == '!') {
                        inSection = '?';
                        openingbalancecreated = false;
                    } else if (inSection == 'U') {
                        // Unit value creation
                        openingbalancecreated = false;
                        QStringList vals = SKGServices::splitCSVLine(line, ',');
                        if (vals.count() == 3 && !vals.at(0).isEmpty()) {
                            err = m_importer->getDocument()->addOrModifyUnitValue(vals.at(0), SKGServices::stringToTime(SKGServices::dateToSqlString(vals.at(2), dateFormat)).date(), SKGServices::stringToDouble(vals.at(1)));
                        }

                    } else if (inSection == 'C') {
                        // Category creation
                        openingbalancecreated = false;
                        if (op == 'N') {
                            SKGCategoryObject Category;
                            val.replace('/', OBJECTSEPARATOR);
                            val.replace(':', OBJECTSEPARATOR);
                            err = SKGCategoryObject::createPathCategory(m_importer->getDocument(), val, Category);
                        }
                    } else if (inSection == 'A') {
                        // Account creation
                        openingbalancecreated = false;
                        if (op == 'N') {
                            // Check if the account already exist
                            SKGAccountObject account2;
                            err = SKGNamedObject::getObjectByName(m_importer->getDocument(), "account", val, account2);
                            IFKO(err) {
                                // Create account
                                SKGBankObject bank(m_importer->getDocument());
                                err = bank.setName(i18nc("Noun",  "Bank for import %1", postFix));
                                if (!err && bank.load().isFailed()) {
                                    err = bank.save(false);
                                }
                                IFOKDO(err, bank.addAccount(account2))
                                IFOKDO(err, account2.setName(val))
                                if (!err && account2.load().isFailed()) {
                                    err = account2.save(false);    // Save only
                                }
                            }

                            IFOK(err) {
                                delete account;
                                account = new SKGAccountObject(account2);
                            }
                        } else if (op == 'D') {
                            if (account) {
                                err = account->setNumber(val);
                            }
                        } else if (op == 'T') {
                            if (account) {
                                err = account->setType(val == "Bank" ? SKGAccountObject::CURRENT : (val == "CCard" ? SKGAccountObject::CREDITCARD : (val == "Invst" ? SKGAccountObject::INVESTMENT : (val == "Oth A" ? SKGAccountObject::ASSETS : SKGAccountObject::OTHER))));
                            }
                        } else if (op == '^') {
                            // ^     End of entry
                            // save
                            if (account) {
                                err = account->save();
                            }
                        }
                    } else if (inSection == 'B') {
                        // Operation creation
                        /*
                        >>>> Items for Non-Investment Accounts <<<<
                        DONE    D      Date
                        DONE    T      Amount
                            U      Transaction amount (higher possible value than T)
                        DONE    C      Cleared status
                        DONE    N      Number (check or reference number)
                        DONE    P      Payee/description
                        DONE    M      Memo
                        DONE    A      Address (up to 5 lines; 6th line is an optional message)
                        DONE    L      Category (category/class or transfer/class)
                        DONE    S      Category in split (category/class or transfer/class)
                        DONE    E      Memo in split
                        DONE    $      Dollar amount of split
                            %      Percentage of split if percentages are used
                            F      Reimbursable business expense flag
                            X      Small Business extensions
                        DONE    ^      End of entry

                        >>>> Items for Investment Accounts <<<<
                        DONE    D   Date
                            N   Action
                        DONE    Y   Security
                        DONE    I   Price
                        DONE    Q   Quantity (number of shares or split ratio)
                        DONE    T   Transaction amount
                        DONE    C   Cleared status
                            P   Text in the first line for transfers and reminders
                        DONE    M   Memo
                            O   Commission
                            L   Account for the transfer
                            $   Amount transferred
                            ^   End of entry
                        */
                        stringForHash += line;
                        if (op == 'D') {
                            // D     Date
                            /*
                            Dates in US QIF files are usually in the format MM/DD/YY, although
                            four-digit years are not uncommon.  Dates sometimes occur without the
                            slash separator, or using other separators in place of the slash,
                            commonly '-' and '.'.  US Quicken seems to be using the ' to indicate
                            post-2000 two-digit years (such as 01/01'00 for Jan 1 2000).  Some
                            banks appear to be using a completely undifferentiated numeric QString
                            formateed YYYYMMDD in downloaded QIF files.
                            */
                            // Operation creation
                            SKGUnitObject unit;
                            IFOK(err) {
                                if (account != NULL) {
                                    err = account->addOperation(currentOperation, true);
                                    if (!openingbalancecreated) {
                                        double initBalance;
                                        account->getInitialBalance(initBalance, unit);
                                    }
                                } else {
                                    SKGAccountObject defAccount;
                                    err = m_importer->getDefaultAccount(defAccount);
                                    IFOKDO(err, defAccount.addOperation(currentOperation, true))
                                    if (!openingbalancecreated) {
                                        double initBalance;
                                        defAccount.getInitialBalance(initBalance, unit);
                                    }
                                }
                                currentOperationInitialized = true;
                            }

                            // Set date
                            currentOperationDate = SKGServices::stringToTime(SKGServices::dateToSqlString(val, dateFormat)).date();
                            IFOKDO(err, currentOperation.setDate(currentOperationDate))

                            // Set unit
                            IFOK(err) {
                                // Create unit if needed
                                // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit
                                if (!unit.exist()) {
                                    err = m_importer->getDefaultUnit(unit, &currentOperationDate);
                                }
                                IFOKDO(err, currentOperation.setUnit(unit))
                            }

                            IFOK(err) currentOperation.save();

                            // Create suboperation
                            IFOKDO(err, currentOperation.addSubOperation(currentSubOperation))
                        } else if (op == 'Y') {
                            // Y     Security
                            if (!div) {
                                currentUnitForInvestment = val;

                                SKGUnitObject unit(m_importer->getDocument());
                                err = unit.setName(currentUnitForInvestment);
                                IFOKDO(err, unit.setSymbol(currentUnitForInvestment))
                                if (unit.load().isFailed()) {
                                    IFOKDO(err, unit.setType(investmentAccount ? SKGUnitObject::SHARE : SKGUnitObject::CURRENCY))
                                    IFOKDO(err, unit.save(false))
                                }
                                IFOKDO(err, currentOperation.setUnit(unit))
                            } else {
                                // For dividend, if comment is empty, we set the security in comment
                                if (currentOperation.getComment().isEmpty()) {
                                    err = currentOperation.setComment(val);
                                }
                            }
                        } else if (op == 'O') {
                            // O     Commission
                            // Get previous quantity
                            double quantity = SKGServices::stringToDouble(val);
                            SKGObjectBase::SKGListSKGObjectBase subops;
                            payement.getSubOperations(subops);
                            if (subops.count()) {
                                SKGSubOperationObject subpayement(subops.at(0));
                                err = subpayement.setQuantity(subpayement.getQuantity() + quantity);
                                IFOKDO(err, subpayement.save())
                            }

                            SKGSubOperationObject subcommission;
                            if (!payement.exist()) {
                                // We have to create a new operation
                                if (account != NULL) {
                                    err = account->addOperation(payement, true);
                                } else {
                                    SKGAccountObject defAccount;
                                    err = m_importer->getDefaultAccount(defAccount);
                                    IFOKDO(err, defAccount.addOperation(payement, true))
                                }
                                IFOKDO(err, payement.setDate(currentOperationDate))
                                IFOK(err) {
                                    // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit
                                    SKGUnitObject unit;
                                    if (account && !openingbalancecreated) {
                                        double initBalance;
                                        account->getInitialBalance(initBalance, unit);
                                    }
                                    if (!unit.exist()) {
                                        err = m_importer->getDefaultUnit(unit, &currentOperationDate);
                                    }
                                    IFOKDO(err, payement.setUnit(unit))
                                }
                                IFOKDO(err, payement.save())
                            }
                            IFOKDO(err, payement.addSubOperation(subcommission))
                            IFOKDO(err, subcommission.setQuantity(-quantity))
                            IFOKDO(err, subcommission.save(false, false))
                        } else if (op == 'I') {
                            // I     Price
                            currentUnitPrice = SKGServices::stringToDouble(val);
                            if (currentUnitPrice && !currentUnitForInvestment.isEmpty()) {
                                err = m_importer->getDocument()->addOrModifyUnitValue(currentUnitForInvestment, currentOperationDate, currentUnitPrice);
                            }
                        } else if (op == 'N') {
                            if (investmentAccount) {
                                // N     Action
                                /*
                                QIF N Line    Notes
                                ============  =====
                                Aktab         Same as ShrsOut.
                                AktSplit      Same as StkSplit.
                                Aktzu         Same as ShrsIn.
                                Buy           Buy shares.
                                BuyX          Buy shares. Used with an L line.
                                Cash          Miscellaneous cash transaction. Used with an L line.
                                CGMid         Mid-term capital gains.
                                CGMidX        Mid-term capital gains. For use with an L line.
                                CGLong        Long-term capital gains.
                                CGLongX       Long-term capital gains. For use with an L line.
                                CGShort       Short-term capital gains.
                                CGShortX      Short-term capital gains. For use with an L line.
                                ContribX      Same as XIn. Used for tax-advantaged accounts.
                                CvrShrt       Buy shares to cover a short sale.
                                CvrShrtX      Buy shares to cover a short sale. Used with an L line.
                                Div           Dividend received.
                                DivX          Dividend received. For use with an L line.
                                Errinerg      Same as Reminder.
                                Exercise      Exercise an option.
                                ExercisX      Exercise an option. For use with an L line.
                                Expire        Mark an option as expired. (Uses D, N, Y & M lines)
                                Grant         Receive a grant of stock options.
                                Int           Same as IntInc.
                                IntX          Same as IntIncX.
                                IntInc        Interest received.
                                IntIncX       Interest received. For use with an L line.
                                K.gewsp       Same as CGShort. (German)
                                K.gewspX      Same as CGShortX. (German)2307068
                                Kapgew        Same as CGLong. Kapitalgewinnsteuer.(German)
                                KapgewX       Same as CGLongX. Kapitalgewinnsteuer. (German)
                                Kauf          Same as Buy. (German)
                                KaufX         Same as BuyX. (German)
                                MargInt       Margin interest paid.
                                MargIntX      Margin interest paid. For use with an L line.
                                MiscExp       Miscellaneous expense.
                                MiscExpX      Miscellaneous expense. For use with an L line.
                                MiscInc       Miscellaneous income.
                                MiscIncX      Miscellaneous income. For use with an L line.
                                ReinvDiv      Reinvested dividend.
                                ReinvInt      Reinvested interest.
                                ReinvLG       Reinvested long-term capital gains.
                                Reinvkur      Same as ReinvLG.
                                Reinvksp      Same as ReinvSh.
                                ReinvMd       Reinvested mid-term capital gains.
                                ReinvSG       Same as ReinvSh.
                                ReinvSh       Reinvested short-term capital gains.
                                Reinvzin      Same as ReinvDiv.
                                Reminder      Reminder. (Uses D, N, C & M lines)
                                RtrnCap       Return of capital.
                                RtrnCapX      Return of capital. For use with an L line.
                                Sell          Sell shares.
                                SellX         Sell shares. For use with an L line.
                                ShtSell       Short sale.
                                ShrsIn        Deposit shares.
                                ShrsOut       Withdraw shares.
                                StkSplit      Share split.
                                Verkauf       Same as Sell. (German)
                                VerkaufX      Same as SellX. (German)
                                Vest          Mark options as vested. (Uses N, Y, Q, C & M lines)
                                WithDrwX      Same as XOut. Used for tax-advantaged accounts.
                                XIn           Transfer cash from another account.
                                XOut          Transfer cash to another account.
                                */
                                val = val.toLower();
                                if (val.contains("div") && val != "reinvdiv") {
                                    // TODO(Stephane MANKOWSKI) err=currentOperation.setProperty ( "SKG_OP_ORIGINAL_AMOUNT", "" );
                                    div = true;
                                } else if (val.contains("sell") || val.contains("verkauf") || val.contains("miscexp")) {
                                    quantityFactor = -1;
                                }
                                // Correction 214851 vvvv
                                // err=currentOperation.setComment ( val );
                                // if ( !err ) err=currentOperation.setMode ( i18nc ( "Noun, the title of an item","Title" ) );
                                // Correction 214851 ^^^^
                            } else {
                                // N     Num (check or reference number)
                                // Set number
                                bool ok;
                                int number = val.toInt(&ok);
                                if (ok && number != 0) {
                                    err = currentOperation.setNumber(number);
                                } else {
                                    err = currentOperation.setMode(val);
                                }
                            }
                        } else if (op == 'Q') {
                            // Q     Quantity (number of shares or split ratio)
                            // Set value
                            if (!val.isEmpty()) {
                                double previousQuantity = currentSubOperation.getQuantity();
                                if (previousQuantity) {
                                    // We have to create a new operation
                                    if (account != NULL) {
                                        err = account->addOperation(payement, true);
                                    } else {
                                        SKGAccountObject defAccount;
                                        err = m_importer->getDefaultAccount(defAccount);
                                        IFOKDO(err, defAccount.addOperation(payement, true))
                                    }
                                    IFOKDO(err, payement.setDate(currentOperationDate))
                                    IFOK(err) {
                                        // Create unit if needed
                                        // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit
                                        SKGUnitObject unit;
                                        if (account && !openingbalancecreated) {
                                            double initBalance;
                                            account->getInitialBalance(initBalance, unit);
                                        }
                                        if (!unit.exist()) {
                                            err = m_importer->getDefaultUnit(unit, &currentOperationDate);
                                        }
                                        IFOKDO(err, payement.setUnit(unit))
                                    }
                                    IFOKDO(err, payement.save())
                                    IFOKDO(err, currentOperation.setGroupOperation(payement))

                                    SKGSubOperationObject subpayement;
                                    IFOKDO(err, payement.addSubOperation(subpayement))
                                    IFOKDO(err, subpayement.setQuantity(-previousQuantity))
                                    IFOKDO(err, subpayement.save())
                                }

                                IFOKDO(err, currentSubOperation.setQuantity(quantityFactor * SKGServices::stringToDouble(val)))
                            }
                        } else if (op == 'T') {
                            // T     Amount
                            // Set value
                            checkOperationAmount = SKGServices::stringToDouble(val);
                            err = currentSubOperation.setQuantity(checkOperationAmount / currentUnitPrice);
                            if (!err && investmentAccount) {
                                err = currentOperation.setProperty("SKG_OP_ORIGINAL_AMOUNT", val);
                            }
                        } else if (op == '$') {
                            // Dollar amount of split
                            // Set value
                            if (!investmentAccount) {
                                double vald = SKGServices::stringToDouble(val);
                                checkSuboperationsAmount += vald;
                                err = currentSubOperation.setQuantity(vald);

                                // save
                                IFOKDO(err, currentSubOperation.save())

                                // Create suboperation
                                IFOKDO(err, currentOperation.addSubOperation(currentSubOperation))

                                latestSubCatMustBeRemoved = true;
                            }
                        } else if (op == 'P') {
                            // P Payee
                            // Set Payee
                            // Clean QIF coming from bankperfect
                            val.remove("[auto]");

                            err = SKGPayeeObject::createPayee(m_importer->getDocument(), val, currentPayee);
                            IFOKDO(err, currentOperation.setPayee(currentPayee))
                        } else if (op == 'A') {
                            // A      Address (up to 5 lines; 6th line is an optional message)
                            QString add = currentPayee.getAddress();
                            if (!add.isEmpty()) {
                                add += ' ';
                            }
                            add += val;
                            err = currentPayee.setAddress(add);
                            IFOKDO(err, currentPayee.save())
                        } else if (op == 'M') {
                            // M     Memo
                            // Set Memo
                            err = currentOperation.setComment(val);
                        } else if (op == 'E') {
                            // E     Memo in split
                            // Set Memo
                            err = currentSubOperation.setComment(val);
                        } else if (op == 'S' || op == 'L') {
                            // S     Category in split (Category/Transfer/Class)// L  Category (Category/Subcategory/Transfer/Class)
                            // LCategory of transaction
                            // L[Transfer account]
                            // LCategory of transaction/Class of transaction
                            // L[Transfer account]/Class of transaction// Set Category
                            if (!val.isEmpty()) {
                                // Correction bug 216520 vvvv
                                // If the operation is splitter, it is not a transfer
                                if (op == 'S') {
                                    transferAccount = "";
                                }
                                // Correction bug 216520 vvvv

                                if (val[0] == '[') {
                                    int pos = val.indexOf(']');
                                    if (pos != -1) {
                                        SKGPayeeObject payeeObj;
                                        currentOperation.getPayee(payeeObj);
                                        bool opening = (payeeObj.getName().compare(OPENINGBALANCE, Qt::CaseInsensitive) == 0);

                                        // If the very first Bank transaction in the file has a payee of "Opening Balance", the L line contains the name of the account that the file describes. This is not a transfer
                                        if (op == 'L' && automaticAccount && account && opening) {
                                            QString accountName = val.mid(1, pos - 1);

                                            SKGAccountObject newAccount(m_importer->getDocument());
                                            err = newAccount.setName(accountName);
                                            IFOK(err) {
                                                if (newAccount.exist()) {
                                                    // Oups, the real account is existing and it is another one
                                                    err = newAccount.load();

                                                    // We move the operation in the right account
                                                    IFOKDO(err, currentOperation.setParentAccount(newAccount))
                                                    IFOKDO(err, currentOperation.save())

                                                    // We delete the previous account if empty
                                                    IFOK(err) {
                                                        if (account->getNbOperation() == 0) {
                                                            err = account->remove();
                                                        }
                                                        delete account;
                                                        account = new SKGAccountObject(newAccount);
                                                    }
                                                } else {
                                                    err = account->setName(accountName);
                                                    IFOKDO(err, account->save())
                                                }
                                            }
                                        }
//                                            if ( op=='L' && currentOperation.getPayee().compare ( "Opening Balance", Qt::CaseInsensitive ) !=0 && !investmentAccount)
                                        if (op == 'L' && !opening) {
                                            transferAccount = val.mid(1, pos - 1);
                                            if (account && transferAccount == account->getName()) {
                                                transferAccount = "";
                                            }
                                        }
                                        val = val.mid(pos + 2);
                                    }
                                }
                                if (!err && !val.isEmpty()) {
                                    SKGCategoryObject Category;
                                    val.replace('/', OBJECTSEPARATOR);
                                    val.replace(':', OBJECTSEPARATOR);
                                    val.replace(',', OBJECTSEPARATOR);
                                    val.replace(';', OBJECTSEPARATOR);
                                    err = SKGCategoryObject::createPathCategory(m_importer->getDocument(), val, Category);
                                    IFOKDO(err, currentSubOperation.setCategory(Category))
                                }
                            }
                        } else if (op == 'C') {
                            // C     Cleared status
                            // Set status
                            err = currentOperation.setStatus((val == "C" || val == "*" ? SKGOperationObject::POINTED : (val == "R" || val == "X" ? SKGOperationObject::CHECKED : SKGOperationObject::NONE)));
                        } else if (op == '^') {
                            // ^     End of entry
                            // save

                            if (currentOperationInitialized) {
                                QByteArray hash = QCryptographicHash::hash(stringForHash.toUtf8(), QCryptographicHash::Md5);
                                SKGPayeeObject payeeObj;
                                currentOperation.getPayee(payeeObj);
                                bool opening = (payeeObj.getName().compare(OPENINGBALANCE, Qt::CaseInsensitive) == 0);
                                if (!err && opening) {
                                    // Specific values for initial balance
                                    err = currentOperation.setStatus(SKGOperationObject::CHECKED);
                                    IFOKDO(err, currentOperation.setAttribute("d_date", "0000-00-00"))
                                    IFOKDO(err, currentSubOperation.setAttribute("d_date", "0000-00-00"))
                                    openingbalancecreated = true;
                                }

                                IFOKDO(err, currentOperation.setImportID(hash.toHex()))
                                IFOKDO(err, currentOperation.save())
                                if (!latestSubCatMustBeRemoved && !err) {
                                    err = currentSubOperation.save();
                                }

                                // Create transfer is needed
                                if (!err && !transferAccount.isEmpty()) {
                                    // Get origin op
                                    SKGOperationObject opOrigin(m_importer->getDocument(), currentOperation.getID());
                                    SKGAccountObject accountOrigin;
                                    IFOKDO(err, opOrigin.getParentAccount(accountOrigin))
                                    IFOK(err) {
                                        // Is the created operation already existing as a transfer?
                                        double qua = SKGServices::stringToDouble(opOrigin.getAttribute("f_QUANTITY"));
                                        QString wc = "t_import_id='QIF TRANSFER-" % SKGServices::stringToSqlString(transferAccount) % "' AND t_ACCOUNT='" % SKGServices::stringToSqlString(accountOrigin.getName()) %
                                                     "' AND (ABS(f_CURRENTAMOUNT-(" % SKGServices::doubleToString(opOrigin.getCurrentAmount()) % "))<0.0001 OR f_QUANTITY=" % SKGServices::doubleToString(qua) % ")"
                                                     " AND ABS(julianday(d_date) - julianday('" % SKGServices::dateToSqlString(QDateTime(opOrigin.getDate())) % "'))<1"
                                                     " ORDER BY ABS(julianday(d_date) - julianday('" % SKGServices::dateToSqlString(QDateTime(opOrigin.getDate())) % "')) ASC";
                                        SKGObjectBase::SKGListSKGObjectBase obs;
                                        m_importer->getDocument()->getObjects("v_operation_display", wc, obs);
                                        if (obs.count()) {
                                            // We have to merge them and we do not need to create the transfer
                                            SKGOperationObject firstOne(obs.at(0));
                                            err = opOrigin.setStatus(SKGOperationObject::NONE);  // To be sure we can delete it
                                            IFOKDO(err, opOrigin.save())
                                            IFOKDO(err, firstOne.mergeAttribute(opOrigin))

                                            SKGObjectBase::SKGListSKGObjectBase list;
                                            IFOKDO(err, currentOperation.getGroupedOperations(list))
                                            foreach(const SKGObjectBase & o, list) {
                                                SKGOperationObject op2(o);
                                                IFOKDO(err, op2.setStatus(SKGOperationObject::NONE))
                                                IFOKDO(err, op2.remove(false, true))
                                            }
                                        } else {
                                            // Create target account if needed
                                            SKGAccountObject accountTransfer(m_importer->getDocument());
                                            if (m_accountCache.contains(transferAccount)) {
                                                accountTransfer = m_accountCache[transferAccount];
                                            } else {
                                                accountTransfer.setName(transferAccount);
                                                if (!accountTransfer.exist()) {
                                                    // The account is created in the same bank by default
                                                    SKGBankObject bankOrigin;
                                                    IFOKDO(err, accountOrigin.getBank(bankOrigin))
                                                    IFOKDO(err, accountTransfer.setBank(bankOrigin))
                                                    IFOKDO(err, accountTransfer.save(false, true))
                                                } else {
                                                    err = accountTransfer.load();
                                                }

                                                m_accountCache[transferAccount] = accountTransfer;
                                            }

                                            // Create operation
                                            SKGUnitObject unit;
                                            opOrigin.getUnit(unit);

                                            SKGOperationObject opTransfer;
                                            IFOKDO(err, accountTransfer.addOperation(opTransfer, true))
                                            IFOKDO(err, opTransfer.setDate(opOrigin.getDate()))
                                            IFOKDO(err, opTransfer.setComment(opOrigin.getComment()))
                                            SKGPayeeObject payeeObj2;
                                            opTransfer.getPayee(payeeObj2);
                                            IFOKDO(err, opTransfer.setPayee(payeeObj2))
                                            IFOKDO(err, opTransfer.setStatus(opOrigin.getStatus()))
                                            IFOKDO(err, opTransfer.setUnit(unit))
                                            IFOKDO(err, opTransfer.setImportID("QIF TRANSFER-" % accountOrigin.getName()))
                                            IFOKDO(err, opTransfer.setGroupOperation(opOrigin))
                                            IFOKDO(err, opTransfer.save())

                                            SKGSubOperationObject subopTransfer;
                                            IFOKDO(err, opTransfer.addSubOperation(subopTransfer))
                                            IFOKDO(err, subopTransfer.setQuantity(-qua))
                                            IFOKDO(err, subopTransfer.save())
                                        }
                                    }
                                }
                            }

                            // Check Sum($)=T for incident 214462
                            QString checkOperationAmountString = SKGServices::doubleToString(checkOperationAmount);
                            QString checkSuboperationsAmountString = SKGServices::doubleToString(checkSuboperationsAmount);
                            if (!err && checkOperationAmount != 0 && checkSuboperationsAmount != 0 && checkOperationAmountString != checkSuboperationsAmountString) {
                                SKGSubOperationObject suboprepair;
                                IFOKDO(err, currentOperation.addSubOperation(suboprepair))
                                IFOKDO(err, suboprepair.setQuantity(checkOperationAmount - checkSuboperationsAmount))
                                IFOKDO(err, suboprepair.setComment(i18nc("An information message",  "Auto repaired operation")))
                                IFOKDO(err, suboprepair.save())

                                IFOKDO(err, m_importer->getDocument()->sendMessage(i18nc("An information message",  "The total amount of the operation (%1) was different to the sum of the sub-operations (%2). The operation has been repaired.", checkOperationAmountString, checkSuboperationsAmountString), SKGDocument::Warning));
                            }

                            // Initialize variables
                            currentOperationInitialized = false;
                            latestSubCatMustBeRemoved = false;
                            currentUnitForInvestment = "";
                            quantityFactor = 1;
                            currentUnitPrice = 1;
                            stringForHash = "";
                            checkOperationAmount = 0;
                            checkSuboperationsAmount = 0;
                            transferAccount = "";
                            payement = SKGOperationObject();
                        } else {
                            // A    Address (up to five lines; the sixth line is an optional message)
                        }
                    }

                    if (!err && i % 500 == 0) {
                        err = m_importer->getDocument()->executeSqliteOrder("ANALYZE");
                    }
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }

                delete account;
                account = NULL;
                SKGENDTRANSACTION(m_importer->getDocument(),  err);

                // Lines treated
                IFOKDO(err, m_importer->getDocument()->stepForward(3))
            }
        }
    }
    SKGENDTRANSACTION(m_importer->getDocument(),  err);

    return err;
}

bool SKGImportPluginQif::isExportPossible()
{
    SKGTRACEINFUNC(10);
    return (!m_importer ? true : m_importer->getFileNameExtension() == "QIF");
}

SKGError SKGImportPluginQif::exportFile()
{
    if (!m_importer) {
        return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
    }
    SKGError err;
    SKGTRACEINFUNCRC(2, err);

    // Open file
    QSaveFile file(m_importer->getLocalFileName(false));
    if (!file.open(QIODevice::WriteOnly)) {
        err.setReturnCode(ERR_INVALIDARG).setMessage(i18nc("Error message",  "Save file '%1' failed", m_importer->getFileName().toDisplayString()));
    } else {
        QTextStream stream(&file);
        if (!m_importer->getCodec().isEmpty()) {
            stream.setCodec(m_importer->getCodec().toLatin1().constData());
        }

        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export %1 file", "QIF"), 3);
        IFOK(err) {
            // Export categories
            SKGObjectBase::SKGListSKGObjectBase categories;
            IFOKDO(err, m_importer->getDocument()->getObjects("v_category_display_tmp", "1=1 ORDER BY t_fullname, id", categories))
            int nbcat = categories.count();
            if (!err && nbcat) {
                stream << "!Type:Cat\n";
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export categories"), nbcat);
                for (int i = 0; !err && i < nbcat; ++i) {
                    SKGCategoryObject cat(categories.at(i));
                    QString catName = cat.getFullName();
                    if (!catName.isEmpty()) {
                        stream << "N" << catName.replace(OBJECTSEPARATOR, ":") << endl;
                        if (SKGServices::stringToDouble(cat.getAttribute("f_REALCURRENTAMOUNT")) < 0) {
                            stream << "E" << endl;
                        } else {
                            stream << "I" << endl;
                        }
                        stream << "^" << endl;
                    }
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }

                SKGENDTRANSACTION(m_importer->getDocument(),  err);
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(1))

            SKGServices::SKGUnitInfo primaryUnit = m_importer->getDocument()->getPrimaryUnit();

            // Get operations
            QString currentAccountName;
            SKGObjectBase::SKGListSKGObjectBase operations;
            IFOKDO(err, m_importer->getDocument()->getObjects("v_operation_display_all", "t_template='N' ORDER BY t_ACCOUNT, d_date, id", operations))
            int nb = operations.count();
            IFOK(err) {
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export operations"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    SKGOperationObject operation(operations.at(i));

                    // Get account name
                    QString accountName = operation.getAttribute("t_ACCOUNT");

                    // In the same account ?
                    if (accountName != currentAccountName) {
                        SKGAccountObject account(m_importer->getDocument());
                        account.setName(accountName);
                        account.load();

                        SKGBankObject bank;
                        account.getBank(bank);

                        // Write header
                        stream << "!Account\n";
                        stream << 'N' << accountName << endl;
                        QString type = (account.getType() == SKGAccountObject::CURRENT ? "Bank" : (account.getType() == SKGAccountObject::CREDITCARD ? "CCard" : (account.getType() == SKGAccountObject::INVESTMENT ? "Invst" : (account.getType() == SKGAccountObject::ASSETS ? "Oth A" : "Cash"))));
                        stream << 'T' << type << endl;
                        QString number = bank.getNumber();
                        QString bnumber = account.getAgencyNumber();
                        QString cnumber = account.getNumber();
                        if (!bnumber.isEmpty()) {
                            if (!number.isEmpty()) {
                                number += '-';
                            }
                            number += bnumber;
                        }
                        if (!cnumber.isEmpty()) {
                            if (!number.isEmpty()) {
                                number += '-';
                            }
                            number += cnumber;
                        }
                        stream << 'D' << number << endl;
                        // stream << "/"      Statement balance date
                        // stream << "$"      Statement balance amount
                        stream << '^' << endl;
                        currentAccountName = accountName;

                        stream << "!Type:" << type << "\n";
                    }


                    // Write operation
                    /*
                    DONE    D      Date
                    DONE    T      Amount
                    N/A U      Transaction amount (higher possible value than T)
                    DONE    C      Cleared status
                    DONE    N      Number (check or reference number)
                    DONE    P      Payee/description
                    DONE    M      Memo
                    N/A A      Address (up to 5 lines; 6th line is an optional message)
                    DONE    L      Category (category/class or transfer/class)
                    DONE    S      Category in split (category/class or transfer/class)
                    DONE    E      Memo in split
                    DONE    $      Dollar amount of split
                    N/A %      Percentage of split if percentages are used
                    N/A F      Reimbursable business expense flag
                    N/A X      Small Business extensions
                    DONE    Y      Security
                    DONE    I      Price
                    DONE    Q      Quantity (number of shares or split ratio)
                    N/A    O      Commission
                    DONE    ^      End of entry
                    */
                    SKGUnitObject unit;
                    operation.getUnit(unit);
                    bool investment = false;
                    bool unitExported = false;
                    if (unit.getSymbol() != primaryUnit.Symbol && !primaryUnit.Symbol.isEmpty()) {
                        unitExported = true;
                    }
                    if (unit.getType() == SKGUnitObject::SHARE) {
                        unitExported = true;
                        investment = true;
                    }

                    QString date = SKGServices::dateToSqlString(QDateTime(operation.getDate()));
                    if (date.isEmpty()) {
                        // This is an opening balance
                        date = "0000-00-00";
                    }
                    stream << 'D' << date << endl;
                    if (!unitExported) {
                        stream << 'T' << SKGServices::doubleToString(operation.getCurrentAmount()) << endl;
                    }

                    if (!investment) {
                        int number = operation.getNumber();
                        if (number) {
                            stream << 'N' << operation.getNumber() << endl;
                        }
                    } else {
                        stream << 'N' << (operation.getCurrentAmount() > 0 ? "Buy" : "Sell") << endl;
                    }

                    if (unitExported) {
                        stream << 'Y' << unit.getSymbol() << endl;
                    }

                    SKGPayeeObject payeeObj;
                    operation.getPayee(payeeObj);
                    QString payee = payeeObj.getName();
                    QString address = payeeObj.getAddress();
                    if (date == "0000-00-00") {
                        payee = OPENINGBALANCE;
                    }
                    if (!payee.isEmpty()) {
                        stream << 'P' << payee << endl;
                    }
                    if (!address.isEmpty()) {
                        stream << 'A' << address << endl;
                    }

                    QString memo = operation.getMode() % "  " % operation.getComment();
                    memo = memo.trimmed();
                    if (!memo.isEmpty()) {
                        stream << 'M' << memo << endl;
                    }

                    SKGOperationObject::OperationStatus status = operation.getStatus();
                    stream << 'C' << (status == SKGOperationObject::POINTED ? "C" : (status == SKGOperationObject::CHECKED ? "R" : "")) << endl;

                    // Get sub operations
                    SKGObjectBase::SKGListSKGObjectBase suboperations;
                    err = operation.getSubOperations(suboperations);
                    IFOK(err) {
                        int nbSubOps = suboperations.size();
                        QString category;
                        if (nbSubOps == 1) {
                            SKGSubOperationObject suboperation(suboperations.at(0));
                            // Dump quantity
                            if (unitExported) {
                                stream << 'Q' << SKGServices::doubleToString(qAbs(suboperation.getQuantity())) << endl;
                                stream << 'I' << SKGServices::doubleToString(qAbs(operation.getCurrentAmount() / suboperation.getQuantity())) << endl;
                            }

                            // Get category of this simple operation
                            SKGCategoryObject cat;
                            suboperation.getCategory(cat);
                            category = cat.getFullName().replace(OBJECTSEPARATOR, ":");
                        }

                        // Is it a transfer
                        SKGOperationObject transfer;
                        if (operation.isTransfer(transfer)) {
                            if (!category.isEmpty()) {
                                category.prepend('/');
                            }

                            SKGAccountObject transferAccount;
                            err = transfer.getParentAccount(transferAccount);
                            IFOK(err) category.prepend('[' % transferAccount.getName() % ']');
                        }
                        if (!category.isEmpty()) {
                            stream << 'L' << category << endl;
                        }

                        if (nbSubOps > 1) {
                            // Split operation
                            for (int k = 0; k < nbSubOps; ++k) {
                                SKGSubOperationObject suboperation(suboperations.at(k));
                                SKGCategoryObject cat;
                                suboperation.getCategory(cat);

                                QString category2 = cat.getFullName().replace(OBJECTSEPARATOR, ":");
                                if (!category2.isEmpty()) {
                                    stream << 'S' << category2 << endl;
                                }
                                QString memo2 = suboperation.getComment();
                                memo2 = memo2.trimmed();
                                if (!memo2.isEmpty()) {
                                    stream << 'E' << memo2 << endl;
                                }
                                stream << '$' << SKGServices::doubleToString(suboperation.getQuantity()) << endl;
                            }
                        }
                    }

                    stream << '^' << endl;
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }

                SKGENDTRANSACTION(m_importer->getDocument(),  err);
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(2))

            // Export prices
            SKGObjectBase::SKGListSKGObjectBase unitvalues;
            IFOKDO(err, m_importer->getDocument()->getObjects("v_unitvalue", "1=1 ORDER BY (select t_name from unit where v_unitvalue.rd_unit_id=unit.id), d_date", unitvalues))
            nb = unitvalues.count();
            if (!err && nb) {
                stream << "!Type:Prices\n";
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export units"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    SKGUnitValueObject unitVal(unitvalues.at(i));
                    SKGUnitObject unit;
                    err = unitVal.getUnit(unit);
                    IFOK(err) {
                        QStringList vals;
                        QString v = unit.getSymbol();
                        if (v.isEmpty()) {
                            v = unit.getName();
                        }
                        vals.push_back(v);
                        vals.push_back(SKGServices::doubleToString(unitVal.getQuantity()));
                        vals.push_back(SKGServices::dateToSqlString(QDateTime(unitVal.getDate())));

                        stream << SKGServices::stringsToCsv(vals) << endl;
                    }
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }
                stream << "^" << endl;

                SKGENDTRANSACTION(m_importer->getDocument(),  err);
            }
            IFOKDO(err, m_importer->getDocument()->stepForward(3))
            SKGENDTRANSACTION(m_importer->getDocument(),  err);
        }

        // Close file
        file.commit();
    }

    return err;
}

QString SKGImportPluginQif::getMimeTypeFilter() const
{
    return "*.qif|" % i18nc("A file format", "QIF file");
}

#include <skgimportpluginqif.moc>
