/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include <iostream>

#include <boost/test/unit_test.hpp>

#include "ecflow/core/CalendarUpdateParams.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Family.hpp"
#include "ecflow/node/Jobs.hpp"
#include "ecflow/node/JobsParam.hpp"
#include "ecflow/node/Suite.hpp"
#include "ecflow/node/Task.hpp"

using namespace std;
using namespace ecf;
using namespace boost::posix_time;
using namespace boost::gregorian;

BOOST_AUTO_TEST_SUITE(U_Node)

BOOST_AUTO_TEST_SUITE(T_TimeDependencies)

// See ECFLOW-337
// See ECFLOW-833. Also See Note: ACore/doc/TimeDependencies.ddoc

BOOST_AUTO_TEST_CASE(test_day_time_combination) {
    cout << "ANode:: ...test_day_time_combination\n";
    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 7.6.2015  # sunday
    //  task t1              # run task monday at 10, Day acts like a guard, time if ignored until day is free
    //      day monday
    //      time 10:00
    Defs defs;
    suite_ptr suite                   = defs.add_suite("s1");
    boost::posix_time::ptime the_time = boost::posix_time::ptime(date(2015, 6, 7), time_duration(0, 0, 0)); // sunday
    suite->addClock(ClockAttr(the_time));
    task_ptr t1 = suite->add_task("t1");
    t1->addDay(DayAttr(DayAttr::MONDAY));
    t1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));

    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    boost::posix_time::ptime expected_time =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(10, 0, 0)); // Monday & 10
    // cout << "expected_time =  " << expected_time << "\n";

    int submitted = 0;
    for (int m = 1; m < 120; m++) { // run for 5 days

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << "submitted at " << suite->calendar().suiteTime() << "\n";
            BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time,
                                "\nExpected to submit at " << expected_time << " only, but also found "
                                                           << suite->calendar().suiteTime());
        }

        defs.updateCalendar(calUpdateParams);
        if (suite->calendar().dayChanged()) {
            Node::Requeue_args args(Node::Requeue_args::TIME);
            t1->requeue(args);
        }
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged: " <<   suite->calendar().dayChanged()
        // << "\n";
    }
    BOOST_CHECK_MESSAGE(submitted == 1, "Expected one submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_date_time_combination) {
    cout << "ANode:: ...test_date_time_combination\n";
    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 7.6.2015  # sunday
    //  task t1              # run task monday at 10, Date acts like a guard, time if ignored until date is free
    //      date 8.6.2015
    //      time 10:00
    Defs defs;
    suite_ptr suite                   = defs.add_suite("s1");
    boost::posix_time::ptime the_time = boost::posix_time::ptime(date(2015, 6, 7), time_duration(0, 0, 0)); // sunday
    suite->addClock(ClockAttr(the_time));
    task_ptr t1 = suite->add_task("t1");
    t1->addDate(DateAttr(8, 6, 2015)); // Monday
    t1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    boost::posix_time::ptime expected_time =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(10, 0, 0)); // Monday & 10
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 100; m++) {

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << "submitted at " << suite->calendar().suiteTime() << "\n";
            BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time,
                                "\nExpected to submit at " << expected_time << " only, but also found "
                                                           << suite->calendar().suiteTime());
        }

        defs.updateCalendar(calUpdateParams);
        if (suite->calendar().dayChanged()) {
            Node::Requeue_args args;
            t1->requeue(args);
        }
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged: " <<   suite->calendar().dayChanged()
        // << "\n";
    }
    BOOST_CHECK_MESSAGE(submitted == 1, "Expected one submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_day_time_combination_in_hierarchy) {
    cout << "ANode:: ...test_day_time_combination_in_hierarchy\n";
    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 7.6.2015 # sunday
    //  family f1
    //    day monday     # day attribute prevents time from being free
    //    task t1
    //      time 10:00   # should run ONCE, monday at 10:00
    Defs defs;
    suite_ptr suite                   = defs.add_suite("s1");
    boost::posix_time::ptime the_time = boost::posix_time::ptime(date(2015, 6, 7), time_duration(0, 0, 0)); // sunday
    suite->addClock(ClockAttr(the_time));
    family_ptr f1 = suite->add_family("f1");
    f1->addDay(DayAttr(DayAttr::MONDAY));
    task_ptr t1 = f1->add_task("t1");
    t1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    boost::posix_time::ptime expected_time =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(10, 0, 0)); // Monday & 10
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 120; m++) { // run for 5 days

        // const std::vector<DayAttr>& days = f1->days();
        // const std::vector<ecf::TimeAttr>& times = t1->timeVec();
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged:" << suite->calendar().dayChanged() <<
        // " " << days[0].dump() << " " << times[0].dump() << "\n";

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << "   submitted at " << suite->calendar().suiteTime() << " " << days[0].dump() << " " <<
            // times[0].dump() << "\n";

            // 1st Run: Monday at 10:00 am
            if (submitted == 1)
                BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time,
                                    "\nExpected to submit at " << expected_time << " only, but also found "
                                                               << suite->calendar().suiteTime());

            Node::Requeue_args args;
            f1->requeue(args);
            // cout << "   requeue f1 at " << suite->calendar().suiteTime() << " " << days[0].dump() << " " <<
            // times[0].dump() << "\n";
        }

        defs.updateCalendar(calUpdateParams);
    }
    BOOST_CHECK_MESSAGE(submitted == 1, "Expected one submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_time_day_combination_in_hierarchy) {
    cout << "ANode:: ...test_time_day_combination_in_hierarchy\n";
    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 7.6.2015 # sunday
    //  family f1
    //    time 10:00
    //    task t1      # task should run *TWICE*, Monday morning @00:00 and, at 10:00
    //      day monday
    Defs defs;
    suite_ptr suite                   = defs.add_suite("s1");
    boost::posix_time::ptime the_time = boost::posix_time::ptime(date(2015, 6, 7), time_duration(0, 0, 0)); // sunday
    suite->addClock(ClockAttr(the_time));
    family_ptr f1 = suite->add_family("f1");
    f1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    task_ptr t1 = f1->add_task("t1");
    t1->addDay(DayAttr(DayAttr::MONDAY));
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    boost::posix_time::ptime expected_time1 =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(0, 0, 0)); // Monday & 00:00
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 120; m++) { // run for 5 days
        // const std::vector<DayAttr>& days = t1->days();
        // const std::vector<ecf::TimeAttr>& times = f1->timeVec();
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged:" << suite->calendar().dayChanged() <<
        // " " << days[0].dump() << " " << times[0].dump() << "\n";

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << "   submitted at " << suite->calendar().suiteTime() << " " << days[0].dump() << " " <<
            // times[0].dump() << "\n";

            // 1st Run: Monday at 00:00 am
            if (submitted == 1)
                BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time1,
                                    "\nExpected to submit at " << expected_time1 << " only, but also found "
                                                               << suite->calendar().suiteTime());

            Node::Requeue_args args;
            f1->requeue(args);
            // cout << "   requeue f1 at " << suite->calendar().suiteTime() << " " << days[0].dump() << " " <<
            // times[0].dump() << "\n";
        }

        defs.updateCalendar(calUpdateParams);
    }
    BOOST_CHECK_MESSAGE(submitted == 2, "Expected two submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_date_time_combination_in_hierarchy) {
    cout << "ANode:: ...test_date_time_combination_in_hierarchy\n";

    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 7.6.2015 # sunday
    //  family f1
    //    date 8.6.15  # monday date now guards the time
    //    task t1
    //      time 10:00
    Defs defs;
    suite_ptr suite                   = defs.add_suite("s1");
    boost::posix_time::ptime the_time = boost::posix_time::ptime(date(2015, 6, 7), time_duration(0, 0, 0)); // sunday
    suite->addClock(ClockAttr(the_time));
    family_ptr f1 = suite->add_family("f1");
    f1->addDate(DateAttr(8, 6, 2015)); // Monday
    task_ptr t1 = f1->add_task("t1");
    t1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    boost::posix_time::ptime expected_time =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(10, 0, 0)); // Monday & 10
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 100; m++) {

        // const std::vector<DateAttr>& dates = f1->dates();
        // const std::vector<ecf::TimeAttr>& times = t1->timeVec();
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged:" << suite->calendar().dayChanged() <<
        // " " << dates[0].dump() << " " << times[0].dump() << "\n";

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << "  submitted at " << suite->calendar().suiteTime() << " " << dates[0].dump() << " " <<
            // times[0].dump() << "\n";

            // New to ecflow 5.0, parent date will guard the time. i.e will not let time be free until date is
            // satisfied.
            if (submitted == 1)
                BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time,
                                    "\nExpected to submit at " << expected_time << " only, but also found "
                                                               << suite->calendar().suiteTime());

            Node::Requeue_args args;
            f1->requeue(args);
            // cout << "  requeue at " << suite->calendar().suiteTime() << " " << dates[0].dump() << " " <<
            // times[0].dump() << "\n";
        }

        defs.updateCalendar(calUpdateParams);
    }
    BOOST_CHECK_MESSAGE(submitted == 1, "Expected one submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_time_date_combination_in_hierarchy) {
    cout << "ANode:: ...test_time_date_combination_in_hierarchy \n";
    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 7.6.2015 # sunday
    //  family f1
    //    time 10:00
    //    task t1
    //       date 8.6.15  # monday, run once at Monday morning, and gain Monday at 10:00
    //
    Defs defs;
    suite_ptr suite                   = defs.add_suite("s1");
    boost::posix_time::ptime the_time = boost::posix_time::ptime(date(2015, 6, 7), time_duration(0, 0, 0)); // sunday
    suite->addClock(ClockAttr(the_time));
    family_ptr f1 = suite->add_family("f1");
    f1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    task_ptr t1 = f1->add_task("t1");
    t1->addDate(DateAttr(8, 6, 2015)); // Monday
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    boost::posix_time::ptime expected_time1 =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(0, 0, 0)); // Monday & 00:00
    boost::posix_time::ptime expected_time2 =
        boost::posix_time::ptime(date(2015, 6, 8), time_duration(10, 0, 0)); // Monday & 10
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 100; m++) {

        // const std::vector<DateAttr>& dates = t1->dates();
        // const std::vector<ecf::TimeAttr>& times = f1->timeVec();
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged:" << suite->calendar().dayChanged() <<
        // " " << dates[0].dump() << " " << times[0].dump() << "\n";

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << "  submitted at " << suite->calendar().suiteTime() << " " << dates[0].dump() << " " <<
            // times[0].dump() << "\n";

            // 1st Run: MONDAY Morning 00:00 time was free from sunday
            if (submitted == 1)
                BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time1,
                                    "\nExpected to submit at " << expected_time1 << " only, but also found "
                                                               << suite->calendar().suiteTime());

            // 2nd Run: Monday at 10:00
            if (submitted == 2)
                BOOST_CHECK_MESSAGE(suite->calendar().suiteTime() == expected_time2,
                                    "\nExpected to submit at " << expected_time2 << " only, but also found "
                                                               << suite->calendar().suiteTime());

            Node::Requeue_args args;
            f1->requeue(args);
            // cout << "  requeue at " << suite->calendar().suiteTime() << " " << dates[0].dump() << " " <<
            // times[0].dump() << "\n";
        }

        defs.updateCalendar(calUpdateParams);
    }
    BOOST_CHECK_MESSAGE(submitted == 2, "Expected two submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_impossible_day_combination) {
    cout << "ANode:: ...test_impossible_day_combination\n";
    // Create the suite, starting on a sunday
    // suite s1
    //  clock real 4.8.2019 # sunday
    //  family f1
    //    day monday
    //    task t1        # task should never run, can't be Sunday/Monday at same time.
    //      day tuesday
    //      time 10:00
    Defs defs;
    suite_ptr suite = defs.add_suite("s1");
    suite->addClock(ClockAttr(boost::posix_time::ptime(date(2019, 8, 4), time_duration(0, 0, 0)))); // sunday
    family_ptr f1 = suite->add_family("f1");
    f1->addDay(DayAttr(DayAttr::MONDAY));
    task_ptr t1 = f1->add_task("t1");
    t1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    t1->addDay(DayAttr(DayAttr::TUESDAY));
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 100; m++) {
        // const std::vector<DateAttr>& fdates = f1->dates();
        // const std::vector<DateAttr>& dates = t1->dates();
        // const std::vector<ecf::TimeAttr>& times = t1->timeVec();
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged:" << suite->calendar().dayChanged() <<
        // "\n   "
        //          << fdates[0].dump() << " " << dates[0].dump() << " " << times[0].dump() << " rep: " <<
        //          f1->repeat().value() << "\n";

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << " submitted " << suite->calendar().suiteTime() << " dayChanged:" <<
            // suite->calendar().dayChanged() << "\n   "
            //          << fdates[0].dump() << " " << dates[0].dump() << " " << times[0].dump() << " rep: " <<
            //          f1->repeat().value() << "\n";
            Node::Requeue_args args;
            t1->requeue(args);
        }

        defs.updateCalendar(calUpdateParams);
    }
    BOOST_CHECK_MESSAGE(submitted == 0, "Expected zero submission but found " << submitted);
}

