/***************************************************************************
 *   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 GSB import / export.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgimportplugingsb.h"
#include "skgtraces.h"
#include "skgservices.h"
#include "skgbankincludes.h"
#include "skgobjectbase.h"
#include "skgimportexportmanager.h"
#include "skgpayeeobject.h"

#include <klocale.h>
#include <kfilterdev.h>

#include <QDomDocument>
#include <qfileinfo.h>

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGImportPluginGsbFactory, registerPlugin<SKGImportPluginGsb>();)
/**
 * This plugin export.
 */
K_EXPORT_PLUGIN(SKGImportPluginGsbFactory("skrooge_import_gsb", "skrooge_import_gsb"))

SKGImportPluginGsb::SKGImportPluginGsb(QObject* iImporter, const QVariantList& iArg)
    : SKGImportPlugin(iImporter)
{
    SKGTRACEIN(10, "SKGImportPluginGsb::SKGImportPluginGsb");
    Q_UNUSED(iArg);
}

SKGImportPluginGsb::~SKGImportPluginGsb()
{}

bool SKGImportPluginGsb::isImportPossible()
{
    SKGTRACEIN(10, "SKGImportPluginCsv::isExportPossible");
    return (!m_importer ? true : QFileInfo(m_importer->getFileName()).suffix().toUpper() == "GSB");
}

