/* This file is part of the KDE project
   SPDX-FileCopyrightText: 2009 Dag Andersen <dag.andersen@kdemail.net>

   SPDX-License-Identifier: LGPL-2.0-or-later
*/
// clazy:excludeall=qstring-arg
#include "ResourceModelTester.h"

#include "kptcommand.h"
#include "kptcalendar.h"
#include "kptdatetime.h"
#include "kptresource.h"
#include "kptnode.h"
#include "kpttask.h"
#include "kptschedule.h"
#include "kptappointment.h"

#include <QModelIndex>

#include <QTest>

namespace QTest
{
    template<>
            char *toString(const KPlato::DateTime &dt)
    {
        return toString(dt.toString());
    }
}


namespace KPlato
{

void ResourceModelTester::printDebug(long id) const {
    Project *p = m_project;
    Resource *r = m_resource;
    qDebug()<<"Debug info -------------------------------------";
    qDebug()<<"project start time:"<<p->startTime().toString();
    qDebug()<<"project end time  :"<<p->endTime().toString();

    qDebug()<<"Resource start:"<<r->startTime(id).toString();
    qDebug()<<"Resource end  :"<<r->endTime(id).toString();
    qDebug()<<"Appointments:"<<r->numAppointments(id)<<"(internal)";
    const QList<Appointment*> appointments = r->appointments(id);
    for (Appointment *a : appointments) {
        const AppointmentIntervalList intervals = a->intervals().map().values();
        for (const AppointmentInterval &i : intervals ) {
            qDebug()<<"  "<<i.startTime().toString()<<"-"<<i.endTime().toString()<<";"<<i.load();
        }
    }
    qDebug()<<"Appointments:"<<r->numExternalAppointments()<<"(external)";
    const QList<Appointment*> appointments2 = r->externalAppointmentList();
    for (Appointment *a : appointments2) {
        const AppointmentIntervalList intervals = a->intervals().map().values();
        for (const AppointmentInterval &i : intervals) {
            qDebug()<<"  "<<i.startTime().toString()<<"-"<<i.endTime().toString()<<";"<<i.load();
        }
    }
}

void ResourceModelTester::printSchedulingLog(const ScheduleManager &sm) const
{
    qDebug()<<"Scheduling log ---------------------------------";
    const QStringList logs = sm.expected()->logMessages();
    for (const QString &s : logs) {
        qDebug()<<s;
    }
}

void ResourceModelTester::initTestCase()
{
    m_project = new Project();
    m_project->setName("P1");
    m_project->setId(m_project->uniqueNodeId());
    m_project->registerNodeId(m_project);
    DateTime targetstart = DateTime(QDate::currentDate(), QTime(0,0,0));
    DateTime targetend = DateTime(targetstart.addDays(3));
    m_project->setConstraintStartTime(targetstart);
    m_project->setConstraintEndTime(targetend);

    // standard worktime defines 8 hour day as default
    QVERIFY(m_project->standardWorktime());
    QCOMPARE(m_project->standardWorktime()->day(), 8.0);
    m_calendar = new Calendar("Test");
    m_calendar->setDefault(true);
    QTime t1(9, 0, 0);
    QTime t2 (17, 0, 0);
    int length = t1.msecsTo(t2);
    for (int i=1; i <= 7; ++i) {
        CalendarDay *d = m_calendar->weekday(i);
        d->setState(CalendarDay::Working);
        d->addInterval(t1, length);
    }
    m_project->addCalendar(m_calendar);


    ResourceGroup *g = new ResourceGroup();
    g->setName("G1");
    m_project->addResourceGroup(g);
    m_resource = new Resource();
    m_resource->setName("R1");
    m_resource->setCalendar(m_calendar);
    m_project->addResource(m_resource);
    g->addResource(m_resource);

    m_task = m_project->createTask();
    m_task->setName("T1");
    m_project->addTask(m_task, m_project);
    m_task->estimate()->setUnit(Duration::Unit_h);
    m_task->estimate()->setExpectedEstimate(8.0);
    m_task->estimate()->setType(Estimate::Type_Effort);


    ResourceGroupRequest *gr = new ResourceGroupRequest(g);
    m_task->addRequest(gr);
    m_task->requests().addResourceRequest(new ResourceRequest(m_resource, 100), gr);

    m_model.setProject(m_project);

    QModelIndex idx;
    int rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    idx = m_model.index(0, 0, idx);
    QCOMPARE(g->name(), m_model.data(idx).toString());
    
    rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    idx = m_model.index(0, 0, idx);
    QCOMPARE(m_resource->name(), m_model.data(idx).toString());

    idx = m_model.parent(idx);
    QCOMPARE(g->name(), m_model.data(idx).toString());
}

void ResourceModelTester::cleanupTestCase()
{
    delete m_project;
}

void ResourceModelTester::internalAppointments()
{
    ScheduleManager *sm = m_project->createScheduleManager("Test Plan");
    m_project->addScheduleManager(sm);
    sm->createSchedules();
    m_project->calculate(*sm);
    long id = sm->scheduleId();
    m_model.setScheduleManager(sm);

    //printDebug(sm->scheduleId());

    QModelIndex idx;
    // resource group
    int rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    QModelIndex gidx = m_model.index(0, 0, idx);
    QVERIFY(gidx.isValid());

    // resource
    rows = m_model.rowCount(gidx);
    QCOMPARE(rows, 1);
    QModelIndex ridx = m_model.index(0, 0, gidx);
    QCOMPARE(m_resource->name(), m_model.data(ridx).toString());

    ridx = m_model.index(m_resource);
    QVERIFY(ridx.isValid());

    // appointment
    rows = m_model.rowCount(ridx);
    QCOMPARE(rows, m_resource->numAppointments(id));
    QCOMPARE(rows, 1);

    QModelIndex aidx = m_model.index(0, 0, ridx); // first appointment
    QVERIFY(aidx.isValid());
    rows = m_model.rowCount(aidx); // num intervals
    QCOMPARE(rows, 1);

    // interval
    QModelIndex iidx = m_model.index(0, 0, aidx); // first interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);

    // appointment
    idx = m_model.parent(iidx);
    QCOMPARE(idx, aidx);
    // resource
    idx = m_model.parent(aidx);
    QCOMPARE(idx, ridx);

    // resource group
    idx = m_model.parent(ridx);
    QCOMPARE(idx, gidx);

    // top
    idx = m_model.parent(gidx);
    QVERIFY(! idx.isValid());

}

void ResourceModelTester::externalAppointments()
{
    DateTime targetstart = m_project->constraintStartTime();
    DateTime targetend = m_project->constraintEndTime();
    Task *t = m_task;
    Resource *r = m_resource;

    r->addExternalAppointment("Ext-1", "External project 1", targetstart, targetstart.addDays(1), 100);
    r->addExternalAppointment("Ext-1", "External project 1", targetend.addDays(-1), targetend, 100);

    ScheduleManager *sm = m_project->createScheduleManager("Test Plan");
    m_project->addScheduleManager(sm);
    sm->createSchedules();
    m_project->calculate(*sm);
    long id = sm->scheduleId();
    m_model.setScheduleManager(sm);
    //printSchedulingLog(*sm);
    //printDebug(sm->scheduleId());
    
    // resource group
    QModelIndex idx;
    int rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    idx = m_model.index(0, 0, idx);
    QVERIFY(idx.isValid());
    QVERIFY(! m_model.parent(idx).isValid());

    // resource
    rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    QModelIndex ridx = m_model.index(0, 0, idx);
    QCOMPARE(m_resource->name(), m_model.data(ridx).toString());
    QCOMPARE(ridx.parent(), idx);

    ridx = m_model.index(m_resource);
    QVERIFY(ridx.isValid());

    // appointments
    rows = m_model.rowCount(ridx);
    QCOMPARE(rows, m_resource->numAppointments(id) + m_resource->numExternalAppointments());
    QCOMPARE(rows, 2); // one internal, one external

    // internal appointment
    QModelIndex aidx = m_model.index(0, 0, ridx); // first appointment (internal)
    QVERIFY(aidx.isValid());
    rows = m_model.rowCount(aidx); // num intervals
    QCOMPARE(rows, 1);
    QCOMPARE(aidx.parent(), ridx);

    QModelIndex iidx = m_model.index(0, 0, aidx); // first interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);
    QCOMPARE(iidx.parent(), aidx);

    // external appointment
    aidx = m_model.index(1, 0, ridx); // second appointment (external)
    QVERIFY(aidx.isValid());
    rows = m_model.rowCount(aidx); // num intervals
    QCOMPARE(rows, 2);
    QCOMPARE(aidx.parent(), ridx);

    iidx = m_model.index(0, 0, aidx); // first interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);
    QCOMPARE(iidx.parent(), aidx);

    iidx = m_model.index(1, 0, aidx); // second interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);
    QCOMPARE(iidx.parent(), aidx);

    QCOMPARE(t->startTime(), m_calendar->firstAvailableAfter(targetstart + Duration(1, 0, 0), t->endTime()));
    QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0));
    
}

