/***************************************************************************
 *   Copyright (C) 2020 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 <https://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @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::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.startOfDay()));
}

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

                    if (!op.isTemplate()) {
                        // Set old op as recurrent
                        IFOKDO(err, op.setAttribute(QStringLiteral("r_recurrentoperation_id"), SKGServices::intToString(getID())))
                        IFOKDO(err, op.save())

                        // Set new operation as reference
                        IFOKDO(err, setParentOperation(newOp))
                    } else {
                        // Set new op as recurrent
                        IFOKDO(err, newOp.setAttribute(QStringLiteral("r_recurrentoperation_id"), SKGServices::intToString(getID())))
                        IFOKDO(err, newOp.save())
                    }

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

                    // 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", "Operation '%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("Operation '%2' will be inserted in one day", "Operation '%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 operation 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;
}



