/* This file is part of the KDE project
 * Copyright (C) 2009, 2010, 2012 Dag Andersen <dag.andersen@kdemail.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

// clazy:excludeall=qstring-arg
#include "KPlatoRCPSScheduler.h"

#include "kptproject.h"
#include "kptschedule.h"
#include "kptresource.h"
#include "kpttask.h"
#include "kptrelation.h"
#include "kptdebug.h"

#include <librcps.h>

#include <QString>
#include <QTimer>
#include <QMutexLocker>
#include <QLocale>

#include <KLocalizedString>
#include <KFormat>

#include <iostream>

#define GENERATION_MIN_LIMIT 5000

#define PROGRESS_CALLBACK_FREQUENCY 100
#define PROGRESS_MAX_VALUE 120000
#define PROGRESS_INIT_VALUE 12000
#define PROGRESS_INIT_STEP 2000

/* low weight == late, high weight == early */
#define WEIGHT_ASAP         50
#define WEIGHT_ALAP         1
#define WEIGHT_CONSTRAINT   1000
#define WEIGHT_FINISH       1000

#define GROUP_TARGETTIME    1
#define GROUP_CONSTRAINT    2


class ProgressInfo
{
public:
    explicit ProgressInfo() : init(true), base(0), progress(0)
    {
        fitness.group = 0;
        fitness.weight = 0;
    }
    bool init;
    int base;
    int progress;
    struct rcps_fitness fitness;
};


KPlatoRCPSScheduler::KPlatoRCPSScheduler(Project *project, ScheduleManager *sm, ulong granularity, QObject *parent)
    : SchedulerThread(project, sm, parent),
    result(-1),
    m_schedule(0),
    m_recalculate(false),
    m_usePert(false),
    m_backward(false),
    m_problem(0),
    m_timeunit(granularity / 1000),
    m_offsetFromTime_t(0),
    m_progressinfo(new ProgressInfo())
{
    connect(this, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)), project, SIGNAL(sigCalculationStarted(KPlato::Project*,KPlato::ScheduleManager*)));
    emit sigCalculationStarted(project, sm);

    connect(this, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)), project, SIGNAL(sigCalculationFinished(KPlato::Project*,KPlato::ScheduleManager*)));
}

KPlatoRCPSScheduler::~KPlatoRCPSScheduler()
{
    delete m_progressinfo;
    qDeleteAll(m_duration_info_list);
    qDeleteAll(m_weight_info_list);
    rcps_problem_free(m_problem);
}

int KPlatoRCPSScheduler::progress_callback(int generations, struct rcps_fitness fitness, void *arg)
{
    if (arg == 0) {
        return -1;
    }
    KPlatoRCPSScheduler *self = static_cast<KPlatoRCPSScheduler*>(arg);
    //debugPlan<<"KPlatoRCPSScheduler::progress_callback"<<generations<<fitness<<arg;
    return self->progress(generations, fitness);
}

int KPlatoRCPSScheduler::progress(int generations, struct rcps_fitness fitness)
{
    if (m_haltScheduling) {
        debugPlan<<"KPlatoRCPSScheduler::progress:"<<"halt";
        return -1;
    }
    if (m_stopScheduling) {
        m_schedule->logWarning(i18n("Scheduling halted after %1 generations", generations), 1);
        debugPlan<<"KPlatoRCPSScheduler::progress:"<<"stop";
        return -1;
    }
//     std::cout << "Progress after: " << generations << " generations\n";
    if (m_progressinfo->init) {
        if (generations == 0) {
            m_progressinfo->progress += PROGRESS_INIT_STEP;
        } else {
            m_progressinfo->progress = PROGRESS_INIT_VALUE;
            m_progressinfo->init = false;
//             std::cout << "Population generated: "<< generations << "\n";
        }
    } else {
        m_progressinfo->progress = PROGRESS_INIT_VALUE + generations;
    }
    // detect change in fitness
    if (rcps_fitness_cmp(&m_progressinfo->fitness, &fitness) != 0) {
//         std::cout << "Fitness changed in generation: " << generations << " group=["<<m_progressinfo->fitness.group<<"->"<<fitness.group<<"]"<<" weight=["<<m_progressinfo->fitness.weight<<"->"<<fitness.weight<<"]\n";
        m_progressinfo->fitness = fitness;
        m_progressinfo->base = generations;
    }
    m_manager->setProgress(m_progressinfo->progress);
    setProgress(m_progressinfo->progress);
    // stop if fitness does not change in GENERATION_MIN_LIMIT generations
/*    int result = (generations >= m_progressinfo->base + GENERATION_MIN_LIMIT ? 1 : 0);
    if (result) {
        //debugPlan<<"KPlatoRCPSScheduler::progress, stop after"<<generations<<"generations, progress:"<<m_progressinfo->progress;
        m_schedule->logDebug(QString("Acceptable solution found after %1 generations").arg(generations), 1);
        std::cout << "Acceptable solution found after " << generations << " generations\n";
    }*/
    return 0;
}

int KPlatoRCPSScheduler::duration_callback(int direction, int time, int nominal_duration, void *arg)
{
    //debugPlan<<"plan_duration:"<<direction<<time<<nominal_duration<<arg;
    if (arg == 0) {
        return nominal_duration;
    }
    KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>(arg);
    return info->self->duration(direction, time, nominal_duration, info);
}

int KPlatoRCPSScheduler::duration(int direction, int time, int nominal_duration,  KPlatoRCPSScheduler::duration_info  *info)
{
    if (m_haltScheduling || m_manager == 0) {
        return nominal_duration;
    }
    ++(info->calls);
    if (info->cache.contains(QPair<int, int>(time, direction))) {
        return info->cache[ QPair<int, int>(time, direction) ];
    }
    if (m_manager->recalculate() && info->task->completion().isFinished()) {
        return 0;
    }
    int dur = 0;
    if (info->task->constraint() == Node::FixedInterval) {
        // duration may depend on daylight saving so we need to calculate
        // NOTE: dur may not be correct if time != info->task->constraintStartTime, let's see what happens...
        dur = (info->task->constraintEndTime() - info->task->constraintStartTime()).seconds() / m_timeunit;
        info->task->schedule()->logDebug(QString("Fixed interval: Time=%1, duration=%2 (%3, %4)").arg(time).arg(dur).arg(fromRcpsTime(time).toString()).arg(Duration((qint64)(dur) * m_timeunit * 1000).toDouble(Duration::Unit_h)));
    } else if (info->estimatetype == Estimate::Type_Effort) {
        if (info->requests.isEmpty()) {
            dur = info->estimate.seconds() / m_timeunit;
        } else {
            dur = info->task->requests().duration(
                        info->requests,
                        fromRcpsTime(time),
                        info->estimate,
                        0, /*no schedule*/
                        m_backward ? ! direction : direction
                    ).seconds() / m_timeunit;
            //debugPlan<<info->task->name()<< QString("duration_callback effort: backward=%5, direction=%6 (direction=%7); Time=%1, duration=%2 (%3, %4)").arg(time).arg(dur).arg(fromRcpsTime(time).toString()).arg(Duration((qint64)(dur) * m_timeunit * 1000).toDouble(Duration::Unit_h)).arg(m_backward).arg(direction).arg(m_backward ? !direction : direction);
        }
    } else {
        dur = info->task->length(
                    fromRcpsTime(time),
                    info->estimate,
                    0, /*no schedule*/
                    m_backward ? ! direction : direction
                ).seconds() / m_timeunit;
    }
    info->cache[ QPair<int, int>(time, direction) ] = dur;
    info->task->schedule()->logDebug(QString("duration_callback: Time=%1, duration=%2 (%3, %4)").arg(time).arg(dur).arg(fromRcpsTime(time).toString()).arg(Duration((qint64)(dur) * m_timeunit * 1000).toDouble(Duration::Unit_h)));
    return dur;
}