void ResourceModelTester::externalOverbook()
{
    DateTime targetstart = m_project->constraintStartTime();
    DateTime targetend = m_project->constraintEndTime();
    Task *t = m_task;
    Resource *r = m_resource;

    r->addExternalAppointment("Ext-1", "External project 1", targetstart, targetstart.addDays(1), 100);
    r->addExternalAppointment("Ext-1", "External project 1", targetend.addDays(-1), targetend, 100);

    ScheduleManager *sm = m_project->createScheduleManager("Test Plan");
    m_project->addScheduleManager(sm);
    sm->setAllowOverbooking(true);
    sm->createSchedules();
    m_project->calculate(*sm);
    long id = sm->scheduleId();
    m_model.setScheduleManager(sm);
    //printSchedulingLog(*sm);
    //printDebug(id);

    // resource group
    QModelIndex idx;
    int rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    idx = m_model.index(0, 0, idx);
    QVERIFY(idx.isValid());
    
    // resource
    rows = m_model.rowCount(idx);
    QCOMPARE(rows, 1);
    idx = m_model.index(0, 0, idx);
    QCOMPARE(m_resource->name(), m_model.data(idx).toString());

    idx = m_model.index(m_resource);
    QVERIFY(idx.isValid());

    // appointments
    rows = m_model.rowCount(idx);
    QCOMPARE(rows, m_resource->numAppointments(id) + m_resource->numExternalAppointments());
    QCOMPARE(rows, 2); // one internal, one external

    // internal appointment
    QModelIndex aidx = m_model.index(0, 0, idx); // first appointment (internal)
    QVERIFY(aidx.isValid());
    rows = m_model.rowCount(aidx); // num intervals
    QCOMPARE(rows, 1);

    QModelIndex iidx = m_model.index(0, 0, aidx); // first interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);

    // external appointment
    aidx = m_model.index(1, 0, idx); // second appointment (external)
    QVERIFY(aidx.isValid());
    rows = m_model.rowCount(aidx); // num intervals
    QCOMPARE(rows, 2);

    iidx = m_model.index(0, 0, aidx); // first interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);

    iidx = m_model.index(1, 0, aidx); // second interval
    QVERIFY(iidx.isValid());
    rows = m_model.rowCount(iidx); // intervals don't have children
    QCOMPARE(rows, 0);


    QCOMPARE(t->startTime(), m_calendar->firstAvailableAfter(targetstart, t->endTime()));
    QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0));

}


} //namespace KPlato

QTEST_GUILESS_MAIN(KPlato::ResourceModelTester)
