/***************************************************************************
 *   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 implements classes SKGOperationObject.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgoperationobject.h"
#include "skgrecurrentoperationobject.h"
#include "skgsuboperationobject.h"
#include "skgaccountobject.h"
#include "skgunitobject.h"
#include "skgpayeeobject.h"
#include "skgservices.h"
#include "skgdocument.h"
#include "skgtraces.h"

#include <klocale.h>

SKGOperationObject::SKGOperationObject(SKGDocument* iDocument, int iID) : SKGObjectBase(iDocument, "v_operation", iID)
{}

SKGOperationObject::~SKGOperationObject()
{}

SKGOperationObject::SKGOperationObject(const SKGOperationObject& iObject)
    : SKGObjectBase(iObject)
{}

SKGOperationObject::SKGOperationObject(const SKGObjectBase& iObject)
{
    if (iObject.getRealTable() == "operation") {
        copyFrom(iObject);
    } else {
        *this = SKGObjectBase(iObject.getDocument(), "v_operation", iObject.getID());
    }
}

const SKGOperationObject& SKGOperationObject::operator= (const SKGObjectBase& iObject)
{
    copyFrom(iObject);
    return *this;
}

SKGError SKGOperationObject::duplicate(SKGOperationObject& oOperation, const QDate& iDate, bool iTemplateMode) const
{
    SKGError err;
    SKGTRACEINRC(20, "SKGOperationObject::duplicate", err);
    //Create the duplicated operation
    oOperation = SKGOperationObject(getDocument(), this->getID());  //To be sure the object is on v_operation
    if (!err) err = oOperation.load();
    if (!err) err = oOperation.resetID();
    if (!err) err = oOperation.setDate(iDate);
    if (!err) err = oOperation.setStatus(SKGOperationObject::NONE);
    if (!err) err = oOperation.setImported(false);
    if (!err) err = oOperation.setTemplate(iTemplateMode);
    if (!err) err = oOperation.setImportID("");
    if (!err) err = oOperation.bookmark(false);
    if (!err) err = oOperation.setNumber(0);
    if (!err) err = oOperation.setGroupOperation(oOperation);    //We have to ungroup first to be able to group after
    if (!err) err = oOperation.save(false, false);    //Just an insert + no reload (doesn't work if the )

    //Duplicate subop
    if (!err) {
        SKGListSKGObjectBase subops;
        err = getSubOperations(subops);
        int nbsupops = subops.count();
        for (int i = 0; !err && i < nbsupops; ++i) {
            SKGSubOperationObject subop(subops.at(i));
            err = subop.resetID();
            if (!err) err = subop.setParentOperation(oOperation);
            if (!err) err = subop.save(false);    //Just an insert
        }
    }

    //Duplicate grouped operation to support recurrent transfers
    if (!err) {
        SKGListSKGObjectBase goupops;
        err = getGroupedOperations(goupops);
        int nbgoupops = goupops.count();
        for (int i = 0; !err && i < nbgoupops; ++i) {
            SKGOperationObject groupop(goupops.at(i));
            if (groupop != *this) {
                //Create the duplicated operation
                SKGOperationObject newgroupop = groupop;
                err = newgroupop.resetID();
                if (!err) err = newgroupop.setDate(iDate);
                if (!err) err = newgroupop.setStatus(SKGOperationObject::NONE);
                if (!err) err = newgroupop.setImported(false);
                if (!err) err = newgroupop.setTemplate(iTemplateMode);
                if (!err) err = newgroupop.setImportID("");
                if (!err) err = newgroupop.bookmark(false);
                if (!err) err = newgroupop.setNumber(0);
                if (!err) err = newgroupop.setGroupOperation(newgroupop);    //We have to ungroup first to be able to group after
                if (!err) err = newgroupop.setGroupOperation(oOperation);
                if (!err) err = newgroupop.save(false);    //Just an insert

                //Duplicate subop
                if (!err) {
                    SKGListSKGObjectBase subops;
                    err = groupop.getSubOperations(subops);
                    int nbsupops = subops.count();
                    for (int i = 0; !err && i < nbsupops; ++i) {
                        SKGSubOperationObject subop(subops.at(i));
                        err = subop.resetID();
                        if (!err) err = subop.setParentOperation(newgroupop);
                        if (!err) err = subop.save(false);    //Just an insert
                    }
                }
            }
        }
    }

    if (!err) err = oOperation.load();
    return err;
}


SKGError SKGOperationObject::getParentAccount(SKGAccountObject& oAccount) const
{
    SKGObjectBase objTmp;
    SKGError err = getDocument()->getObject("v_account", "id=" % getAttribute("rd_account_id"), objTmp);
    oAccount = objTmp;
    return err;
}

SKGError SKGOperationObject::setParentAccount(const SKGAccountObject& iAccount)
{
    SKGError err;
    QString currentAccount = getAttribute("rd_account_id");
    QString newAccount = SKGServices::intToString(iAccount.getID());
    if (newAccount == "0") err = SKGError(ERR_FAIL, i18nc("Error message",  "%1 failed because linked object is not yet saved in the database.", QString("SKGOperationObject::setParentAccount")));
    else {
        if (newAccount != currentAccount) {
            if (iAccount.isClosed()) err = SKGError(ERR_FAIL, i18nc("Error message",  "Impossible to add an operation in a closed account"));
            else err = setAttribute("rd_account_id", newAccount);
        }
    }
    return err;
}

SKGError SKGOperationObject::setMode(const QString& iMode)
{
    return setAttribute("t_mode", iMode);
}

QString SKGOperationObject::getMode() const
{
    return getAttribute("t_mode");
}

SKGError SKGOperationObject::setPayee(const SKGPayeeObject& iPayee)
{
    return setAttribute("r_payee_id", SKGServices::intToString(iPayee.getID()));
}

SKGError SKGOperationObject::getPayee(SKGPayeeObject& oPayee) const
{
    SKGError err = getDocument()->getObject("v_payee", "id=" % SKGServices::intToString(SKGServices::stringToInt(getAttribute("r_payee_id"))), oPayee);
    return err;
}

SKGError SKGOperationObject::setComment(const QString& iComment)
{
    return setAttribute("t_comment", iComment);
}

QString SKGOperationObject::getComment() const
{
    return getAttribute("t_comment");
}

SKGError SKGOperationObject::setNumber(qlonglong iNumber)
{
    return setAttribute("i_number", SKGServices::intToString(iNumber));
}

qlonglong SKGOperationObject::getNumber() const
{
    return SKGServices::stringToInt(getAttribute("i_number"));
}

SKGOperationObject::OperationStatus SKGOperationObject::getStatus() const
{
    QString t_status = getAttribute("t_status");
    if (t_status == "Y") return SKGOperationObject::CHECKED;
    else if (t_status == "P") return SKGOperationObject::POINTED;
    return SKGOperationObject::NONE;
}

SKGError SKGOperationObject::setStatus(SKGOperationObject::OperationStatus iStatus)
{
    return setAttribute("t_status", (iStatus == SKGOperationObject::CHECKED ? "Y" : (iStatus == SKGOperationObject::POINTED ? "P" : "N")));
}

SKGError SKGOperationObject::setDate(const QDate& iDate)
{
    return setAttribute("d_date", SKGServices::dateToSqlString(QDateTime(iDate)));
}

QDate SKGOperationObject::getDate() const
{
    return SKGServices::stringToTime(getAttribute("d_date")).date();
}

SKGError SKGOperationObject::getUnit(SKGUnitObject& oUnit) const
{
    SKGError err = (getDocument() == NULL ? SKGError(ERR_POINTER, i18nc("Error message", "Operation impossible because the document is missing")) : getDocument()->getObject("v_unit", "id=" % getAttribute("rc_unit_id"), oUnit));
    //SKGError err = getDocument()->getObject("v_unit", "id=" % getAttribute("rc_unit_id"), oUnit);
    return err;
}

SKGError SKGOperationObject::setUnit(const SKGUnitObject& iUnit)
{
    return setAttribute("rc_unit_id", SKGServices::intToString(iUnit.getID()));
}

bool SKGOperationObject::isInGroup() const
{
    return (getAttribute("i_group_id") != "0");
}

bool SKGOperationObject::isTransfer(SKGOperationObject& oOperation) const
{
    SKGTRACEIN(10, "SKGOperationObject::isTransfer")
    SKGObjectBase::SKGListSKGObjectBase ops;
    getGroupedOperations(ops);
    if (ops.count() == 2) oOperation = (*this == SKGOperationObject(ops.at(0)) ? ops.at(1) : ops.at(0));
    return (getAttribute("t_TRANSFER") == "Y");
}

SKGError SKGOperationObject::getGroupedOperations(SKGListSKGObjectBase& oGroupedOperations) const
{
    SKGError err;
    QString gpId1 = getAttribute("i_group_id");
    if (gpId1 == "0") oGroupedOperations.clear();
    else  err = getDocument()->getObjects("v_operation", "i_group_id=" % gpId1, oGroupedOperations);
    return err;
}

SKGError SKGOperationObject::getGroupOperation(SKGOperationObject& oOperation) const
{
    SKGError err = getDocument()->getObject("v_operation", "id=" % getAttribute("i_group_id"), oOperation);
    return err;
}

SKGError SKGOperationObject::setGroupOperation(const SKGOperationObject& iOperation)
{
    SKGError err;
    SKGTRACEINRC(20, "SKGOperationObject::setGroupOperation", err);

    //Is it a remove group ?
    if (iOperation == *this) {
        //Yes
        err = setAttribute("i_group_id", "0");
    } else {
        //Get previous groups
        QString group1 = getAttribute("i_group_id");
        QString group2 = iOperation.getAttribute("i_group_id");

        //Create a new group
        SKGStringListList result;
        err = getDocument()->executeSelectSqliteOrder("SELECT max(i_group_id) from operation", result);
        if (!err) {
            //Compute new group id
            QString newIdGroup('1');
            if (result.count() == 2) {
                newIdGroup = SKGServices::intToString(SKGServices::stringToInt(result.at(1).at(0)) + 1);
            }

            //Set group id
            SKGOperationObject op1 = SKGOperationObject(iOperation.getDocument(), iOperation.getID());
            err = op1.setAttribute("i_group_id", newIdGroup);
            if (!err) err = op1.save(true, false);

            if (!err) err = setAttribute("i_group_id", newIdGroup);

            //Update all objects of group2
            if (!err && !group1.isEmpty() && group1 != "0") err = getDocument()->executeSqliteOrder("UPDATE operation SET i_group_id=" % newIdGroup % " WHERE i_group_id=" % group1);

            //Update all objects of group2
            if (!err && !group1.isEmpty() && group2 != "0") err = getDocument()->executeSqliteOrder("UPDATE operation SET i_group_id=" % newIdGroup % " WHERE i_group_id=" % group2);
        }
    }

    return err;
}

SKGError SKGOperationObject::bookmark(bool iBookmark)
{
    return setAttribute("t_bookmarked", iBookmark ? "Y" : "N");

}

bool SKGOperationObject::isBookmarked() const
{
    return (getAttribute("t_bookmarked") == "Y" ? true : false);
}

SKGError SKGOperationObject::setImported(bool iImported)
{
    return setAttribute("t_imported", iImported ? "Y" : "N");

}

bool SKGOperationObject::isImported() const
{
    return (getAttribute("t_imported") != "N" ? true : false);
}

SKGError SKGOperationObject::setImportID(const QString& iImportID)
{
    SKGError err = setAttribute("t_import_id", iImportID);
    if (!err && !iImportID.isEmpty()) err = setAttribute("t_imported", "T");
    return err;
}

QString SKGOperationObject::getImportID() const
{
    return getAttribute("t_import_id");
}

SKGError SKGOperationObject::setTemplate(bool iTemplate)
{
    return setAttribute("t_template", iTemplate ? "Y" : "N");

}

bool SKGOperationObject::isTemplate() const
{
    return (getAttribute("t_template") != "N" ? true : false);
}

int SKGOperationObject::getNbSubOperations() const
{
    return SKGServices::stringToInt(getAttribute("i_NBSUBOPERATIONS"));
}

SKGError SKGOperationObject::addSubOperation(SKGSubOperationObject& oSubOperation)
{
    SKGError err;
    if (getID() == 0) err = SKGError(ERR_FAIL, i18nc("Error message",  "%1 failed because linked object is not yet saved in the database.", QString("SKGOperationObject::addSubOperation")));
    else {
        oSubOperation = SKGSubOperationObject(getDocument());
        err = oSubOperation.setParentOperation(*this);
    }
    return err;
}

SKGError SKGOperationObject::getSubOperations(SKGListSKGObjectBase& oSubOperations) const
{
    SKGError err;
    if (getID() == 0) err = SKGError(ERR_FAIL, i18nc("Error message",  "%1 failed because linked object is not yet saved in the database.", QString("SKGOperationObject::getSubOperations")));
    else {

        err = getDocument()->getObjects("v_suboperation",
                                        "rd_operation_id=" % SKGServices::intToString(getID()), oSubOperations);
    }
    return err;
}

double SKGOperationObject::getCurrentAmount() const
{
    return SKGServices::stringToDouble(getAttribute("f_CURRENTAMOUNT"));
}

double SKGOperationObject::getBalance() const
{
    double output = 0.0;
    SKGStringListList result;
    SKGError err = getDocument()->executeSelectSqliteOrder("SELECT TOTAL(f_CURRENTAMOUNT) FROM v_operation WHERE t_template='N' AND "
                   "rd_account_id=" % getAttribute("rd_account_id") % " AND (d_date<'" % getAttribute("d_date") % "' OR "
                   "(d_date='" % getAttribute("d_date") % "' AND id<=" % SKGServices::intToString(getID()) % "))", result);
    if (!err) {
        output = SKGServices::stringToDouble(result.at(1).at(0));
    }

    return output;
}

double SKGOperationObject::getAmount(const QDate& iDate) const
{
    //Get quantity
    double quantity = SKGServices::stringToDouble(getAttribute("f_QUANTITY"));

    //Is the unit value already in cache ?
    double coef = 1;
    QString val = getDocument()->getCachedValue("unitvalue-" % getAttribute("rc_unit_id"));
    if (!val.isEmpty()) {
        //Yes
        coef = SKGServices::stringToDouble(val);
    } else {
        //No
        SKGUnitObject unit;
        if (getUnit(unit).isSucceeded()) coef = unit.getAmount(iDate);
    }

    return coef * quantity;
}

SKGError SKGOperationObject::addRecurrentOperation(SKGRecurrentOperationObject& oRecurrentOperation) const
{
    SKGError err;
    if (getID() == 0) err = SKGError(ERR_FAIL, i18nc("Error message",  "%1 failed because linked object is not yet saved in the database.", QString("SKGOperationObject::addRecurrentOperation")));
    else {
        oRecurrentOperation = SKGRecurrentOperationObject(getDocument());
        err = oRecurrentOperation.setParentOperation(*this);
        if (!err) oRecurrentOperation.setDate(getDate());
    }
    return err;
}

SKGError SKGOperationObject::getRecurrentOperations(SKGListSKGObjectBase& oRecurrentOperation) const
{
    SKGError err;
    if (getID() == 0) err = SKGError(ERR_FAIL, i18nc("Error message",  "%1 failed because linked object is not yet saved in the database.", QString("SKGOperationObject::getRecurrentOperation")));
    else {
        err = getDocument()->getObjects("v_recurrentoperation",
                                        "rd_operation_id=" % SKGServices::intToString(getID()), oRecurrentOperation);
    }
    return err;
}

SKGError SKGOperationObject::mergeAttribute(const SKGOperationObject& iDeletedOne)
{
    //Merge operation
    SKGError err = setDate(iDeletedOne.getDate());
    if (!err) err = setImportID(iDeletedOne.getImportID());
    if (!err) err = setAttribute("t_imported", iDeletedOne.getAttribute("t_imported"));
    if (!err && getComment().isEmpty()) err = setComment(iDeletedOne.getComment());
    SKGPayeeObject payee;
    getPayee(payee);
    if (!err) err = setPayee(payee);
    if (!err && getMode().isEmpty()) err = setMode(iDeletedOne.getMode());
    if (!err && !isBookmarked()) err = bookmark(iDeletedOne.isBookmarked());
    if (!err && !getNumber()) err = setNumber(iDeletedOne.getNumber());
    if (!err) err = save();

    //Merge suboperations
    if (qAbs(getCurrentAmount() - iDeletedOne.getCurrentAmount()) > 0.0001) {
        SKGObjectBase::SKGListSKGObjectBase subOps1;
        if (!err) err = getSubOperations(subOps1);

        SKGObjectBase::SKGListSKGObjectBase subOps2;
        if (!err) err = iDeletedOne.getSubOperations(subOps2);

        if (subOps2.count() == 1 && subOps1.count() == 1) {
            //Change the quantity of the sub operation
            SKGSubOperationObject so1(subOps1.at(0));
            SKGSubOperationObject so2(subOps2.at(0));
            if (!err) err = so1.setQuantity(so2.getQuantity());
            if (!err) err = so1.save();
        } else if (subOps2.count() >= 1 && subOps1.count() >= 1) {
            //Add a split
            SKGSubOperationObject so1;
            if (!err) err = addSubOperation(so1);
            if (!err) err = so1.setQuantity(iDeletedOne.getCurrentAmount() - getCurrentAmount());
            if (!err) err = so1.save();
        }
        if (!err) getDocument()->sendMessage(i18nc("An information message",  "Amount has been changed to be aligned with the imported operation"), SKGDocument::Positive);
    }

    //Delete useless operation
    if (!err) err = iDeletedOne.remove(false);
    return err;
}

SKGError SKGOperationObject::mergeSuboperations(const SKGOperationObject& iDeletedOne)
{
    SKGError err;
    SKGObjectBase::SKGListSKGObjectBase subops;
    err = iDeletedOne.getSubOperations(subops);
    int nb = subops.count();
    for (int i = 0; !err && i < nb; ++i) {
        SKGSubOperationObject subop(subops.at(i));
        err = subop.setParentOperation(*this);
        if (!err) err = subop.save();
    }
    if (!err) err = iDeletedOne.remove();
    return err;
}

#include "skgoperationobject.moc"

