/***************************************************************************
 * SPDX-FileCopyrightText: 2022 S. MANKOWSKI stephane@mankowski.fr
 * SPDX-FileCopyrightText: 2022 G. DE BURE support@mankowski.fr
 * SPDX-License-Identifier: GPL-3.0-or-later
 ***************************************************************************/
/** @file
 * This file implements classes SKGRecurrentOperationObject.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgrecurrentoperationobject.h"

#include <klocalizedstring.h>

#include "skgdocumentbank.h"
#include "skgoperationobject.h"
#include "skgservices.h"
#include "skgsuboperationobject.h"
#include "skgtraces.h"

SKGRecurrentOperationObject::SKGRecurrentOperationObject(): SKGRecurrentOperationObject(nullptr)
{}

SKGRecurrentOperationObject::SKGRecurrentOperationObject(SKGDocument* iDocument, int iID): SKGObjectBase(iDocument, QStringLiteral("v_recurrentoperation"), iID)
{}

SKGRecurrentOperationObject::~SKGRecurrentOperationObject()
    = default;

SKGRecurrentOperationObject::SKGRecurrentOperationObject(const SKGRecurrentOperationObject& iObject) = default;

SKGRecurrentOperationObject::SKGRecurrentOperationObject(const SKGObjectBase& iObject)
{
    if (iObject.getRealTable() == QStringLiteral("recurrentoperation")) {
        copyFrom(iObject);
    } else {
        *this = SKGObjectBase(iObject.getDocument(), QStringLiteral("v_recurrentoperation"), iObject.getID());
    }
}

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

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

SKGError SKGRecurrentOperationObject::getParentOperation(SKGOperationObject& oOperation) const
{
    SKGObjectBase objTmp;
    SKGError err = getDocument()->getObject(QStringLiteral("v_operation"), "id=" % getAttribute(QStringLiteral("rd_operation_id")), objTmp);
    oOperation = objTmp;
    return err;
}

SKGError SKGRecurrentOperationObject::setParentOperation(const SKGOperationObject& iOperation)
{
    return setAttribute(QStringLiteral("rd_operation_id"), SKGServices::intToString(iOperation.getID()));
}

SKGError SKGRecurrentOperationObject::setTemplate(bool iTemplate)
{
    SKGError err;

    if (iTemplate == isTemplate()) {
        err.addError(ERR_UNEXPECTED, i18nc("Error message", "Trying to set the same template status \"%1\" to a recurrent operation", iTemplate));
        return err;
    }

    SKGOperationObject parentOp;
    err = getParentOperation(parentOp);
    if (iTemplate) {
        // Convert to template
        SKGOperationObject operationObjOrig = parentOp;
        IFOKDO(err, operationObjOrig.duplicate(parentOp, operationObjOrig.getDate(), true))

        IFOKDO(err, setParentOperation(parentOp))
        IFOKDO(err, save())

        IFOKDO(err, operationObjOrig.setAttribute(QStringLiteral("r_recurrentoperation_id"), SKGServices::intToString(getID())))
        IFOKDO(err, operationObjOrig.save())
    } else {
        // Convert to non-template
        SKGObjectBase::SKGListSKGObjectBase transactions;
        IFOKDO(err, getRecurredOperations(transactions))
        IFOK(err) {
            if (!transactions.isEmpty()) {
                SKGOperationObject lastObj(transactions.last());
                IFOKDO(err, setParentOperation(lastObj))
                IFOKDO(err, save())
                IFOKDO(err, lastObj.setAttribute(QStringLiteral("r_recurrentoperation_id"), QString()))
                IFOKDO(err, lastObj.save())

                SKGObjectBase::SKGListSKGObjectBase goupops;
                IFOKDO(err, parentOp.getGroupedOperations(goupops))
                IFOK(err) {
                    int nbgoupops = goupops.count();
                    for (int i = 0; !err && i < nbgoupops; ++i) {
                        SKGOperationObject groupop(goupops.at(i));
                        if (groupop != parentOp) {
                            IFOKDO(err, groupop.remove(true))
                        }
                    }
                }

                IFOKDO(err, parentOp.remove(true))
            } else {
                err.addError(ERR_FAIL, i18nc("Error message", "Need at least one transaction to convert a schedule to a non-template one"));
            }
        }
    }

    return err;
}

bool SKGRecurrentOperationObject::isTemplate() const
{
    SKGOperationObject op;
    SKGError err = getParentOperation(op);
    IFOK(err) return op.isTemplate();
    return false;
}

SKGError SKGRecurrentOperationObject::setPeriodIncrement(int iIncrement)
{
    return setAttribute(QStringLiteral("i_period_increment"), SKGServices::intToString(iIncrement));
}

int SKGRecurrentOperationObject::getPeriodIncrement() const
{
    return SKGServices::stringToInt(getAttribute(QStringLiteral("i_period_increment")));
}

SKGRecurrentOperationObject::PeriodUnit SKGRecurrentOperationObject::getPeriodUnit() const
{
    QString t_period_unit = getAttribute(QStringLiteral("t_period_unit"));
    if (t_period_unit == QStringLiteral("D")) {
        return SKGRecurrentOperationObject::DAY;
    }
    if (t_period_unit == QStringLiteral("W")) {
        return SKGRecurrentOperationObject::WEEK;
    }
    if (t_period_unit == QStringLiteral("M")) {
        return SKGRecurrentOperationObject::MONTH;
    }
    return SKGRecurrentOperationObject::YEAR;
}

SKGError SKGRecurrentOperationObject::setPeriodUnit(SKGRecurrentOperationObject::PeriodUnit iPeriod)
{
    return setAttribute(QStringLiteral("t_period_unit"), (iPeriod == SKGRecurrentOperationObject::DAY ? QStringLiteral("D") : (iPeriod == SKGRecurrentOperationObject::WEEK ? QStringLiteral("W") : (iPeriod == SKGRecurrentOperationObject::MONTH ? QStringLiteral("M") : QStringLiteral("Y")))));
}

SKGError SKGRecurrentOperationObject::setAutoWriteDays(int iDays)
{
    return setAttribute(QStringLiteral("i_auto_write_days"), SKGServices::intToString(iDays));
}

int SKGRecurrentOperationObject::getAutoWriteDays() const
{
    return SKGServices::stringToInt(getAttribute(QStringLiteral("i_auto_write_days")));
}

SKGError SKGRecurrentOperationObject::setWarnDays(int iDays)
{
    return setAttribute(QStringLiteral("i_warn_days"), SKGServices::intToString(iDays));
}

int SKGRecurrentOperationObject::getWarnDays() const
{
    return SKGServices::stringToInt(getAttribute(QStringLiteral("i_warn_days")));
}

bool SKGRecurrentOperationObject::hasTimeLimit() const
{
    return (getAttribute(QStringLiteral("t_times")) == QStringLiteral("Y"));
}

SKGError SKGRecurrentOperationObject::timeLimit(bool iTimeLimit)
{
    return setAttribute(QStringLiteral("t_times"), iTimeLimit ? QStringLiteral("Y") : QStringLiteral("N"));
}

SKGError SKGRecurrentOperationObject::setTimeLimit(QDate iLastDate)
{
    // Get parameters
    QDate firstDate = this->getDate();
    if (iLastDate < firstDate) {
        return setTimeLimit(0);
    }
    SKGRecurrentOperationObject::PeriodUnit period = this->getPeriodUnit();
    int occu = qMax(this->getPeriodIncrement(), 1);

    // Compute nb time
    int nbd = firstDate.daysTo(iLastDate);
    if (period == SKGRecurrentOperationObject::DAY) {
        nbd = nbd / occu;
    } else if (period == SKGRecurrentOperationObject::WEEK) {
        nbd = nbd / (7 * occu);
    } else if (period == SKGRecurrentOperationObject::MONTH) {
        nbd = (iLastDate.day() >= firstDate.day() ? 0 : -1) + (iLastDate.year() - firstDate.year()) * 12 + (iLastDate.month() - firstDate.month());
    } else if (period == SKGRecurrentOperationObject::YEAR) {
        nbd = nbd / (365 * occu);
    }

    if (nbd < -1) {
        nbd = -1;
    }
    return setTimeLimit(nbd + 1);
}

SKGError SKGRecurrentOperationObject::setTimeLimit(int iTimeLimit)
{
    return setAttribute(QStringLiteral("i_nb_times"), SKGServices::intToString(iTimeLimit));
}

int SKGRecurrentOperationObject::getTimeLimit() const
{
    return SKGServices::stringToInt(getAttribute(QStringLiteral("i_nb_times")));
}

SKGError SKGRecurrentOperationObject::setDate(QDate iDate)
{
    return setAttribute(QStringLiteral("d_date"), SKGServices::dateToSqlString(iDate));
}

QDate SKGRecurrentOperationObject::getNextDate() const
{
    QDate nextDate = getDate();
    SKGRecurrentOperationObject::PeriodUnit punit = getPeriodUnit();
    int p = getPeriodIncrement();
    if (punit == SKGRecurrentOperationObject::DAY) {
        nextDate = nextDate.addDays(p);
    } else if (punit == SKGRecurrentOperationObject::WEEK) {
        nextDate = nextDate.addDays(7 * p);
    } else if (punit == SKGRecurrentOperationObject::MONTH) {
        nextDate = nextDate.addMonths(p);
    } else if (punit == SKGRecurrentOperationObject::YEAR) {
        nextDate = nextDate.addYears(p);
    }
    return nextDate;
}

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

SKGError SKGRecurrentOperationObject::warnEnabled(bool iWarn)
{
    return setAttribute(QStringLiteral("t_warn"), iWarn ? QStringLiteral("Y") : QStringLiteral("N"));
}

bool SKGRecurrentOperationObject::isWarnEnabled() const
{
    return (getAttribute(QStringLiteral("t_warn")) == QStringLiteral("Y"));
}

SKGError SKGRecurrentOperationObject::autoWriteEnabled(bool iAutoWrite)
{
    return setAttribute(QStringLiteral("t_auto_write"), iAutoWrite ? QStringLiteral("Y") : QStringLiteral("N"));
}

bool SKGRecurrentOperationObject::isAutoWriteEnabled() const
{
    return (getAttribute(QStringLiteral("t_auto_write")) == QStringLiteral("Y"));
}

SKGError SKGRecurrentOperationObject::getRecurredOperations(SKGListSKGObjectBase& oOperations) const
{
    return getDocument()->getObjects(QStringLiteral("v_operation"), "r_recurrentoperation_id=" % SKGServices::intToString(getID()) % " ORDER BY d_date", oOperations);
}

SKGError SKGRecurrentOperationObject::process(int& oNbInserted, bool iForce, QDate iDate)
{
    SKGError err;
    SKGTRACEINFUNCRC(10, err)
    oNbInserted = 0;

    if (!hasTimeLimit() || getTimeLimit() > 0) {
        if (isAutoWriteEnabled() || iForce) {
            QDate nextDate = getDate();
            if (nextDate.isValid() && iDate >= nextDate.addDays(-getAutoWriteDays())) {
                SKGOperationObject op;
                err = getParentOperation(op);
                IFOK(err) {
                    // Create the duplicated operation
                    SKGOperationObject newOp;
                    err = op.duplicate(newOp, nextDate);
                    IFOKDO(err, newOp.setRecurrentOperation(getID()))
                    IFOKDO(err, load())

                    if (!err && hasTimeLimit()) {
                        err = setTimeLimit(getTimeLimit() - 1);
                    }
                    IFOKDO(err, save())

                    // Process again in case of multi insert needed
                    int nbi = 0;
                    IFOKDO(err, process(nbi, iForce, iDate))
                    oNbInserted = oNbInserted + 1 + nbi;

                    // Send message
                    IFOKDO(err, newOp.load())
                    IFOK(err) {
                        err = getDocument()->sendMessage(i18nc("An information message", "Transaction '%1' has been inserted", newOp.getDisplayName()), SKGDocument::Positive);
                    }
                }
            }
        }

        if (isWarnEnabled() && !err) {
            QDate nextDate = getDate();
            if (QDate::currentDate() >= nextDate.addDays(-getWarnDays())) {
                SKGOperationObject op;
                err = getParentOperation(op);
                IFOK(err) {
                    int nbdays = QDate::currentDate().daysTo(nextDate);
                    if (nbdays > 0) {
                        err = getDocument()->sendMessage(i18np("Transaction '%2' will be inserted in one day", "Transaction '%2' will be inserted in %1 days", nbdays, getDisplayName()));
                    }
                }
            }
        }
    }
    return err;
}

SKGError SKGRecurrentOperationObject::process(SKGDocumentBank* iDocument, int& oNbInserted, bool iForce, QDate iDate)
{
    SKGError err;
    oNbInserted = 0;

    // Get all transaction with auto_write
    SKGListSKGObjectBase recuOps;
    if (iDocument != nullptr) {
        err = iDocument->getObjects(QStringLiteral("v_recurrentoperation"), QLatin1String(""), recuOps);
    }

    int nb = recuOps.count();
    for (int i = 0; !err && i < nb; ++i) {
        SKGRecurrentOperationObject recu(recuOps.at(i));
        int nbi = 0;
        err = recu.process(nbi, iForce, iDate);
        oNbInserted += nbi;
    }

    return err;
}