int KPlatoRCPSScheduler::weight_callback(int time, int duration, struct rcps_fitness *nominal_weight, void* weight_arg, void* fitness_arg)
{
    //debugPlan<<"plan_weight:"<<time<<nominal_weight<<arg;
    if (weight_arg == 0) {
        nominal_weight->weight *= time;
        return 0;
    }
    KPlatoRCPSScheduler::weight_info *winfo = static_cast<KPlatoRCPSScheduler::weight_info*>(weight_arg);
    KPlatoRCPSScheduler::fitness_info *finfo = static_cast<KPlatoRCPSScheduler::fitness_info*>(fitness_arg);
    return winfo->self->weight(time, duration, nominal_weight, winfo, finfo);
}

void *KPlatoRCPSScheduler::fitness_callback_init(void *arg)
{
    Q_ASSERT(arg);
    KPlatoRCPSScheduler::fitness_info *info = static_cast<KPlatoRCPSScheduler::fitness_info*>(arg);
    Q_ASSERT(info);
    fitness_info *finfo = new fitness_info;
    finfo->self = info->self;
//     debugPlan<<info->self;
    return finfo;
}

int KPlatoRCPSScheduler::fitness_callback_result(struct rcps_fitness *fit, void *arg)
{
    KPlatoRCPSScheduler::fitness_info *info = static_cast<KPlatoRCPSScheduler::fitness_info*>(arg);
    info->self->fitness(fit, info);
    delete info;
    return 0;
}

int KPlatoRCPSScheduler::fitness(struct rcps_fitness *fit, KPlatoRCPSScheduler::fitness_info *info)
{
/*    std::cout << ">-------------------------------------------\n";
    std::cout << "Sequence: ";
    foreach (Task *t, info->jobs) { std::cout << (t ? t->name().toLocal8Bit().data() : "End") << ", "; }
    std::cout << "\n";
    debugPlan<<info->map;*/
    QMultiMap<int, QPair<int, Task*> >::const_iterator it = info->map.constFind(GROUP_CONSTRAINT);
    if (it != info->map.constEnd()) {
        // constraint
        fit->group = GROUP_CONSTRAINT;
        for (; it.key() == GROUP_CONSTRAINT && it != info->map.constEnd(); ++it) {
            fit->weight += it.value().first;
            QString s = it.value().second ? it.value().second->name() : "End node";
//             std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
//             m_schedule->logDebug(QString("%3: %1 %2").arg(it.key()).arg(it.value().first).arg(it.value().second->name()));
        }
//         std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
        return 0;
    }
    it = info->map.constFind(GROUP_TARGETTIME);
    if (it != info->map.constEnd()) {
        // missed target time
        fit->group = GROUP_TARGETTIME;
        for (; it.key() == GROUP_TARGETTIME && it != info->map.constEnd(); ++it) {
            fit->weight += it.value().first;
            QString s = it.value().second ? it.value().second->name() : "End node";
//             std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
//             m_schedule->logDebug(QString("%3: %1 %2").arg(it.key()).arg(it.value().first).arg(it.value().second->name()));
        }
//         std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
        return 0;
    }
    fit->group = 0;
    for (it = info->map.constBegin(); it != info->map.constEnd(); ++it) {
        fit->weight += it.value().first;
        QString s = it.value().second ? it.value().second->name() : "End node";
//         std::cout << s.toLocal8Bit().data() << ": group=" << it.key() << " weight=" << it.value().first << "\n";
//        m_schedule->logDebug(QString("%3: %1 %2").arg(it.key()).arg(it.value().first).arg(it.value().second->name()));
    }
//     std::cout << "Result: group= " << fit->group << " weight=" << fit->weight << "\n--------------------------\n";
    return 0;
}

int KPlatoRCPSScheduler::weight(int time, int duration, struct rcps_fitness *nominal_weight, KPlatoRCPSScheduler::weight_info* info, KPlatoRCPSScheduler::fitness_info* finfo)
{
    if (m_haltScheduling || m_manager == 0) {
        return 0;
    }
    if (m_manager->recalculate() && info->task->completion().isFinished()) {
        return 0;
    }
    struct rcps_fitness &f = *nominal_weight;
    f.group = 0;
    f.weight = time;
    if (info->isEndJob) {
        if (info->finish == 0) {
            info->finish = time;
/*            const char *s = QString("First  : %1 %2 %3 End job").arg(time, 10).arg(duration, 10).arg(w, 10).toLatin1();
            std::cout<<s<<"\n";*/
        }
/*        w = WEIGHT_FINISH * info->finish / (time > 0 ? time : 1);
        if (time > info->targettime) {
            w = w + (WEIGHT_CONSTRAINT * (time - info->targettime));
        }*/
        if (time > info->targettime) {
            f.group = GROUP_TARGETTIME;
            f.weight = time - info->targettime;
        }

/*        const char *s = QString("End job: %1 %2 %3 End job target: %4").arg(time, 10).arg(duration, 10).arg(w, 10).arg(info->targettime).toLatin1();
        std::cout<<s<<"\n";*/
    } else {
        if (m_backward) {
            switch (info->task->constraint()) {
                case Node::FinishNotLater:
                    if (info->targettime > time) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * (info->targettime - time);
                    }
                    break;
                case Node::MustFinishOn:
                    if (info->targettime != time) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * abs(info->targettime - time);
                    }
                    break;
                case Node::StartNotEarlier:
                    if (info->targettime < time) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * (time - info->targettime);
                    }
                    break;
                case Node::MustStartOn:
                case Node::FixedInterval:
                    if (info->targettime != time) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * abs(info->targettime - time);
                    }
                    break;
                default:
                    break;
            }
/*            const char *s = QString("Backward: %1 %2 %3 %4 (target: %5)").arg(time, 10).arg(duration, 10).arg(w, 10).arg(info->task->name()).arg(info->targettime).toLatin1();
            std::cout<<s<<"\n";*/
        } else {
            switch (info->task->constraint()) {
                case Node::StartNotEarlier:
                    if (time < info->targettime) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * (info->targettime - time);
                    }
                    break;
                case Node::MustStartOn:
                case Node::FixedInterval:
                    if (info->targettime != time) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * (abs(info->targettime - time));
                    }
                    break;
                case Node::FinishNotLater:
//                     std::cout << "FNL " << info->task->name().toLocal8Bit().data() << ": end="<<time+duration<<" target="<<info->targettime<<"\n";
                    if (time + duration > info->targettime) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * (time - info->targettime);
                    }
//                     std::cout << info->task->name().toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";
                    break;
                case Node::MustFinishOn:
//                     std::cout << "MSO " << info->task->name().toLocal8Bit().data() << ": end="<<time+duration<<" target="<<info->targettime<<"\n";
                    if (info->targettime != time + duration) {
                        f.group = GROUP_CONSTRAINT;
                        f.weight = WEIGHT_CONSTRAINT * abs(info->targettime - time);
                    }
//                     std::cout << info->task->name().toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";
                    break;
                default:
                    break;
            }
/*            const char *s = QString("Forward: %1 %2 %3 %4 (target: %5)").arg(time, 10).arg(duration, 10).arg(w, 10).arg(info->task->name()).arg(info->targettime).toLatin1();
            std::cout<<s<<"\n";*/
        }
    }
//     QString s = info->task ? info->task->name() : "End node";
    if (finfo) {
        finfo->map.insert(f.group, QPair<int, Task*>(f.weight, info->task));
        finfo->jobs << info->task;
//         debugPlan<<s<<":"<<finfo->map;
    }// else debugPlan<<s<<":"<<"No finfo!";
/*    std::cout << "Weight: " << s.toLocal8Bit().data() << ": group=" << f.group << " weight=" << f.weight << "\n";*/
    return 0;
}

void KPlatoRCPSScheduler::run()
{
    if (m_haltScheduling) {
        deleteLater();
        return;
    }
    if (m_stopScheduling) {
        return;
    }
    { // mutex -->
        m_projectMutex.lock();
        m_managerMutex.lock();

        m_project = new Project();
        loadProject(m_project, m_pdoc);
        m_project->setName("Schedule: " + m_project->name()); //Debug
        m_project->stopcalculation = false;
        m_manager = m_project->scheduleManager(m_mainmanagerId);
        Q_CHECK_PTR(m_manager);
        Q_ASSERT(m_manager->expected());
        Q_ASSERT(m_manager != m_mainmanager);
        Q_ASSERT(m_manager->scheduleId() == m_mainmanager->scheduleId());
        Q_ASSERT(m_manager->expected() != m_mainmanager->expected());
        m_manager->setName("Schedule: " + m_manager->name()); //Debug
        m_schedule = m_manager->expected();

        connect(m_manager, SIGNAL(sigLogAdded(Schedule::Log)), this, SLOT(slotAddLog(Schedule::Log)));

        m_project->initiateCalculation(*m_schedule);
        m_project->initiateCalculationLists(*m_schedule);

        m_problem = rcps_problem_new();
        rcps_problem_setfitness_mode(m_problem, FITNESS_WEIGHT);

        m_usePert = m_manager->usePert();
        m_recalculate = m_manager->recalculate();
        if (m_recalculate) {
            m_starttime =  m_manager->recalculateFrom();
            m_backward = false;
        } else {
            m_backward = m_manager->schedulingDirection();
            m_starttime = m_backward ? m_project->constraintEndTime() : m_project->constraintStartTime();
        }
        m_targettime = m_backward ? m_project->constraintStartTime() : m_project->constraintEndTime();

        m_project->setCurrentSchedule(m_manager->expected()->id());

        m_schedule->setPhaseName(0, i18n("Init"));
        QLocale locale;
        if (! m_backward) {
            m_schedule->logDebug(QString("Schedule project using RCPS Scheduler, starting at %1, granularity %2 sec").arg(locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat)).arg(m_timeunit), 0);
            if (m_recalculate) {
                m_schedule->logInfo(i18n("Re-calculate project from start time: %1", locale.toString(m_starttime, QLocale::ShortFormat)), 0);
            } else {
                m_schedule->logInfo(i18n("Schedule project from start time: %1", locale.toString(m_starttime, QLocale::ShortFormat)), 0);
            }
        } else {
            m_schedule->logDebug(QString("Schedule project backward using RCPS Scheduler, starting at %1, granularity %2 sec").arg(locale.toString(QDateTime::currentDateTime(), QLocale::ShortFormat)).arg(m_timeunit), 0);
            m_schedule->logInfo(i18n("Schedule project from end time: %1", locale.toString(m_starttime, QLocale::ShortFormat)), 0);
        }

        m_managerMutex.unlock();
        m_projectMutex.unlock();
    } // <--- mutex
    m_progressinfo->progress += PROGRESS_INIT_STEP / 5;
    setProgress(m_progressinfo->progress);
 
    result = kplatoToRCPS();
    if (result != 0) {
        m_schedule->logError(i18n("Failed to build a valid RCPS project"));
        setProgress(PROGRESS_MAX_VALUE);
        return;
    }
    m_schedule->setPhaseName(1, i18n("Schedule"));

    setMaxProgress(PROGRESS_MAX_VALUE);
    solve();
    if (m_haltScheduling) {
        deleteLater();
        return;
    }
    if (result != 0) {
        m_schedule->logError(i18n("Invalid scheduling solution. Result: %1", result), 1);
    }
    kplatoFromRCPS();
    setProgress(PROGRESS_MAX_VALUE);
}

int KPlatoRCPSScheduler::check()
{
    return rcps_check(m_problem);
}

void KPlatoRCPSScheduler::solve()
{
    debugPlan<<"KPlatoRCPSScheduler::solve()";
    struct rcps_solver *s = rcps_solver_new();
    rcps_solver_set_progress_callback(s, PROGRESS_CALLBACK_FREQUENCY, this, &KPlatoRCPSScheduler::progress_callback);
    rcps_solver_set_duration_callback(s, &KPlatoRCPSScheduler::duration_callback);

    rcps_problem_set_weight_callback(m_problem, &KPlatoRCPSScheduler::weight_callback);
    fitness_init_arg.self = this;
    rcps_problem_set_fitness_callback(m_problem, &KPlatoRCPSScheduler::fitness_callback_init, &fitness_init_arg, &KPlatoRCPSScheduler::fitness_callback_result);

    Q_ASSERT(check() == 0);

    rcps_solver_setparam(s, SOLVER_PARAM_POPSIZE, 1000);

    rcps_solver_solve(s, m_problem);
    result = rcps_solver_getwarnings(s);
    rcps_solver_free(s);
}

int KPlatoRCPSScheduler::kplatoToRCPS()
{
    addResources();
    addTasks();
    addDependencies();
    addRequests();
    setWeights();
    setConstraints();

    int r = check();
    return r;
}

