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

#include <klocale.h>
#include <kgenericfactory.h>
#include <kstandarddirs.h>

#include <QDir>
#include <QProcess>
#include <QUuid>
#include <qjson/parser.h>

QMap<QString, SKGUnitObject> SKGImportPluginMny::m_mapIdUnit;
QMap<QString, SKGAccountObject> SKGImportPluginMny::m_mapIdAccount;
QMap<QString, SKGCategoryObject> SKGImportPluginMny::m_mapIdCategory;
QMap<QString, SKGPayeeObject> SKGImportPluginMny::m_mapIdPayee;

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGImportPluginMnyFactory, registerPlugin<SKGImportPluginMny>();)
/**
 * This plugin export.
 */
K_EXPORT_PLUGIN(SKGImportPluginMnyFactory("skrooge_import_kmy", "skrooge_import_kmy"))

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

    m_parameters["password"] = "";
}

SKGImportPluginMny::~SKGImportPluginMny()
{}

bool SKGImportPluginMny::removeDir(const QString& dirName)
{
    bool result = false;
    QDir dir(dirName);

    if (dir.exists(dirName)) {
        Q_FOREACH(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
            if (info.isDir()) {
                result = SKGImportPluginMny::removeDir(info.absoluteFilePath());
            } else {
                result = QFile::remove(info.absoluteFilePath());
            }

            if (!result) {
                return result;
            }
        }
        result = dir.rmdir(dirName);
    }
    return result;
}

