/***************************************************************************
 *   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 SKGRecurrentOperationObject.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgrecurrentoperationobject.h"

#include <KLocale>

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

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

SKGRecurrentOperationObject::~SKGRecurrentOperationObject()
{
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

SKGError SKGRecurrentOperationObject::setTimeLimit(const QDate& iLastDate)
{
    // Get parameters
    QDate firstDate = this->getDate();
    SKGRecurrentOperationObject::PeriodUnit period = this->getPeriodUnit();
    int occu = this->getPeriodIncrement();

    // 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("i_nb_times", SKGServices::intToString(iTimeLimit));
}

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

SKGError SKGRecurrentOperationObject::setDate(const QDate& iDate)
{
    return setAttribute("d_date", SKGServices::dateToSqlString(QDateTime(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("d_date")).date();
}

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

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

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

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

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

SKGError SKGRecurrentOperationObject::process(int& oNbInserted, bool iForce, const 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("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("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, const QDate& iDate)
{
    SKGError err;
    oNbInserted = 0;

    // Get all operation with auto_write
    SKGListSKGObjectBase recuOps;
    if (iDocument) {
        err = iDocument->getObjects("v_recurrentoperation", "", 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;
}

#include "skgrecurrentoperationobject.moc"