void KPlatoRCPSScheduler::taskFromRCPSForward(struct rcps_job *job, Task *task, QMap<Node*, QList<ResourceRequest*> > &resourcemap)
{
    if (m_haltScheduling || m_manager == 0) {
        return;
    }
    QLocale locale;
    Schedule *cs = task->currentSchedule();
    Q_ASSERT(cs);
    struct rcps_mode *mode = rcps_mode_get(job, rcps_job_getmode_res(job));
    /* get the duration of this mode */
    KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>(rcps_mode_get_cbarg(mode));
    qint64 dur = 0;
    qint64 st = rcps_job_getstart_res(job);
    if (info == 0) {
        dur = rcps_mode_getduration(mode);
    } else {
        cs->logDebug(QString("Task '%1' estimate: %2").arg(task->name()).arg(task->estimate()->value(Estimate::Use_Expected, false).toString()), 1);
        cs->logDebug(QString("Task '%1' duration called %2 times, cached values: %3").arg(rcps_job_getname(job)).arg(info->calls).arg(info->cache.count()));

        dur = duration_callback(0, st, rcps_mode_getduration(mode), info);

        for (QMap<QPair<int, int>, int>::ConstIterator it = info->cache.constBegin(); it != info->cache.constEnd(); ++it) {
            cs->logDebug(QString("Task '%1' start: %2, duration: %3 (%4, %5 hours)").arg(rcps_job_getname(job)).arg(it.key().first).arg(it.value()).arg(fromRcpsTime(it.key().first).toString()).arg((double)(it.value())/60.0), 1);
        }
    }
    DateTime start = m_starttime.addSecs(st * m_timeunit);
    DateTime end = start + Duration(dur * m_timeunit * 1000);
    cs->logDebug(QString("Task '%1' start=%2, duration=%3: %4 - %5").arg(rcps_job_getname(job)).arg(st).arg(dur).arg(locale.toString(start, QLocale::ShortFormat)).arg(locale.toString(end, QLocale::ShortFormat)), 1);
    task->setStartTime(start);
    task->setEndTime(end);
    for (int reqs = 0; reqs < rcps_request_count(mode); ++reqs) {
        struct rcps_request *req = rcps_request_get(mode, reqs);
        struct rcps_alternative *alt = rcps_alternative_get(req, rcps_request_getalternative_res(req));
        int amount = rcps_alternative_getamount(alt);
        struct rcps_resource *res = rcps_alternative_getresource(alt);

        cs->logDebug(QString("Job %1: resource %2 is %3 available").arg(rcps_job_getname(job)).arg(rcps_resource_getname(res)).arg(amount), 1);
        
        // do actual appointments etc
        ResourceRequest *r = m_requestmap.value(req);
        if (r == 0) {
            cs->logWarning(i18n("No resource request is registered"), 1);
            continue;
        }
        resourcemap[ task ] << r;
        cs->logDebug(QString("Make appointments to resource %1").arg(r->resource()->name()), 1);
        r->makeAppointment(cs, amount);
    }
    if (m_recalculate) {
        if (task->completion().isFinished()) {
            task->copySchedule();
            if (m_manager) {
                cs->logDebug(QString("Task is completed, copied schedule: %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
            }
        } else if (task->completion().isStarted()) {
            task->copyAppointments(DateTime(), start);
            if (m_manager) {
                cs->logDebug(QString("Task is %4% completed, copied appointments from %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(start, QLocale::ShortFormat)).arg(task->completion().percentFinished()), 1);
            }
        }
    }
    cs->setScheduled(true);
    if (task->estimate()->type() == Estimate::Type_Effort) {
        if (task->appointmentStartTime().isValid()) {
            task->setStartTime(task->appointmentStartTime());
        }
        if (task->appointmentEndTime().isValid()) {
            task->setEndTime(task->appointmentEndTime());
        }
        if (info && info->requests.isEmpty()) {
            cs->setResourceError(true);
            cs->logError(i18n("No resource has been allocated"), 1);
        }
   } else if (task->estimate()->calendar()) {
        DateTime t = task->estimate()->calendar()->firstAvailableAfter(task->startTime(), task->endTime());
        if (t.isValid()) {
            task->setStartTime(t);
        }
        t = task->estimate()->calendar()->firstAvailableBefore(task->endTime(), task->startTime());
        if (t.isValid()) {
            task->setEndTime(t);
        }
    } //else  Fixed duration
    task->setDuration(task->endTime() - task->startTime());
    cs->logInfo(i18n("Scheduled task to start at %1 and finish at %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
}

void KPlatoRCPSScheduler::kplatoFromRCPS()
{
    if (m_backward) {
        kplatoFromRCPSBackward();
    } else {
        kplatoFromRCPSForward();
    }
}

void KPlatoRCPSScheduler::kplatoFromRCPSForward()
{
    //debugPlan<<"KPlatoRCPSScheduler::kplatoFromRCPSForward:";
    MainSchedule *cs = static_cast<MainSchedule*>(m_project->currentSchedule());
    QMap<Node*, QList<ResourceRequest*> > resourcemap;
    int count = rcps_job_count(m_problem);
    int step = (PROGRESS_MAX_VALUE - m_progressinfo->progress) / count;
    DateTime projectstart = m_starttime.addSecs(rcps_job_getstart_res(m_jobstart) * m_timeunit);
    for (int i = 0; i < count; ++i) {
        m_progressinfo->progress += step;
        m_manager->setProgress(m_progressinfo->progress);
        setProgress(m_progressinfo->progress);

        struct rcps_job *job = rcps_job_get(m_problem, i);
        Task *task = m_taskmap.value(job);
        if (task == 0) {
            continue; //might be dummy task for lag, ...
        }
        taskFromRCPSForward(job, task, resourcemap);

        if (projectstart > task->startTime()) {
            projectstart = task->startTime();
        }
    }
    qint64 st = rcps_job_getstart_res(m_jobstart) * m_timeunit;
    DateTime start = m_starttime.addSecs(st);
    qint64 et = rcps_job_getstart_res(m_jobend) * m_timeunit;
    DateTime end = m_starttime.addSecs(et);
    m_project->setStartTime(projectstart);
    m_project->setEndTime(end);

    adjustSummaryTasks(m_schedule->summaryTasks());
    
    calculatePertValues(resourcemap);

    QLocale locale;
    cs->logInfo(i18n("Project scheduled to start at %1 and finish at %2", locale.toString(projectstart, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat)), 1);

    if (m_manager) {
        cs->logDebug(QString("Project scheduling finished at %1").arg(QDateTime::currentDateTime().toString()), 1);
        m_project->finishCalculation(*m_manager);
        m_manager->scheduleChanged(cs);
    }
}

void KPlatoRCPSScheduler::taskFromRCPSBackward(struct rcps_job *job, Task *task, QMap<Node*, QList<ResourceRequest*> > &resourcemap)
{
    if (m_haltScheduling || m_manager == 0) {
        return;
    }
    QLocale locale;
    Schedule *cs = task->currentSchedule();
    Q_ASSERT(cs);
    struct rcps_mode *mode = rcps_mode_get(job, rcps_job_getmode_res(job));
    /* get the duration of this mode */
    KPlatoRCPSScheduler::duration_info *info = static_cast<KPlatoRCPSScheduler::duration_info*>(rcps_mode_get_cbarg(mode));
    qint64 dur = 0;
    qint64 st = rcps_job_getstart_res(job);
    if (info == 0) {
        dur = rcps_mode_getduration(mode);
    } else {
        cs->logDebug(QString("Task '%1' estimate: %2").arg(task->name()).arg(task->estimate()->value(Estimate::Use_Expected, false).toString()), 1);
        cs->logDebug(QString("Task '%1' duration called %2 times, cached values: %3").arg(rcps_job_getname(job)).arg(info->calls).arg(info->cache.count()));

        dur = duration_callback(0, st, rcps_mode_getduration(mode), info);

        for (QMap<QPair<int, int>, int>::ConstIterator it = info->cache.constBegin(); it != info->cache.constEnd(); ++it) {
            cs->logDebug(QString("Task '%1' start: %2, duration: %3 (%4, %5 hours)").arg(rcps_job_getname(job)).arg(it.key().first).arg(it.value()).arg(fromRcpsTime(it.key().first).toString()).arg((double)(it.value())/60.0), 1);
        }
    }
    DateTime end = fromRcpsTime(st);
    DateTime start = fromRcpsTime(st + dur);
    cs->logDebug(QString("Task '%1' start=%2, duration=%3: %4 - %5").arg(rcps_job_getname(job)).arg(st).arg(dur).arg(locale.toString(start, QLocale::ShortFormat)).arg(locale.toString(end, QLocale::ShortFormat)), 1);
    task->setStartTime(start);
    task->setEndTime(end);
    for (int reqs = 0; reqs < rcps_request_count(mode); ++reqs) {
        struct rcps_request *req = rcps_request_get(mode, reqs);
        struct rcps_alternative *alt = rcps_alternative_get(req, rcps_request_getalternative_res(req));
        int amount = rcps_alternative_getamount(alt);
        struct rcps_resource *res = rcps_alternative_getresource(alt);

        cs->logDebug(QString("Job %1: resource %2 is %3 available").arg(rcps_job_getname(job)).arg(rcps_resource_getname(res)).arg(amount), 1);

        // do actual appointments etc
        ResourceRequest *r = m_requestmap.value(req);
        if (r == 0) {
            cs->logWarning(i18n("No resource request is registered"), 1);
            continue;
        }
        resourcemap[ task ] << r;
        cs->logDebug(QString("Make appointments to resource %1").arg(r->resource()->name()), 1);
        r->makeAppointment(cs, amount);
    }
    if (m_recalculate) {
        if (task->completion().isFinished()) {
            task->copySchedule();
            if (m_manager) {
                cs->logDebug(QString("Task is completed, copied schedule: %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
            }
        } else if (task->completion().isStarted()) {
            task->copyAppointments(DateTime(), start);
            if (m_manager) {
                cs->logDebug(QString("Task is %4% completed, copied appointments from %2 to %3").arg(task->name()).arg(locale.toString(task->startTime(), QLocale::ShortFormat)).arg(locale.toString(start, QLocale::ShortFormat)).arg(task->completion().percentFinished()), 1);
            }
        }
    }
    cs->setScheduled(true);
    if (task->estimate()->type() == Estimate::Type_Effort) {
        if (task->appointmentStartTime().isValid()) {
            task->setStartTime(task->appointmentStartTime());
        }
        if (task->appointmentEndTime().isValid()) {
            task->setEndTime(task->appointmentEndTime());
        }
        if (info && info->requests.isEmpty()) {
            cs->setResourceError(true);
            cs->logError(i18n("No resource has been allocated"), 1);
        }
    } else if (task->estimate()->calendar()) {
        DateTime t = task->estimate()->calendar()->firstAvailableAfter(task->startTime(), task->endTime());
        if (t.isValid()) {
            task->setStartTime(t);
        }
        t = task->estimate()->calendar()->firstAvailableBefore(task->endTime(), task->startTime());
        if (t.isValid()) {
            task->setEndTime(t);
        }
    } //else  Fixed duration
    task->setDuration(task->endTime() - task->startTime());
    cs->logInfo(i18n("Scheduled task to start at %1 and finish at %2", locale.toString(task->startTime(), QLocale::ShortFormat), locale.toString(task->endTime(), QLocale::ShortFormat)), 1);
}


void KPlatoRCPSScheduler::kplatoFromRCPSBackward()
{
    QLocale locale;
    //debugPlan<<"KPlatoRCPSScheduler::kplatoFromRCPSBackward:";
    MainSchedule *cs = static_cast<MainSchedule*>(m_project->currentSchedule());
    QMap<Node*, QList<ResourceRequest*> > resourcemap;
    int count = rcps_job_count(m_problem);
    int step = (PROGRESS_MAX_VALUE - m_progressinfo->progress) / count;
    DateTime projectstart = fromRcpsTime(rcps_job_getstart_res(m_jobend));
    for (int i = 0; i < count; ++i) {
        m_progressinfo->progress += step;
        m_manager->setProgress(m_progressinfo->progress);
        setProgress(m_progressinfo->progress);

        struct rcps_job *job = rcps_job_get(m_problem, i);
        Task *task = m_taskmap.value(job);
        if (task == 0) {
            continue; //might be dummy task for lag, ...
        }
        taskFromRCPSBackward(job, task, resourcemap);

        if (projectstart > task->startTime()) {
            projectstart = task->startTime();
        }
    }
    DateTime end = fromRcpsTime(rcps_job_getstart_res(m_jobstart));
    m_project->setStartTime(projectstart);
    m_project->setEndTime(end);
    cs->logInfo(i18n("Project scheduled to start at %1 and finish at %2", locale.toString(projectstart, QLocale::ShortFormat), locale.toString(end, QLocale::ShortFormat)), 1);
    if (projectstart < m_project->constraintStartTime()) {
        cs->setConstraintError(true);
        cs->logError(i18n("Must start project early in order to finish in time: %1", locale.toString(m_project->constraintStartTime(), QLocale::ShortFormat)), 1);
    }
    adjustSummaryTasks(m_schedule->summaryTasks());

    calculatePertValues(resourcemap);

    if (m_manager) {
        cs->logDebug(QString("Project scheduling finished at %1").arg(QDateTime::currentDateTime().toString()), 1);
        m_project->finishCalculation(*m_manager);
        m_manager->scheduleChanged(cs);
    }
}

void KPlatoRCPSScheduler::adjustSummaryTasks(const QList<Node*> &nodes)
{
    foreach (Node *n, nodes) {
        adjustSummaryTasks(n->childNodeIterator());
        if (n->parentNode()->type() == Node::Type_Summarytask) {
            DateTime pt = n->parentNode()->startTime();
            DateTime nt = n->startTime();
            if (! pt.isValid() || pt > nt) {
                n->parentNode()->setStartTime(nt);
            }
            pt = n->parentNode()->endTime();
            nt = n->endTime();
            if (! pt.isValid() || pt < nt) {
                n->parentNode()->setEndTime(nt);
            }
        }
    }
}

void KPlatoRCPSScheduler::calculatePertValues(const QMap<Node*, QList<ResourceRequest*> > &map)
{
    if (m_manager) {
        m_schedule->setPhaseName(2, i18nc("Project Evaluation and Review Technique", "PERT"));
    }
    foreach (Node *n, m_project->allNodes()) {
        if (n->type() != Node::Type_Task && n->type() != Node::Type_Milestone) {
            continue;
        }
        Task *t = static_cast<Task*>(n);
        if (n->isStartNode()) {
            (void)calculateLateStuff(map, static_cast<Task*>(n));
        }
        if (n->isEndNode()) {
            (void)calculateEarlyStuff(map,  static_cast<Task*>(n));
        }
        switch (n->constraint()) {
            case Node::StartNotEarlier:
                n->schedule()->setNegativeFloat(n->startTime() < n->constraintStartTime()
                            ? n->startTime() - n->constraintStartTime()
                            :  Duration::zeroDuration);
                break;
            case Node::MustStartOn:
            case Node::FixedInterval:
                n->schedule()->setNegativeFloat(n->startTime() > n->constraintStartTime()
                            ? n->startTime() - n->constraintStartTime()
                            :  n->constraintStartTime() - n->startTime());
                break;
            case Node::FinishNotLater:
                n->schedule()->setNegativeFloat(n->endTime() > n->constraintEndTime()
                                ? n->endTime() - n->constraintEndTime()
                                : Duration::zeroDuration);
                break;
            case Node::MustFinishOn:
                n->schedule()->setNegativeFloat(n->endTime() > n->constraintEndTime()
                                ? n->endTime() - n->constraintEndTime()
                                : n->constraintEndTime() - n->endTime());
                break;
            default:
                break;
        }
        if (t->negativeFloat() != 0) {
            n->schedule()->setConstraintError(true);
            n->schedule()->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", n->constraintToString(true), KFormat().formatDuration(t->negativeFloat().milliseconds())));
        }

    }
}

Duration KPlatoRCPSScheduler::calculateLateStuff(const QMap<Node*, QList<ResourceRequest*> > &map, Task *task)
{
    Schedule *cs = task->currentSchedule();
    Duration pf = m_project->endTime() - m_project->startTime();
    QList<Relation*> lst = task->dependChildNodes() + task->childProxyRelations();
    if (lst.isEmpty()) {
        // end node
        DateTime end = task->endTime();
        if (task->estimate()->type() == Estimate::Type_Effort) {
            foreach (ResourceRequest *r, map.value(static_cast<Node*>(task))) {
                DateTime x = r->resource()->availableBefore(m_project->endTime(), task->endTime(), 0);
                cs->logDebug(QString("Resource '%1' available before %2: %3").arg(r->resource()->name()).arg(m_project->endTime().toString()).arg(x.toString()), 2);
                if (x.isValid() && x > end) {
                    end = x;
                }
            }
        } else if (task->estimate()->calendar()) {
            end = task->estimate()->calendar()->firstAvailableBefore(m_project->endTime(), task->endTime());
            cs->logDebug(QString("Calendar work time before %1: %2").arg(m_project->endTime().toString()).arg(end.toString()), 2);
        }
        // TODO must calculate backwards to get late *start* of task
        pf = end.isValid() ? end - task->endTime() : m_project->endTime() - task->endTime();
        task->setFreeFloat(pf);
    } else {
        Duration prev = pf;
        Duration x = pf;
        foreach (Relation *r, lst) {
            prev = qMin(prev, calculateLateStuff(map, static_cast<Task*>(r->child())));
            DateTime end = task->endTime();
            if (task->estimate()->type() == Estimate::Type_Effort) {
                foreach (ResourceRequest *req, map.value(static_cast<Node*>(task))) {
                    DateTime y = req->resource()->availableBefore(r->child()->startTime(), task->endTime(), 0);
                    cs->logDebug(QString("Resource '%1' available before %2: %3").arg(req->resource()->name()).arg(r->child()->startTime().toString()).arg(y.toString()), 2);
                    if (y.isValid() && y > end) {
                        end = y;
                    }
                }
            } else if (task->estimate()->calendar()) {
                end = task->estimate()->calendar()->firstAvailableBefore(r->child()->startTime(), task->endTime());
                cs->logDebug(QString("Calendar work time before %1: %2").arg(r->child()->startTime().toString()).arg(end.toString()), 2);
            }
            x = qMin(x, end.isValid() ? end - task->endTime() : r->child()->startTime() - task->endTime());
        }
        task->setFreeFloat(x);
        // TODO must calculate backwards to get late *start* of task
        pf = prev + x;
    }
    task->setPositiveFloat(pf);
    task->setLateFinish(task->endTime() + pf);
    task->setLateStart(task->lateFinish() - (task->endTime() - task->startTime()));
    QLocale locale;
    cs->logDebug(QString("Late start %1, late finish %2, positive float %3").arg(locale.toString(task->lateStart(), QLocale::ShortFormat)).arg(locale.toString(task->lateFinish(), QLocale::ShortFormat)).arg(pf.toString()), 2);
    return pf;
}

Duration KPlatoRCPSScheduler::calculateEarlyStuff(const QMap<Node*, QList<ResourceRequest*> > &map, Task *task)
{
    Schedule *cs = task->currentSchedule();
    Duration tot = m_project->endTime() - m_project->startTime();
    QList<Relation*> lst = task->dependParentNodes() + task->parentProxyRelations();
    if (lst.isEmpty()) {
        // start node
        DateTime earlystart = task->startTime();
        if (task->estimate()->type() == Estimate::Type_Effort) {
            foreach (ResourceRequest *r, map.value(static_cast<Node*>(task))) {
                DateTime x = r->resource()->availableAfter(m_project->startTime(), task->startTime(), 0);
                cs->logDebug(QString("Resource '%1' available after %2 (%4): %3").arg(r->resource()->name()).arg(m_project->startTime().toString()).arg(x.toString()).arg(task->startTime().toString()), 2);
                if (x.isValid() && x < earlystart) {
                    earlystart = x;
                }
            }
        } else if (task->estimate()->calendar()) {
            earlystart = task->estimate()->calendar()->firstAvailableAfter(m_project->startTime(), task->startTime());
            cs->logDebug(QString("Calendar work time after %1: %2").arg(m_project->startTime().toString()).arg(earlystart.toString()), 2);
        }
        // TODO must calculate forward to get early *????* of task
        tot = earlystart.isValid() ? task->startTime() - earlystart : task->startTime() - m_project->startTime();
    } else {
        Duration prev = tot;
        Duration x = tot;
        foreach (Relation *r, lst) {
            prev = qMin(prev, calculateEarlyStuff(map, static_cast<Task*>(r->parent())));
            DateTime earlystart = task->startTime();
            if (task->estimate()->type() == Estimate::Type_Effort) {
                foreach (ResourceRequest *req, map.value(static_cast<Node*>(task))) {
                    DateTime y = req->resource()->availableAfter(r->parent()->endTime(), task->startTime(), 0);
                    cs->logDebug(QString("Resource '%1' available after %2: %3").arg(req->resource()->name()).arg(r->parent()->endTime().toString()).arg(y.toString()), 2);
                    if (y.isValid() && y < earlystart) {
                        earlystart = y;
                    }
                }
            } else if (task->estimate()->calendar()) {
                earlystart = task->estimate()->calendar()->firstAvailableAfter(r->parent()->endTime(), task->startTime());
                cs->logDebug(QString("Calendar work time after %1: %2").arg(r->parent()->endTime().toString()).arg(earlystart.toString()), 2);
            }
            x = qMin(x, earlystart.isValid() ? task->startTime() - earlystart : task->startTime() - r->parent()->startTime());
        }
        // TODO must calculate backwards to get late *start* of task
        tot = prev + x;
    }
    task->setEarlyStart(task->startTime() - tot);
    task->setEarlyFinish(task->earlyStart() + (task->endTime() - task->startTime()));
    QLocale locale;
    cs->logDebug(QString("Early start %1, early finish %2").arg(locale.toString(task->earlyStart(), QLocale::ShortFormat)).arg(locale.toString(task->earlyFinish(), QLocale::ShortFormat)), 2);
    return tot;
}

struct rcps_resource *KPlatoRCPSScheduler::addResource(KPlato::Resource *r)
{
    if (m_resourcemap.values().contains(r)) {
        warnPlan<<r->name()<<"already exist";
        return 0;
    }
    struct rcps_resource *res = rcps_resource_new();
    rcps_resource_setname(res, r->name().toLocal8Bit().data());
    rcps_resource_setavail(res, r->units());
    rcps_resource_add(m_problem, res);
    m_resourcemap[res] = r;
    return res;
}

void KPlatoRCPSScheduler::addResources()
{
    debugPlan;
    QList<Resource*> list = m_project->resourceList();
    for (int i = 0; i < list.count(); ++i) {
        addResource(list.at(i));
    }
}

int KPlatoRCPSScheduler::toRcpsTime(const DateTime &time) const
{
    return (m_backward ?  time.secsTo(m_starttime) : m_starttime.secsTo(time)) / m_timeunit;
}

DateTime KPlatoRCPSScheduler::fromRcpsTime(int time) const
{
    return m_starttime.addSecs((m_backward ? -time : time) * m_timeunit);
}

struct rcps_job *KPlatoRCPSScheduler::addTask(KPlato::Task *task)
{
    struct rcps_job *job = rcps_job_new();
    rcps_job_setname(job, task->name().toLocal8Bit().data());
    rcps_job_add(m_problem, job);
    m_taskmap[job] = task;
    return job;
}

void KPlatoRCPSScheduler::addTasks()
{
    debugPlan;
    // Add a start job
    m_jobstart = rcps_job_new();
    rcps_job_setname(m_jobstart, "RCPS start job");
    rcps_job_add(m_problem, m_jobstart);
    struct rcps_mode *mode = rcps_mode_new();
    rcps_mode_add(m_jobstart, mode);

    QList<Node*> list = m_project->allNodes();
    for (int i = 0; i < list.count(); ++i) {
        Node *n = list.at(i);
        switch (n->type()) {
            case Node::Type_Summarytask:
                m_schedule->insertSummaryTask(n);
                break;
            case Node::Type_Task:
            case Node::Type_Milestone:
                addTask(static_cast<Task*>(n));
                break;
            default:
                break;
        }
    }
    // Add an end job
    m_jobend = rcps_job_new();
    rcps_job_setname(m_jobend, "RCPS end job");
    rcps_job_add(m_problem, m_jobend);
    mode = rcps_mode_new();
    rcps_mode_add(m_jobend, mode);
    // add a weight callback argument
    struct KPlatoRCPSScheduler::weight_info *info = new KPlatoRCPSScheduler::weight_info;
    info->self = this;
    info->task = 0;
    info->targettime = toRcpsTime(m_targettime);
    info->isEndJob = true;
    info->finish = 0;

    rcps_mode_set_weight_cbarg(mode, info);
    m_weight_info_list[ m_jobend ] = info;

    for(int i = 0; i < rcps_job_count(m_problem); ++i) {
        debugPlan<<"Task:"<<rcps_job_getname(rcps_job_get(m_problem, i));
    }
}

struct rcps_job *KPlatoRCPSScheduler::addJob(const QString &name, int duration)
{
    struct rcps_job *job = rcps_job_new();
    rcps_job_setname(job, name.toLocal8Bit().data());
    rcps_job_add(m_problem, job);
    struct rcps_mode *mode = rcps_mode_new();
    rcps_mode_setduration(mode, duration);
    rcps_mode_add(job, mode);
    return job;
}

void KPlatoRCPSScheduler::addDependenciesForward(struct rcps_job *job, KPlato::Task *task)
{
    if (task->dependParentNodes().isEmpty() && task->parentProxyRelations().isEmpty()) {
        rcps_job_successor_add(m_jobstart, job, SUCCESSOR_FINISH_START);
    }
    if (task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty()) {
        rcps_job_successor_add(job, m_jobend, SUCCESSOR_FINISH_START);
    }
    foreach (Relation *r, task->dependChildNodes()) {
        Node *n = r->child();
        if (n == 0 || n->type() == Node::Type_Summarytask) {
            continue;
        }
        int type = SUCCESSOR_FINISH_START;
        switch (r->type()) {
            case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
            case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
            case Relation::StartStart: type = SUCCESSOR_START_START; break;
        }
        if (r->lag() == Duration::zeroDuration) {
            rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
        } else {
            // Add a dummy job to represent the lag
            struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
            rcps_job_successor_add(job, dummy, type);
            int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
            rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
        }
    }
    foreach (Relation *r, task->childProxyRelations()) {
        Node *n = r->child();
        if (n == 0 || n->type() == Node::Type_Summarytask) {
            continue;
        }
        int type = SUCCESSOR_FINISH_START;
        switch (r->type()) {
            case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
            case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
            case Relation::StartStart: type = SUCCESSOR_START_START; break;
        }
        if (r->lag() == Duration::zeroDuration) {
            rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
        } else {
            // Add a dummy job to represent the lag
            struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
            rcps_job_successor_add(job, dummy, type);
            int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
            rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
        }
    }
}

void KPlatoRCPSScheduler::addDependenciesBackward(struct rcps_job *job, KPlato::Task *task)
{
    if (task->dependParentNodes().isEmpty() && task->parentProxyRelations().isEmpty()) {
        rcps_job_successor_add(job, m_jobend, SUCCESSOR_FINISH_START);
        //debugPlan<<rcps_job_getname(job)<<"->"<<rcps_job_getname(m_jobend);
    }
    if (task->dependChildNodes().isEmpty() && task->childProxyRelations().isEmpty()) {
        rcps_job_successor_add(m_jobstart, job, SUCCESSOR_FINISH_START);
        //debugPlan<<rcps_job_getname(m_jobstart)<<"->"<<rcps_job_getname(job);
    }
    foreach (Relation *r, task->dependParentNodes()) {
        Node *n = r->parent();
        if (n == 0 || n->type() == Node::Type_Summarytask) {
            continue;
        }
        int type = SUCCESSOR_FINISH_START;
        switch (r->type()) {
            case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
            case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
            case Relation::StartStart: type = SUCCESSOR_START_START; break;
        }
        if (r->lag() == Duration::zeroDuration) {
            rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
            //debugPlan<<rcps_job_getname(job)<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<type;
        } else {
            // Add a dummy job to represent the lag
            struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
            rcps_job_successor_add(job, dummy, type);
            debugPlan<<rcps_job_getname(job)<<"->"<<"dummy lag"<<type;
            int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
            rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
            //debugPlan<<"dummy lag"<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<t;
        }
    }
    foreach (Relation *r, task->parentProxyRelations()) {
        Node *n = r->parent();
        if (n == 0 || n->type() == Node::Type_Summarytask) {
            continue;
        }
        int type = SUCCESSOR_FINISH_START;
        switch (r->type()) {
            case Relation::FinishStart: type = SUCCESSOR_FINISH_START; break;
            case Relation::FinishFinish: type = SUCCESSOR_FINISH_FINISH; break;
            case Relation::StartStart: type = SUCCESSOR_START_START; break;
        }
        if (r->lag() == Duration::zeroDuration) {
            rcps_job_successor_add(job, m_taskmap.key(static_cast<Task*>(n)), type);
            //debugPlan<<rcps_job_getname(job)<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<type;
        } else {
            // Add a dummy job to represent the lag
            struct rcps_job *dummy = addJob(r->lag().toString(), r->lag().seconds() / m_timeunit);
            rcps_job_successor_add(job, dummy, type);
            debugPlan<<rcps_job_getname(job)<<"->"<<"dummy lag"<<type;
            int t = type == SUCCESSOR_FINISH_FINISH ? type : SUCCESSOR_FINISH_START;
            rcps_job_successor_add(dummy, m_taskmap.key(static_cast<Task*>(n)), t);
            //debugPlan<<"dummy lag"<<"->"<<rcps_job_getname(m_taskmap.key(static_cast<Task*>(n)))<<t;
        }
    }
}

void KPlatoRCPSScheduler::addDependencies()
{
    debugPlan;
    QMap<struct rcps_job*, Task*> ::const_iterator it = m_taskmap.constBegin();
    for (; it != m_taskmap.constEnd(); ++it) {
        if (m_backward) {
            addDependenciesBackward(it.key(), it.value());
        } else {
            addDependenciesForward(it.key(), it.value());
        }
    }
}

void KPlatoRCPSScheduler::addRequests()
{
    debugPlan;
    QMap<struct rcps_job*, Task*> ::const_iterator it = m_taskmap.constBegin();
    for (; it != m_taskmap.constEnd(); ++it) {
        addRequest(it.key(), it.value());
    }
}

void KPlatoRCPSScheduler::addRequest(rcps_job *job, Task *task)
{
    debugPlan;
    struct rcps_mode *mode = rcps_mode_new();
    rcps_mode_add(job, mode);
    // add a weight callback argument
    struct KPlatoRCPSScheduler::weight_info *wi = new KPlatoRCPSScheduler::weight_info;
    wi->self = this;
    wi->task = task;
    wi->targettime = 0;
    wi->isEndJob = false;
    wi->finish = 0;

    rcps_mode_set_weight_cbarg(mode, wi);
    m_weight_info_list[ job ] = wi;

    if (task->constraint() != Node::FixedInterval) {
        if (task->type() == Node::Type_Milestone || task->estimate() == 0 || (m_recalculate && task->completion().isFinished())) {
            rcps_mode_setduration(mode, 0);
            return;
        }
        if (task->estimate()->type() == Estimate::Type_Duration && task->estimate()->calendar() == 0) {
            // Fixed duration, no duration callback needed
            rcps_mode_setduration(mode, task->estimate()->value(Estimate::Use_Expected, m_usePert).seconds() / m_timeunit);
            return;
        }
    }
    /* set the argument for the duration callback */
    struct KPlatoRCPSScheduler::duration_info *info = new KPlatoRCPSScheduler::duration_info;
    info->self = this;
    info->calls = 0;
    info->task = task;
    if (m_recalculate && task->completion().isStarted()) {
        info->estimate = task->completion().remainingEffort();
    } else {
        info->estimate = task->estimate()->value(Estimate::Use_Expected, m_usePert);
    }
    info->requests = task->requests().resourceRequests(); // returns team members (not team resource itself)
    info->estimatetype = task->estimate()->type();

    rcps_mode_set_cbarg(mode, info);
    m_duration_info_list[ job ] = info;


    foreach (ResourceRequest *rr, info->requests) {
        Resource *r = rr->resource();
        if (r->type() == Resource::Type_Team) {
            warnPlan<<"There should not be any request to a team resource:"<<r->name();
            continue;
        }
        struct rcps_request *req = rcps_request_new();
        rcps_request_add(mode, req);
        m_requestmap[ req ] = rr;
        struct rcps_alternative *alt = rcps_alternative_new();
        rcps_alternative_setresource(alt, m_resourcemap.key(r));
        rcps_alternative_setamount(alt, (double)rr->units() * 100 / r->units());
        rcps_alternative_add(req, alt);
    }
}

void KPlatoRCPSScheduler::setConstraints()
{
    for (QMap<struct rcps_job*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it) {
        Task *task = it.value();
        struct rcps_job *job = it.key();
        struct weight_info *wi = m_weight_info_list.value(job);
        struct duration_info *di = m_duration_info_list.value(job);
        switch (task->constraint()) {
            case Node::MustStartOn:
            case Node::StartNotEarlier:
                wi->targettime = toRcpsTime(task->constraintStartTime());
                if (m_backward) {
                    int d = 0;
                    if (di) {
                        // as m_backward == true, DURATION_BACKWARD in rcps means forward in plan
                        d = duration(DURATION_BACKWARD, wi->targettime, 0, di);
                    }
                    wi->targettime -= d;
                }
                rcps_job_setearliest_start(job, wi->targettime);
                task->currentSchedule()->logDebug(QString("%2 %3 %4: %5 (rcps=%6)")
                            .arg(task->constraintToString())
                            .arg(m_backward?"backward":"forward")
                            .arg(task->constraintStartTime().toString())
                            .arg(fromRcpsTime(wi->targettime).toString())
                            .arg(wi->targettime));
                break;
            case Node::MustFinishOn:
                wi->targettime = toRcpsTime(task->constraintEndTime());
                if (! m_backward) {
                    int d = 0;
                    if (di) {
                        d = duration(DURATION_BACKWARD, wi->targettime, 0, di);
                    }
                    rcps_job_setearliest_start(job, wi->targettime - d);
                }
                break;
            case Node::FinishNotLater:
                wi->targettime = toRcpsTime(task->constraintEndTime());
                if (m_backward) {
                    rcps_job_setearliest_start(job, wi->targettime);
                }
                break;
            case Node::FixedInterval:
                wi->targettime = m_backward ? toRcpsTime(task->constraintEndTime()) : toRcpsTime(task->constraintStartTime());
                rcps_job_setearliest_start(job, wi->targettime);
                break;
            default:
                break;
        }
    }
}

void KPlatoRCPSScheduler::setWeights()
{
    for (QMap<struct rcps_job*, Task*>::ConstIterator it = m_taskmap.constBegin(); it != m_taskmap.constEnd(); ++it) {
        Task *task = it.value();
        struct rcps_job *job = it.key();
        if (m_backward) {
            switch (task->constraint()) {
                case Node::ASAP:
                    rcps_job_setweight(job, WEIGHT_ALAP);
                    break;
                case Node::ALAP:
                    rcps_job_setweight(job, WEIGHT_ASAP);
                    break;
                case Node::StartNotEarlier:
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::MustStartOn:
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::FinishNotLater:
                    rcps_job_setearliest_start(job, toRcpsTime(task->constraintEndTime()));
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::MustFinishOn:
                    rcps_job_setearliest_start(job, toRcpsTime(task->constraintEndTime()));
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::FixedInterval:
                    rcps_job_setearliest_start(job, toRcpsTime(task->constraintEndTime()));
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                default:
                    rcps_job_setweight(job, WEIGHT_ASAP);
                    break;
            }
        } else {
            switch (task->constraint()) {
                case Node::ASAP:
                    rcps_job_setweight(job, WEIGHT_ASAP);
                    break;
                case Node::ALAP:
                    rcps_job_setweight(job, WEIGHT_ALAP);
                    break;
                case Node::StartNotEarlier:
                    rcps_job_setearliest_start(job, toRcpsTime(task->constraintStartTime()));
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::MustStartOn:
                    rcps_job_setearliest_start(job, toRcpsTime(task->constraintStartTime()));
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::FinishNotLater:
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::MustFinishOn:
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                case Node::FixedInterval:
                    rcps_job_setearliest_start(job, toRcpsTime(task->constraintStartTime()));
                    rcps_job_setweight(job, WEIGHT_CONSTRAINT);
                    break;
                default:
                    rcps_job_setweight(job, WEIGHT_ASAP);
                    break;
            }
        }
    }
}