bool SKGImportPluginMny::isImportPossible()
{
    SKGTRACEIN(10, "SKGImportPluginMny::isImportPossible");
    if (!m_importer) return true;
    QString extension = m_importer->getFileNameExtension();
    return (extension == "MNY");
}

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

    SKGError err;
    SKGTRACEINRC(2, "SKGImportPluginMny::importFile", err);

    //Check install
    if (KStandardDirs::findExe("java").isEmpty()) {
        err.setReturnCode(ERR_INSTALL);
        err.setMessage(i18nc("Error message",  "The java application is not installed"));
        err.setProperty("application", "java");
        return err;
    }

    QString sunriise_jar = QDir::homePath() % "/.skrooge/sunriise.jar";
    if (!QFile(sunriise_jar).exists()) {
        if (m_parameters.contains("install")) {
            QDir::home().mkdir(".skrooge");
            err = SKGServices::upload(KUrl("http://skrooge.org/files/sunriise.jar"), KUrl(sunriise_jar));
            if (err) return err;
        } else {
            err.setReturnCode(ERR_INSTALL);
            err.setMessage(i18nc("Error message",  "The sunriise application is needed for Microsoft Money import but is not installed in '%1'", sunriise_jar));
            err.setProperty("application", "sunriise");
            return err;
        }
    }

    //Initialisation
    m_mapIdUnit.clear();
    m_mapIdAccount.clear();
    m_mapIdCategory.clear();
    m_mapIdPayee.clear();

    //Execute sunriise

    QString uniqueId = QUuid::createUuid().toString();
    QString temporaryPath = QDir::tempPath() % "/" % uniqueId;
    removeDir(temporaryPath);
    QDir::temp().mkdir(uniqueId);
    QDir temporaryDir(temporaryPath);

    QString cmd = "java -cp " % sunriise_jar % " com/le/sunriise/export/ExportToJSON " % m_importer->getLocalFileName() % " " % m_parameters["password"] % " " % temporaryPath;
    QProcess p;
    {
        SKGTRACEINRC(10, "SKGImportPluginMny::importFile-SUNRIISE", err);
        p.start(cmd);
    }
    if (p.waitForFinished() && p.exitCode() == 0) {
        //Import
        err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import %1 file", "MNY"), 4);
        //Step 1-Import categories
        if (!err) {
            /*[ {
            "id" : 137,
            "parentId" : 130,
            "name" : "Other Income",
            "classificationId" : 0,
            "level" : 1
            },*/
            QJson::Parser parser;
            bool ok = false;
            QFile file(temporaryPath % "/categories.json");
            file.open(QIODevice::ReadOnly);
            QByteArray json = file.readAll();
            file.close();
            QVariantList list = parser.parse(json, &ok).toList();
            if (!ok || json.isEmpty()) {
                err.setReturnCode(ERR_FAIL);
                err.setMessage(parser.errorString());

                err.addError(ERR_FAIL, i18nc("Error message",  "Error during parsing of '%1'", file.fileName()));
            } else {
                SKGTRACEINRC(10, "SKGImportPluginMny::importFile-CATEGORY", err);
                int nb = list.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import categories"), nb);
                //Create categories
                int index = 0;
                for (int t = 0; !err && t < 20 && index < nb; ++t) {
                    for (int i = 0; !err && i < nb && index < nb; ++i) {
                        QVariantMap category = list[i].toMap();

                        QString parentId = category["parentId"].toString();
                        QString id = category["id"].toString();
                        QString name = category["name"].toString();

                        if (parentId.isEmpty() || m_mapIdCategory.contains(parentId)) {
                            SKGCategoryObject catObject(m_importer->getDocument());
                            err = catObject.setName(name);
                            if (!err && !parentId.isEmpty()) err = catObject.setParentCategory(m_mapIdCategory[parentId]);
                            if (!err) err = catObject.save();

                            m_mapIdCategory[id] = catObject;

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

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

        //Step -Import payees
        if (!err) {
            QJson::Parser parser;
            bool ok = false;
            QFile file(temporaryPath % "/payees.json");
            file.open(QIODevice::ReadOnly);
            QByteArray json = file.readAll();
            file.close();
            QVariantList list = parser.parse(json, &ok).toList();
            if (!ok || json.isEmpty()) {
                err.setReturnCode(ERR_FAIL);
                err.setMessage(parser.errorString());

                err.addError(ERR_FAIL, i18nc("Error message",  "Error during parsing of '%1'", file.fileName()));
            } else {
                SKGTRACEINRC(10, "SKGImportPluginMny::importFile-PAYEE", err);
                int nb = list.count();
                err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import payees"), nb);
                //Create categories
                for (int i = 0; !err && i < nb; ++i) {
                    QVariantMap payee = list[i].toMap();

                    QString id = payee["id"].toString();
                    QString name = payee["name"].toString();

                    //Is already created?
                    SKGPayeeObject payeeObject(m_importer->getDocument());
                    err = payeeObject.setName(name);
                    if (!err) err = payeeObject.save();

                    m_mapIdPayee[id] = 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(2);
        }

        //Step 3-Import accounts
        if (!err) {
            /*
            {
                "id" : 1,
                "name" : "Investments to Watch",
                "relatedToAccountId" : null,
                "relatedToAccount" : null,
                "type" : 5,
                "accountType" : "INVESTMENT",
                "closed" : false,
                "startingBalance" : 0.0000,
                "currentBalance" : 0,
                "currencyId" : 18,
                "currencyCode" : "GBP",
                "retirement" : false,
                "investmentSubType" : -1,
                "securityHoldings" : [ ],
                "amountLimit" : null,
                "creditCard" : false,
                "401k403b" : false
            }*/
            //List directories
            QStringList list = temporaryDir.entryList(QStringList() << "*.d", QDir::Dirs);
            SKGTRACEINRC(10, "SKGImportPluginMny::importFile-ACCOUNTS", err);
            int nb = list.count();
            err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import accounts"), nb);

            SKGBankObject bank(m_importer->getDocument());
            if (!err) err = bank.setName("Microsof Money");
            if (!err) err = bank.save();

            //Create accounts
            for (int i = 0; !err && i < nb; ++i) {
                QString accountDir = list[i];

                QJson::Parser parser;
                bool ok = false;
                QFile file(temporaryPath % "/" % accountDir % "/account.json");
                file.open(QIODevice::ReadOnly);
                QByteArray json = file.readAll();
                file.close();
                QVariantMap account = parser.parse(json, &ok).toMap();
                if (!ok || json.isEmpty()) {
                    err.setReturnCode(ERR_FAIL);
                    err.setMessage(parser.errorString());

                    err.addError(ERR_FAIL, i18nc("Error message",  "Error during parsing of '%1'", file.fileName()));
                } else {
                    //Create currency
                    SKGUnitObject unitObj;
                    SKGUnitObject::createCurrencyUnit(m_importer->getDocument(), account["currencyCode"].toString(), unitObj);

                    //Create account
                    SKGAccountObject accountObj;
                    err = bank.addAccount(accountObj);
                    if (!err) err = accountObj.setName(account["name"].toString());
                    int type = account["type"].toInt();
                    if (!err) err = accountObj.setType(type == 0 ? SKGAccountObject::CURRENT :
                                                           type == 1 ? SKGAccountObject::CREDITCARD :
                                                           type == 3 ? SKGAccountObject::ASSETS :
                                                           type == 5 ? SKGAccountObject::INVESTMENT :
                                                           type == 6 ? SKGAccountObject::LOAN :
                                                           SKGAccountObject::OTHER);//TODO
                    if (!err) err = accountObj.save();

                    //Update initial balance
                    if (!err) err = accountObj.setInitialBalance(account["startingBalance"].toDouble(), unitObj);

                    if (!err) err = accountObj.setClosed(account["closed"].toBool());
                    if (!err) err = accountObj.save();

                    accountObj.setClosed(false); //Set account open but only in memory. No save
                    m_mapIdAccount[account["id"].toString()] = 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-Import operation
        if (!err) {
            //List directories
            QStringList list = temporaryDir.entryList(QStringList() << "*.d", QDir::Dirs);
            SKGTRACEINRC(10, "SKGImportPluginMny::importFile-OPERATIONS", err);
            int nb = list.count();
            err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import operations"), nb);

            //Create accounts
            int index = 0;
            for (int i = 0; !err && i < nb; ++i) {
                QString accountDir = list[i];

                QJson::Parser parser;
                bool ok = false;
                QFile file(temporaryPath % "/" % accountDir % "/transactions.json");
                file.open(QIODevice::ReadOnly);
                QByteArray json = file.readAll();
                file.close();
                QVariantList operations = parser.parse(json, &ok).toList();
                if (!ok || json.isEmpty()) {
                    err.setReturnCode(ERR_FAIL);
                    err.setMessage(parser.errorString());

                    err.addError(ERR_FAIL, i18nc("Error message",  "Error during parsing of '%1'", file.fileName()));
                } else {
                    int nbo = operations.count();
                    err = m_importer->getDocument()->beginTransaction("#INTERNAL#" % i18nc("Import step", "Import operations"), nbo);
                    for (int j = 0; !err && j < nbo; ++j) {
                        QVariantMap operation = operations[j].toMap();
                        if (!operation["void"].toBool()) {
                            SKGAccountObject accountObj = m_mapIdAccount[operation["accountId"].toString()];

                            SKGUnitObject unitObj;
                            if (!err) err = accountObj.getUnit(unitObj);

                            SKGOperationObject operationObj;
                            if (!err) err = accountObj.addOperation(operationObj);
                            if (!err) err = operationObj.setUnit(unitObj);
                            if (!err) err = operationObj.setDate(QDateTime::fromMSecsSinceEpoch(operation["date"].toULongLong()).date());
                            if (!err) err = operationObj.setComment(operation["memo"].toString());
                            if (!err) {
                                //The number is something like "0     4220725"
                                QString number = operation["number"].toString();
                                if (!number.isEmpty()) {
                                    QStringList ln = SKGServices::splitCSVLine(number, ' ');
                                    err = operationObj.setNumber(SKGServices::stringToInt(ln.at(ln.count() - 1)));
                                }
                            }
                            if (!err) err = operationObj.setAttribute("t_imported", "T");
                            if (!err) err = operationObj.setImportID("MNY-" % operation["id"].toString());
                            QString payId = operation["payeeId"].toString();
                            if (!payId.isEmpty() && !err) err = operationObj.setPayee(m_mapIdPayee[payId]);
                            if (!err) err = operationObj.setStatus(operation["reconciled"].toBool() ? SKGOperationObject::CHECKED :
                                                                       operation["cleared"].toBool() ? SKGOperationObject::POINTED :
                                                                       SKGOperationObject::NONE);
                            if (!err) err = operationObj.save(false);

                            double amount = operation["amount"].toDouble();

                            //Is it a split?
                            QVariantList splits = operation["splits"].toList();
                            int nbs = splits.count();
                            if (nbs) {
                                //Yes
                                for (int k = 0; !err && k < nbs; ++k) {
                                    QVariantMap split = splits[k].toMap();
                                    QVariantMap transaction = split["transaction"].toMap();

                                    SKGSubOperationObject subOperationObj;
                                    if (!err) err = operationObj.addSubOperation(subOperationObj);
                                    QString catId = transaction["categoryId"].toString();
                                    if (!catId.isEmpty() && !err) err = subOperationObj.setCategory(m_mapIdCategory[catId]);
                                    double splitAmount = transaction["amount"].toDouble();
                                    if (!err) err = subOperationObj.setQuantity(splitAmount);
                                    if (!err) err = subOperationObj.setComment(operation["memo"].toString());
                                    if (!err) err = subOperationObj.save(false, false);
                                    amount -= splitAmount;
                                }

                                //Is the amount equal to the sum of split?
                                if (qAbs(amount) > 0.00001) {
                                    //Create one more sub operation to align amounts
                                    SKGSubOperationObject subOperationObj;
                                    if (!err) err = operationObj.addSubOperation(subOperationObj);
                                    if (!err) err = subOperationObj.setQuantity(amount);
                                    if (!err) err = subOperationObj.setComment(operation["memo"].toString());
                                    if (!err) err = subOperationObj.save(false, false);

                                    if (!err) err = m_importer->getDocument()->sendMessage(i18nc("Warning message", "The operation '%1' has been repaired because its amount was not equal to the sum of the amounts of its splits", operationObj.getDisplayName()), SKGDocument::Warning);
                                }
                            } else {
                                //No
                                SKGSubOperationObject subOperationObj;
                                if (!err) err = operationObj.addSubOperation(subOperationObj);
                                QString catId = operation["categoryId"].toString();
                                if (!catId.isEmpty() && !err) err = subOperationObj.setCategory(m_mapIdCategory[catId]);
                                if (!err) err = subOperationObj.setQuantity(amount);
                                if (!err) err = subOperationObj.setComment(operation["memo"].toString());
                                if (!err) err = subOperationObj.save(false, false);
                            }
                        }

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

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

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

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

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


    } else {
        if (p.exitCode() == 1) {
            err.setReturnCode(ERR_ENCRYPTION);
            err.setMessage(i18nc("Error message",  "Invalid password"));
        } else {
            err.setReturnCode(ERR_FAIL);
            err.setMessage(i18nc("Error message",  "The execution of '%1' failed", cmd));

            err.addError(ERR_FAIL, i18nc("Error message",  "The extraction from the Microsoft Money document '%1' failed", m_importer->getFileName().prettyUrl()));
        }
    }

    removeDir(temporaryPath);

    //Clean
    m_mapIdUnit.clear();
    m_mapIdAccount.clear();
    m_mapIdCategory.clear();
    m_mapIdPayee.clear();

    return err;
}

QString SKGImportPluginMny::getMimeTypeFilter() const
{
    return "*.mny|" % i18nc("A file format", "Microsoft Money document");
}

#include "skgimportpluginmny.moc"
