/***************************************************************************
 *   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 IIF import / export.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgimportpluginiif.h"

#include <KLocale>
#include <KFilterDev>
#include <KSaveFile>
#include <KGenericFactory>

#include <QCryptographicHash>
#include <QFileInfo>

#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(SKGImportPluginIifFactory, registerPlugin<SKGImportPluginIif>();)
/**
 * This plugin export.
 */
K_EXPORT_PLUGIN(SKGImportPluginIifFactory("skrooge_import_iif", "skrooge_import_iif"))

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

SKGImportPluginIif::~SKGImportPluginIif()
{
}

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

QString SKGImportPluginIif::getVal(const QStringList& iVals, const QString& iAttribute) const
{
    QString output;

    // Get the correspondng header
    QStringList header = m_headers["!" % iVals[0]];
    if (header.count()) {
        // Get index of getAttribute
        int pos = header.indexOf(iAttribute);
        if (pos != -1 && pos < iVals.count()) {
            output = iVals[pos].trimmed();
        }
    }
    return output;
}

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

    SKGError err;
    SKGTRACEINFUNCRC(2, err);

    // Create account if needed
    QDateTime now = QDateTime::currentDateTime();
    QString postFix = SKGServices::dateToSqlString(now);

    // Begin transaction
    err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "IIF"), 3);
    IFOK(err) {
        // 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().prettyUrl()));
        } else {
            QTextStream stream(&file);
            if (!m_importer->getCodec().isEmpty()) {
                stream.setCodec(m_importer->getCodec().toAscii().constData());
            }

            // Import
            SKGOperationObject operation;
            SKGSubOperationObject suboperation;
            int index = 0;
            while (!stream.atEnd() && !err) {
                // Read line
                QString line = stream.readLine().trimmed();
                if (!line.isEmpty()) {
                    QStringList vals = SKGServices::splitCSVLine(line, '\t');
                    if (vals[0].startsWith(QLatin1Char('!'))) {
                        // This is a header
                        m_headers[vals[0]] = vals;
                    } else {
                        // This is an item
                        QString t = vals[0].trimmed();
                        if (t == "ACCNT") {
                            // This is an account
                            QString accountType = getVal(vals, "ACCNTTYPE");
                            QString name = getVal(vals, "NAME");
                            /*
                            AP: Accounts payable            DONE: ACCOUNT
                            AR: Accounts receivable     DONE: ACCOUNT
                            BANK: Checking or savings       DONE: ACCOUNT
                            CCARD: Credit card account      DONE: ACCOUNT
                            COGS: Cost of goods sold        TODO
                            EQUITY: Capital/Equity      TODO
                            EXEXP: Other expense        DONE: CATEGORY
                            EXINC: Other income         DONE: CATEGORY
                            EXP: Expense            DONE: CATEGORY
                            FIXASSET: Fixed asset       DONE: ACCOUNT
                            INC: Income             DONE: CATEGORY
                            LTLIAB: Long term liability     DONE: ACCOUNT
                            NONPOSTING: Non-posting account TODO
                            OASSET: Other asset         DONE: ACCOUNT
                            OCASSET: Other current asset    DONE: ACCOUNT
                            OCLIAB: Other current liability DONE: ACCOUNT*/
                            if (accountType == "BANK" || accountType == "AP" || accountType == "AR" ||
                                accountType == "OCASSET" || accountType == "FIXASSET" || accountType == "OASSET" ||
                                accountType == "OCLIAB" || accountType == "LTLIAB" ||
                                accountType == "CCARD") {
                                // Check if the account already exist
                                SKGAccountObject account;
                                err = SKGNamedObject::getObjectByName(m_importer->getDocument(), "account", name, account);
                                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(account))
                                    IFOKDO(err, account.setName(name))
                                    if (!err && account.load().isFailed()) {
                                        IFOKDO(err, account.setType(accountType == "BANK" || accountType == "AP" || accountType == "AR" ? SKGAccountObject::CURRENT :
                                                                    accountType == "OCASSET" || accountType == "FIXASSET" || accountType == "OASSET" ? SKGAccountObject::ASSETS :
                                                                    accountType == "OCLIAB" || accountType == "LTLIAB" ? SKGAccountObject::LOAN :
                                                                    accountType == "CCARD" ? SKGAccountObject::CREDITCARD :
                                                                    SKGAccountObject::OTHER))
                                        IFOKDO(err, account.setAgencyNumber(getVal(vals, "ACCNUM")))
                                        IFOKDO(err, account.setComment(getVal(vals, "DESC")))
                                        IFOKDO(err, account.save())
                                        m_accounts[name] = account;
                                    }
                                }
                            } else {
                                // This is a category
                                SKGCategoryObject category;
                                QString originalName = name;
                                IFOKDO(err, SKGCategoryObject::createPathCategory(m_importer->getDocument(), name.replace(':', OBJECTSEPARATOR), category))
                                m_categories[originalName] = category;
                            }
                        } else if (t == "CUST") {
                            // This is a payee
                            QString name = getVal(vals, "NAME");
                            SKGPayeeObject payee;
                            IFOKDO(err, SKGPayeeObject::createPayee(m_importer->getDocument(), name, payee));

                            QString address = getVal(vals, "BADDR1") % " " %
                                              getVal(vals, "BADDR2") % " " %
                                              getVal(vals, "BADDR3") % " " %
                                              getVal(vals, "BADDR4") % " " %
                                              getVal(vals, "BADDR5") % " " %
                                              getVal(vals, "SADDR1") % " " %
                                              getVal(vals, "SADDR2") % " " %
                                              getVal(vals, "SADDR3") % " " %
                                              getVal(vals, "SADDR4") % " " %
                                              getVal(vals, "SADDR5");
                            address = address.trimmed();

                            IFOKDO(err, payee.setAddress(address))
                            IFOKDO(err, payee.save())
                            m_payees[name] = payee;
                        } else if (t == "VEND") {
                            // This is a payee
                            QString name = getVal(vals, "NAME");
                            SKGPayeeObject payee;
                            IFOKDO(err, SKGPayeeObject::createPayee(m_importer->getDocument(), getVal(vals, "NAME"), payee));

                            QString address = getVal(vals, "ADDR1") % " " %
                                              getVal(vals, "ADDR2") % " " %
                                              getVal(vals, "ADDR3") % " " %
                                              getVal(vals, "ADDR4") % " " %
                                              getVal(vals, "ADDR5");
                            address = address.trimmed();

                            IFOKDO(err, payee.setAddress(address))
                            IFOKDO(err, payee.save())
                            m_payees[name] = payee;
                        } else if (t == "ENDTRNS") {
                            operation = SKGOperationObject();
                            suboperation = SKGSubOperationObject();
                        } else if (t == "TRNS") {
                            // This is an operation
                            SKGAccountObject account = m_accounts[getVal(vals, "ACCNT")];
                            IFOKDO(err, account.addOperation(operation))

                            QDate date = SKGServices::stringToTime(SKGServices::dateToSqlString(getVal(vals, "DATE"), "MM/DD/YYYY")).date();
                            IFOKDO(err, operation.setDate(date))
                            IFOKDO(err, operation.setStatus(getVal(vals, "CLEAR") == "Y" ? SKGOperationObject::CHECKED : SKGOperationObject::NONE))

                            QString number = getVal(vals, "DOCNUM");
                            if (!number.isEmpty()) {
                                IFOKDO(err, operation.setNumber(SKGServices::stringToDouble(number)))
                            }

                            // If an initial balance is existing for the account then we use the unit else we look for the most appropriate unit
                            SKGUnitObject unit;
                            double initBalance;
                            account.getInitialBalance(initBalance, unit);
                            if (!unit.exist()) {
                                err = m_importer->getDefaultUnit(unit, &date);
                            }
                            IFOKDO(err, operation.setUnit(unit))

                            IFOKDO(err, operation.setMode(getVal(vals, "TRNSTYPE")))
                            QString p = getVal(vals, "NAME");
                            if (!p.isEmpty()) {
                                IFOKDO(err, operation.setPayee(m_payees[p]))
                            }
                            IFOKDO(err, operation.setComment(getVal(vals, "MEMO")))
                            IFOKDO(err, operation.save())
                        } else if (t == "SPL") {
                            // This is a sub operation
                            QString a = getVal(vals, "ACCNT");
                            if (m_categories.contains(a)) {
                                // This is a split
                                SKGCategoryObject cat = m_categories[a];
                                IFOKDO(err, operation.addSubOperation(suboperation))
                                IFOKDO(err, suboperation.setQuantity(-SKGServices::stringToDouble(getVal(vals, "AMOUNT"))))
                                IFOKDO(err, suboperation.setCategory(cat))
                                IFOKDO(err, suboperation.setComment(getVal(vals, "MEMO")))
                                IFOKDO(err, suboperation.save())
                            } else if (m_accounts.contains(a)) {
                                // This is a transfer
                                SKGAccountObject act = m_accounts[a];

                                SKGOperationObject operation2;
                                IFOKDO(err, act.addOperation(operation2))

                                QDate date = SKGServices::stringToTime(SKGServices::dateToSqlString(getVal(vals, "DATE"), "MM/DD/YYYY")).date();
                                IFOKDO(err, operation2.setDate(date))
                                IFOKDO(err, operation2.setStatus(getVal(vals, "CLEAR") == "Y" ? SKGOperationObject::CHECKED : SKGOperationObject::NONE))

                                QString number = getVal(vals, "DOCNUM");
                                if (!number.isEmpty()) {
                                    IFOKDO(err, operation2.setNumber(SKGServices::stringToDouble(number)))
                                }

                                SKGUnitObject unit;
                                IFOKDO(err, operation.getUnit(unit));
                                IFOKDO(err, operation2.setUnit(unit))

                                IFOKDO(err, operation2.setMode(getVal(vals, "TRNSTYPE")))
                                QString p = getVal(vals, "NAME");
                                if (!p.isEmpty()) {
                                    IFOKDO(err, operation2.setPayee(m_payees[p]))
                                }
                                IFOKDO(err, operation2.setComment(getVal(vals, "MEMO")))
                                IFOKDO(err, operation2.save())

                                SKGSubOperationObject suboperation2;
                                SKGCategoryObject cat = m_categories[a];
                                IFOKDO(err, operation2.addSubOperation(suboperation2))
                                IFOKDO(err, suboperation2.setQuantity(SKGServices::stringToDouble(getVal(vals, "AMOUNT"))))
                                IFOKDO(err, suboperation2.setCategory(cat))
                                IFOKDO(err, suboperation2.setComment(getVal(vals, "MEMO")))
                                IFOKDO(err, suboperation2.save())

                                IFOKDO(err, operation2.setGroupOperation(operation))
                                IFOKDO(err, operation2.save())

                                IFOKDO(err, operation.addSubOperation(suboperation))
                                IFOKDO(err, suboperation.setQuantity(-SKGServices::stringToDouble(getVal(vals, "AMOUNT"))))
                                IFOKDO(err, suboperation.setCategory(cat))
                                IFOKDO(err, suboperation.setComment(getVal(vals, "MEMO")))
                                IFOKDO(err, suboperation.save())
                            }
                        }
                    }

                    ++index;
                    if (!err && index % 500 == 0) {
                        err = m_importer->getDocument()->executeSqliteOrder("ANALYZE");
                    }
                }

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

            // close file
            file.close();
        }
    }
    SKGENDTRANSACTION(m_importer->getDocument(),  err);

    return err;
}

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

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

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

        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export %1 file", "IIF"), 4);
        IFOK(err) {
            // Export accounts
            stream << "!ACCNT\tNAME\tACCNTTYPE\tDESC\tACCNUM\tEXTRA\n";
            SKGObjectBase::SKGListSKGObjectBase accounts;
            IFOKDO(err, m_importer->getDocument()->getObjects("v_account_display", "1=1 ORDER BY t_name, id", accounts))
            int nbaccount = accounts.count();
            if (!err && nbaccount) {

                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export accounts"), nbaccount);
                for (int i = 0; !err && i < nbaccount; ++i) {
                    SKGAccountObject act(accounts.at(i));
                    SKGAccountObject::AccountType type = act.getType();
                    stream << "ACCNT"
                           << '\t' << act.getName()
                           << '\t' << (type == SKGAccountObject::ASSETS ? "FIXASSET" : (type == SKGAccountObject::LOAN ? "OCLIAB" : type == SKGAccountObject::CREDITCARD ? "CCARD" : "BANK"))
                           << '\t' << act.getComment()
                           << '\t' << act.getAgencyNumber()
                           << '\t'
                           << endl;
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }

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

            // 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) {

                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 << "ACCNT"
                               << '\t' << catName.replace(OBJECTSEPARATOR, ":")
                               << '\t' << (SKGServices::stringToDouble(cat.getAttribute("f_REALCURRENTAMOUNT")) < 0 ? "EXP" : "INC")
                               << '\t'
                               << '\t'
                               << '\t'
                               << endl;
                    }
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }

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

            // Export payees
            stream << "!CUST\tNAME\tBADDR1\n";
            SKGObjectBase::SKGListSKGObjectBase payees;
            IFOKDO(err, m_importer->getDocument()->getObjects("v_payee_display", "1=1 ORDER BY t_name, id", payees))
            int nbpayee = payees.count();
            if (!err && nbpayee) {

                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export payees"), nbpayee);
                for (int i = 0; !err && i < nbpayee; ++i) {
                    SKGPayeeObject payee(payees.at(i));
                    stream << "CUST"
                           << '\t' << payee.getName()
                           << '\t' << payee.getAddress()
                           << endl;
                    IFOKDO(err, m_importer->getDocument()->stepForward(i + 1))
                }

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

            // Export operations
            stream << "!TRNS\tTRNSID\tTRNSTYPE\tDATE\tACCNT\tNAME\tCLASS\tAMOUNT\tDOCNUM\tCLEAR\tMEMO\n";
            stream << "!SPL\tSPLID\tTRNSTYPE\tDATE\tACCNT\tNAME\tCLASS\tAMOUNT\tDOCNUM\tCLEAR\tMEMO\n";
            stream << "!ENDTRNS\n";
            SKGObjectBase::SKGListSKGObjectBase operations;
            IFOKDO(err, m_importer->getDocument()->getObjects("v_operation_display", "1=1 ORDER BY d_date, id", operations))
            int nbop = operations.count();
            if (!err && nbop) {
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Export step", "Export operations"), nbop);
                for (int i = 0; !err && i < nbop; ++i) {
                    SKGOperationObject op(operations.at(i));
                    stream << "TRNS"
                           << '\t' << SKGServices::intToString(op.getID())
                           << '\t' << "PAYMENT"
                           << '\t' << op.getDate().toString("M/d/yyyy")
                           << '\t' << op.getAttribute("t_ACCOUNT")
                           << '\t' << op.getAttribute("t_PAYEE")
                           << '\t' << op.getAttribute("t_CATEGORY").replace(OBJECTSEPARATOR, ":")
                           << '\t' << SKGServices::doubleToString(op.getAmount(QDate::currentDate()))
                           << '\t' << SKGServices::doubleToString(op.getNumber())
                           << '\t' << (op.getStatus() == SKGOperationObject::CHECKED ? "Y" : "N")
                           << '\t' << op.getComment()
                           << endl;

                    // Export sub operations
                    SKGObjectBase::SKGListSKGObjectBase sops;
                    err = op.getSubOperations(sops);
                    int nbsops = sops.count();
                    for (int j = 0; !err && j < nbsops; ++j) {
                        SKGSubOperationObject sop(sops.at(j));
                        SKGCategoryObject cat;
                        sop.getCategory(cat);
                        stream << "SPL"
                               << '\t' << SKGServices::intToString(sop.getID())
                               << '\t' << "PAYMENT"
                               << '\t' << op.getDate().toString("M/d/yyyy")
                               << '\t' << cat.getFullName().replace(OBJECTSEPARATOR, ":")
                               << '\t' << op.getAttribute("t_PAYEE")
                               << '\t'
                               << '\t' << SKGServices::doubleToString(-sop.getQuantity())
                               << '\t' << SKGServices::doubleToString(op.getNumber())
                               << '\t' << (op.getStatus() == SKGOperationObject::CHECKED ? "Y" : "N")
                               << '\t' << sop.getComment()
                               << endl;
                    }

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

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

            SKGENDTRANSACTION(m_importer->getDocument(),  err);
        }
    }

    // Close file
    file.finalize();
    file.close();
    return err;
}

QString SKGImportPluginIif::getMimeTypeFilter() const
{
    return "*.iif|" % i18nc("A file format", "Intuit Interchange Format file");
}

#include "skgimportpluginiif.moc"