SKGError SKGImportPluginGsb::importFile()
{
    if (!m_importer) return SKGError(ERR_ABORT, i18nc("Error message", "Invalid parameters"));
    SKGError err;
    SKGTRACEINRC(2, "SKGImportPluginGsb::importFile", err);
    QString iFileName = m_importer->getFileName();
    SKGTRACEL(10) << "Input filename=" << iFileName << endl;

    //Initialisation
    //Open file
    QIODevice* file = KFilterDev::deviceForFile(iFileName, "application/x-gzip");
    if (!file->open(QIODevice::ReadOnly)) {
        err.setReturnCode(ERR_INVALIDARG);
        err.setMessage(i18nc("Error message",  "Open file '%1' failed", iFileName));
    } else {
        QDomDocument doc;

        //Set the file without uncompression
        QString errorMsg;
        int errorLine = 0;
        int errorCol = 0;
        bool contentOK = doc.setContent(file->readAll(), &errorMsg, &errorLine, &errorCol);
        file->close();

        //Get root
        QDomElement docElem = doc.documentElement();

        if (!contentOK) {
            err.setReturnCode(ERR_ABORT);
            err.setMessage(i18nc("Error message",  "%1-%2: '%3'", errorLine, errorCol, errorMsg));
        } else {
            //Check version
            QDomElement general = docElem.firstChildElement("General");
            if (general.isNull()) {
                err.setReturnCode(ERR_ABORT);
                err.setMessage(i18nc("Error message",  "Bad version of Grisbi document. Version must be >= 0.6.0"));
                contentOK = false;
            }
        }

        if (!contentOK) {
            err.addError(ERR_INVALIDARG, i18nc("Error message",  "Invalid XML content in file '%1'", iFileName));
        } else {
            err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "GSB"), 9);

            QMap<QString, SKGUnitObject> mapIdUnit;
            QMap<QString, SKGBankObject> mapIdBank;
            QMap<QString, SKGAccountObject> mapIdAccount;
            QMap<QString, SKGCategoryObject> mapIdCategory;
            QMap<QString, SKGOperationObject> mapIdOperation;
            QMap<QString, SKGPayeeObject> mapIdPayee;
            QMap<QString, QString> mapIdMode;

            //Specifications: http://wiki.grisbi.org/doku.php?id=docs:dev:format_gsb

            //Step 1-Create units
            if (!err) {
                QDomNodeList currencyList = docElem.elementsByTagName("Currency");
                int nb = currencyList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import units"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement currency = currencyList.at(i).toElement();

                    //Creation of the units
                    SKGUnitObject unitObj(m_importer->getDocument());
                    if (!err) err = unitObj.setName(getAttribute(currency,  "Na"));
                    if (!err) err = unitObj.setSymbol(getAttribute(currency,  "Co"));
                    if (!err) err = unitObj.setNumberDecimal(SKGServices::stringToInt(getAttribute(currency,  "Fl")));
                    if (!err) err = unitObj.save();

                    mapIdUnit[getAttribute(currency,  "Nb")] = unitObj;

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(1);

            //Step 2-Create banks
            if (!err) {
                QDomNodeList bankList = docElem.elementsByTagName("Bank");
                int nb = bankList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import banks"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement bank = bankList.at(i).toElement();

                    //Creation of the banks
                    SKGBankObject bankObj(m_importer->getDocument());
                    if (!err) err = bankObj.setName(getAttribute(bank,  "Na"));
                    if (!err) err = bankObj.setNumber(getAttribute(bank,  "BIC"));
                    if (!err) err = bankObj.save();

                    mapIdBank[getAttribute(bank,  "Nb")] = bankObj;

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(2);

            //Step 3-Create accounts
            SKGBankObject bankDefault(m_importer->getDocument());
            if (!err) err = bankDefault.setName("GRISBI");
            if (!err) err = bankDefault.save();
            if (!err) {
                QDomNodeList accountList = docElem.elementsByTagName("Account");
                int nb = accountList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import accounts"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement account = accountList.at(i).toElement();

                    //Creation of the account
                    SKGAccountObject accountObj;
                    SKGBankObject bank = mapIdBank[getAttribute(account,  "Bank")];
                    if (!bank.exist()) bank = bankDefault;
                    if (!err) err = bank.addAccount(accountObj);
                    if (!err) err = accountObj.setName(getAttribute(account,  "Name"));
                    if (!err) err = accountObj.setNumber(getAttribute(account,  "Bank_account_number"));
                    if (!err) err = accountObj.setComment(getAttribute(account,  "Comment"));
                    if (!err) err = accountObj.setAgencyAddress(getAttribute(account,  "Owner_address"));
                    if (!err) err = accountObj.setClosed(getAttribute(account,  "Closed_account") != "0");
                    if (!err) err = accountObj.save();
                    if (!err) {
                        accountObj.setClosed(false); //To be sure that the modification is possible. NOT SAVED
                        err = accountObj.setInitialBalance(SKGServices::stringToDouble(getAttribute(account,  "Initial_balance")), mapIdUnit[getAttribute(account,  "Currency")]);
                    }

                    mapIdAccount[getAttribute(account,  "Number")] = accountObj;

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(3);

            //Step 4-Get payees
            if (!err) {
                QDomNodeList partyList = docElem.elementsByTagName("Party");
                int nb = partyList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import payees"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get payee object
                    QDomElement party = partyList.at(i).toElement();
                    SKGPayeeObject payeeObject;
                    err = SKGPayeeObject::createPayee(m_importer->getDocument(), getAttribute(party,  "Na"), payeeObject);
                    mapIdPayee[getAttribute(party,  "Nb")] = payeeObject;

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(4);

            //Step 5-Get payement mode
            if (!err) {
                QDomNodeList paymentList = docElem.elementsByTagName("Payment");
                int nb = paymentList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import payment mode"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get mode object
                    QDomElement payment = paymentList.at(i).toElement();
                    mapIdMode[getAttribute(payment,  "Number")] = getAttribute(payment,  "Name");

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(5);

            //Step 6-Create categories
            if (!err) {
                QDomNodeList categoryList = docElem.elementsByTagName("Category");
                int nb = categoryList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import categories"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement category = categoryList.at(i).toElement();

                    //Creation of the categories
                    SKGCategoryObject catObj(m_importer->getDocument());
                    if (!err) err = catObj.setName(getAttribute(category,  "Na"));
                    if (!err) err = catObj.save();

                    mapIdCategory[getAttribute(category,  "Nb")] = catObj;

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(6);

            //Step 7-Create subcategories
            if (!err) {
                QDomNodeList categoryList = docElem.elementsByTagName("Sub_category");
                int nb = categoryList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import categories"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement category = categoryList.at(i).toElement();

                    //Creation of the subcategories
                    SKGCategoryObject catParentObj = mapIdCategory[getAttribute(category,  "Nbc")];
                    SKGCategoryObject catObj;
                    if (!err) err = catParentObj.addCategory(catObj);
                    if (!err) err = catObj.setName(getAttribute(category,  "Na"));
                    if (!err) err = catObj.save();

                    QString id = SKGServices::intToString(10000 * SKGServices::stringToInt(getAttribute(category,  "Nbc")) + SKGServices::stringToInt(getAttribute(category,  "Nb")));
                    mapIdCategory[id] = catObj;

                    if (!err) err = m_importer->getDocument()->stepForward(i + 1);
                }

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(7);

            //Step 8-Create transaction
            if (!err) {
                QDomNodeList transactionList = docElem.elementsByTagName("Transaction");
                int nb = transactionList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import operations"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement transaction = transactionList.at(i).toElement();

                    //Creation of the operation
                    SKGOperationObject opObj;
                    QString parentOpId = getAttribute(transaction,  "Mo");
                    if (parentOpId == "0") {
                        SKGAccountObject account = mapIdAccount[getAttribute(transaction,  "Ac")];
                        if (!err) err = account.addOperation(opObj);
                        if (!err) err = opObj.setDate(QDate::fromString(getAttribute(transaction,  "Dt"), "MM/dd/yyyy"));
                        if (!err) err = opObj.setUnit(mapIdUnit[ getAttribute(transaction,  "Cu")]);
                        if (!err) err = opObj.setPayee(mapIdPayee[ getAttribute(transaction,  "Pa")]);
                        if (!err) err = opObj.setMode(mapIdMode[getAttribute(transaction,  "Pn")]);
                        if (!err) err = opObj.setNumber(SKGServices::stringToInt(getAttribute(transaction,  "Pc")));
                        if (!err) err = opObj.setComment(getAttribute(transaction,  "No"));
                        if (!err) err = opObj.setImported(true);
                        if (!err) err = opObj.setImportID("GSB-" % getAttribute(transaction,  "Nb"));
                        if (!err) err = opObj.setStatus(getAttribute(transaction,  "Re") == "0" ? SKGOperationObject::NONE : SKGOperationObject::CHECKED);
                        if (!err) err = opObj.setGroupOperation(mapIdOperation[getAttribute(transaction,  "Trt")]);
                        if (!err) err = opObj.save();
                    } else {
                        opObj = mapIdOperation[parentOpId];
                    }

                    if (getAttribute(transaction,  "Br") == "0") {
                        SKGSubOperationObject subObj;
                        if (!err) err = opObj.addSubOperation(subObj);
                        QString id = SKGServices::intToString(10000 * SKGServices::stringToInt(getAttribute(transaction,  "Ca")) + SKGServices::stringToInt(getAttribute(transaction,  "Sca")));
                        if (!err) err = subObj.setCategory(mapIdCategory[id]);
                        if (!err) err = subObj.setComment(getAttribute(transaction,  "No"));
                        if (!err) err = subObj.setQuantity(SKGServices::stringToDouble(getAttribute(transaction,  "Am")));
                        if (!err) err = subObj.save();
                    }

                    mapIdOperation[getAttribute(transaction,  "Nb")] = opObj;

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

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(8);

            //Step 9-Create scheduled transaction
            if (!err) {
                QDomNodeList scheduledList = docElem.elementsByTagName("Scheduled");
                int nb = scheduledList.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import scheduled operations"), nb);
                for (int i = 0; !err && i < nb; ++i) {
                    //Get account object
                    QDomElement transaction = scheduledList.at(i).toElement();

                    //Creation of the operation
                    SKGAccountObject account = mapIdAccount[getAttribute(transaction,  "Ac")];
                    SKGOperationObject opObj;
                    bool isClosed = account.isClosed();
                    if (isClosed) err = account.setClosed(false);
                    if (!err) err = account.addOperation(opObj);
                    if (!err && isClosed) err = account.setClosed(true);
                    QDate firstDate = QDate::fromString(getAttribute(transaction,  "Dt"), "MM/dd/yyyy");
                    if (!err) err = opObj.setDate(firstDate);
                    if (!err) err = opObj.setUnit(mapIdUnit[ getAttribute(transaction,  "Cu")]);
                    if (!err) err = opObj.setPayee(mapIdPayee[ getAttribute(transaction,  "Pa")]);
                    if (!err) err = opObj.setMode(mapIdMode[getAttribute(transaction,  "Pn")]);
                    if (!err) err = opObj.setNumber(SKGServices::stringToInt(getAttribute(transaction,  "Pc")));
                    if (!err) err = opObj.setComment(getAttribute(transaction,  "No"));
                    if (!err) err = opObj.setImported(true);
                    if (!err) err = opObj.setImportID("GSB-" % getAttribute(transaction,  "Nb"));
                    if (!err) err = opObj.setTemplate(true);
                    if (!err) err = opObj.save();

                    SKGSubOperationObject subObj;
                    if (!err) err = opObj.addSubOperation(subObj);
                    QString id = SKGServices::intToString(10000 * SKGServices::stringToInt(getAttribute(transaction,  "Ca")) + SKGServices::stringToInt(getAttribute(transaction,  "Sca")));
                    if (!err) err = subObj.setCategory(mapIdCategory[id]);
                    if (!err) err = subObj.setComment(getAttribute(transaction,  "No"));
                    if (!err) err = subObj.setQuantity(SKGServices::stringToDouble(getAttribute(transaction,  "Am")));
                    if (!err) err = subObj.save();

                    QString Tra = getAttribute(transaction,  "Tra");
                    if (Tra != "0") {
                        //This is a transfer
                        SKGOperationObject opObj2;
                        if (!err) err = opObj.duplicate(opObj2, opObj.getDate(), true);
                        if (!err) err = opObj2.setImported(true);
                        if (!err) err = opObj2.setImportID("GSB-" % getAttribute(transaction,  "Nb") % "_TR");
                        if (!err) err = opObj2.save();

                        SKGObjectBase::SKGListSKGObjectBase subObjs2;
                        if (!err) err = opObj2.getSubOperations(subObjs2);
                        if (!err && subObjs2.count()) {
                            SKGSubOperationObject subObj2 = subObjs2.at(0);
                            err = subObj2.setQuantity(-subObj.getQuantity());
                            err = subObj2.save();
                        }

                        //Group operations
                        if (!err) err = opObj.setGroupOperation(opObj2);
                        if (!err) err = opObj.save();
                    }

                    //Create the schedule
                    SKGRecurrentOperationObject recuObj;
                    if (!err) err = opObj.addRecurrentOperation(recuObj);
                    if (!err) err = recuObj.setAutoWriteDays(0);
                    if (!err) err = recuObj.autoWriteEnabled(getAttribute(transaction,  "Au") != "0");
                    if (!err) {
                        //text_frequency [] = { _("Once"), _("Weekly"), _("Monthly"), _("two months"), ("trimester"), _("Yearly"), _("Custom"), NULL };
                        int occu = 1;
                        SKGRecurrentOperationObject::PeriodUnit period = SKGRecurrentOperationObject::DAY;
                        QString Pe = getAttribute(transaction,  "Pe");
                        if (Pe == "0") {
                            period = SKGRecurrentOperationObject::MONTH;
                            if (!err) err = recuObj.timeLimit(true);
                            if (!err) err = recuObj.setTimeLimit(1);

                        } else if (Pe == "1") {
                            period = SKGRecurrentOperationObject::WEEK;
                        } else if (Pe == "2") {
                            period = SKGRecurrentOperationObject::MONTH;
                        } else if (Pe == "3") {
                            occu = 2;
                            period = SKGRecurrentOperationObject::MONTH;
                        } else if (Pe == "4") {
                            occu = 3;
                            period = SKGRecurrentOperationObject::MONTH;
                        } else if (Pe == "5") {
                            period = SKGRecurrentOperationObject::YEAR;
                        } else if (Pe == "6") {
                            //text_frequency_user [] = { _("Days"), _("Weeks"), _("Months"), _("Years"), NULL };
                            occu = SKGServices::stringToInt(getAttribute(transaction,  "Pep"));
                            QString Pei = getAttribute(transaction,  "Pei");
                            if (Pei == "0") period = SKGRecurrentOperationObject::DAY;
                            else if (Pei == "1") period = SKGRecurrentOperationObject::WEEK;
                            else if (Pei == "2") period = SKGRecurrentOperationObject::MONTH;
                            else period = SKGRecurrentOperationObject::YEAR;
                        }

                        if (!err) err = recuObj.setPeriodUnit(period);
                        if (!err) err = recuObj.setPeriodIncrement(occu);

                        QString Dtl = getAttribute(transaction,  "Dtl");
                        if (!err && !Dtl.isEmpty()) {
                            if (!err) err = recuObj.timeLimit(true);
                            if (!err) err = recuObj.setTimeLimit(QDate::fromString(Dtl, "MM/dd/yyyy"));
                        }
                    }
                    if (!err) err = recuObj.save();

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

                if (!err) err = m_importer->getDocument()->endTransaction(true);
                else  m_importer->getDocument()->endTransaction(false);
            }
            if (!err) err = m_importer->getDocument()->stepForward(9);

            if (!err) err = m_importer->getDocument()->endTransaction(true);
            else  m_importer->getDocument()->endTransaction(false);

            if (!err) err = m_importer->getDocument()->executeSqliteOrder("ANALYZE");
        }
    }

    delete file;
    return err;
}


QString SKGImportPluginGsb::getAttribute(const QDomElement& iElement, const QString& iAttribute)
{
    QString val = iElement.attribute(iAttribute);
    if (val == "(null)") val = "";
    return val;
}
QString SKGImportPluginGsb::getMimeTypeFilter() const
{
    return "*.gsb|" % i18nc("A file format", "Grisbi file");
}

#include "skgimportplugingsb.moc"