BOOST_AUTO_TEST_CASE(test_impossible_date_combination) {
    cout << "ANode:: ...test_impossible_date_combination\n";

    // Create the suite, starting on a sunday
    // suite s1
    //  family f1
    //    date 5.8.2019   # monday
    //    task t1         # task should never run, can't be Sunday/Monday at same time.
    //      date 6.8.2019 # tuesday
    //      time 10:00
    Defs defs;
    suite_ptr suite = defs.add_suite("s1");
    suite->addClock(ClockAttr(boost::posix_time::ptime(date(2019, 8, 4), time_duration(0, 0, 0)))); // sunday
    family_ptr f1 = suite->add_family("f1");
    f1->addDate(DateAttr(5, 8, 2019)); // Monday
    task_ptr t1 = f1->add_task("t1");
    t1->addTime(ecf::TimeAttr(ecf::TimeSlot(10, 0)));
    t1->addDate(DateAttr(6, 8, 2019)); // Tuesday
    defs.beginAll();

    CalendarUpdateParams calUpdateParams(hours(1));
    // cout << defs << "\n";

    int submitted = 0;
    for (int m = 1; m < 100; m++) {
        // const std::vector<DayAttr>& fdays = f1->days();
        // const std::vector<DayAttr>& days = t1->days();
        // const std::vector<ecf::TimeAttr>& times = t1->timeVec();
        // cout << "Suite time " << suite->calendar().suiteTime() << " dayChanged:" << suite->calendar().dayChanged() <<
        // "\n   "
        //          << fdays[0].dump() << " " << days[0].dump() << " " << times[0].dump() << " rep: " <<
        //          f1->repeat().value() << "\n";

        Jobs jobs(&defs);
        JobsParam jobsParam;
        jobs.generate(jobsParam);

        if (jobsParam.submitted().size()) {
            submitted++;
            // cout << " submitted at " << suite->calendar().suiteTime() << " dayChanged:" <<
            // suite->calendar().dayChanged() << "\n   "
            //          << fdays[0].dump() << " " << days[0].dump() << " " << times[0].dump() << " rep: " <<
            //          f1->repeat().value() << "\n";
            Node::Requeue_args args;
            t1->requeue(args);
        }
        defs.updateCalendar(calUpdateParams);
    }
    BOOST_CHECK_MESSAGE(submitted == 0, "Expected zero submission but found " << submitted);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
