/*
 * 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/date_time/posix_time/posix_time_types.hpp>
#include <boost/test/unit_test.hpp>

#include "TestUtil.hpp"
#include "ecflow/attribute/VerifyAttr.hpp"
#include "ecflow/core/Filesystem.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Family.hpp"
#include "ecflow/node/Suite.hpp"
#include "ecflow/node/Task.hpp"
#include "ecflow/simulator/Simulator.hpp"

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

/// Simulate definition files that are created on then fly. This allows us to create
/// tests with todays date/time this speeds up the testr, we can also validate
/// Defs file, to check for correctness

BOOST_AUTO_TEST_SUITE(S_Simulator)

BOOST_AUTO_TEST_SUITE(T_Time)

BOOST_AUTO_TEST_CASE(test_time) {
    cout << "Simulator:: ...test_time\n";

    // # Note: we have to use relative paths, since these tests are relocatable
    // suite suite
    //   clock real <todays date>
    //   family family
    //     task t1
    //       time <start>
    //   endfamily
    // endsuite

    // Initialise clock with todays date  then create a time attribute + minutes
    // such that the task should only run once, in the next minute
    boost::posix_time::ptime theLocalTime     = Calendar::second_clock_time();
    boost::posix_time::ptime time_plus_minute = theLocalTime + minutes(1);

    Defs theDefs;
    {
        suite_ptr suite = theDefs.add_suite("test_time");
        ClockAttr clockAttr(theLocalTime, false /*false means use real clock*/);
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        task->addTime(ecf::TimeAttr(TimeSlot(time_plus_minute.time_of_day())));
        task->addVerify(VerifyAttr(NState::COMPLETE, 1)); // expect task to complete 1 time
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_time.def"), errorMsg),
                        errorMsg << "\n"
                                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_time.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_time_series) {
    cout << "Simulator:: ...test_time_series\n";

    // suite suite
    //   clock real <sunday>
    //   family family
    //     task t1
    //     time 00:30 23:59 04:00  # should run 6 times 00:30 4:30 8:30 12:30 16:30 20:30
    //   endfamily
    // endsuite

    // Initialise real clock on a Moday, such that task should _only_ run
    // on Monday since we are using a hybrid clock
    Defs theDefs;
    {
        ClockAttr clockAttr(true /*false means use hybrid clock*/);
        clockAttr.date(12, 10, 2009); // 12 October 2009 was a Monday
        suite_ptr suite = theDefs.add_suite("test_time_series");
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        TimeSeries timeSeries(TimeSlot(00, 30), TimeSlot(23, 59), TimeSlot(4, 0), false /* relative */);

        task->addTime(TimeAttr(timeSeries));
        task->addVerify(VerifyAttr(NState::COMPLETE, 6)); // expect task to complete 6 times
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_time_series.def"), errorMsg),
                        errorMsg << "\n"
                                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_time_series.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_time_and_date) {
    cout << "Simulator:: ...test_time_and_date\n";

    // # Note: we have to use relative paths, since these tests are relocatable
    // suite suite
    //   clock real <todays date>
    //   family family
    //     task t1
    //       date  <today date>
    //       time <start>
    //   endfamily
    // endsuite

    Defs theDefs;
    {
        // Initialise clock with todays date  then create a time attribute + minutes
        // such that the task should only run once, in the next minute
        boost::posix_time::ptime theLocalTime     = Calendar::second_clock_time();
        boost::gregorian::date todaysDate         = theLocalTime.date();
        boost::posix_time::ptime time_plus_minute = theLocalTime + minutes(1);

        ClockAttr clockAttr(theLocalTime, false /*false means use real clock*/);
        suite_ptr suite = theDefs.add_suite("test_time_and_date");
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        task->addDate(DateAttr(todaysDate));
        task->addTime(ecf::TimeAttr(TimeSlot(time_plus_minute.time_of_day())));
        task->addVerify(VerifyAttr(NState::COMPLETE, 1)); // expect task to complete 1 time
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_time_and_date.def"), errorMsg),
                        errorMsg << "\n"
                                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_time_and_date.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_time_and_tomorrows_date) {
    cout << "Simulator:: ...test_time_and_tomorrows_date\n";

    // # Note: we have to use relative paths, since these tests are relocatable
    // suite suite
    //   clock real <todays date>
    //   family family
    //     task t1
    //       date  <tomorrows date>
    //       time <start>
    //   endfamily
    // endsuite

    Defs theDefs;
    {
        // Initialise clock with todays date  then create a time attribute + minutes
        // such that the task should only run once, in the next minute
        boost::posix_time::ptime theLocalTime = Calendar::second_clock_time();
        boost::gregorian::date tomorrows_date = theLocalTime.date();
        tomorrows_date += days(1);
        boost::posix_time::ptime time_plus_minute = theLocalTime + minutes(1);

        ClockAttr clockAttr(theLocalTime, false /*false means use real clock*/);
        suite_ptr suite = theDefs.add_suite("test_time_and_tomorrows_date");
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        task->addDate(DateAttr(tomorrows_date));
        task->addTime(ecf::TimeAttr(TimeSlot(time_plus_minute.time_of_day())));
        task->addVerify(VerifyAttr(NState::COMPLETE, 1)); // expect task to complete 1 time
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_time_and_tomorrows_date.def"), errorMsg),
                        errorMsg << "\n"
                                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_time_and_tomorrows_date.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_multiple_times_and_dates) {
    cout << "Simulator:: ...test_multiple_times_and_dates\n";

    // # Note: we have to use relative paths, since these tests are relocatable
    // suite suite
    //   clock real <todays date>
    //   family family
    //     task t1
    //       date <today date>
    //       date <tomorrows date>
    //       time <start>
    //       time <start>
    //   endfamily
    // endsuite
    Defs theDefs;
    {
        // Initialise clock with todays date  then create a time attribute + minutes
        // Note: we don't use:
        //    boost::posix_time::ptime   theLocalTime =  Calendar::second_clock_time();
        // because if run at late we can end up with illegal for the hour, ie. > 24

        boost::gregorian::date todaysDate(2009, 2, 10);
        ptime theLocalTime(todaysDate, hours(3));
        boost::gregorian::date tomarrows_date           = todaysDate + date_duration(1);
        boost::posix_time::time_duration td_plus_minute = theLocalTime.time_of_day() + minutes(1);
        boost::posix_time::time_duration td_plus_hour   = theLocalTime.time_of_day() + hours(1);

        ClockAttr clockAttr(theLocalTime, false /*false means use real clock*/);
        suite_ptr suite = theDefs.add_suite("test_multiple_times_and_dates");
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        task->addDate(DateAttr(todaysDate));
        task->addDate(DateAttr(tomarrows_date));
        task->addTime(TimeAttr(TimeSlot(td_plus_minute)));
        task->addTime(TimeAttr(TimeSlot(td_plus_hour)));
        task->addVerify(VerifyAttr(NState::COMPLETE, 4)); // expect task to complete 4 time
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_multiple_times_and_dates.def"), errorMsg),
                        errorMsg << "\n"
                                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_multiple_times_and_dates.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_multiple_times_and_dates_hybrid) {
    cout << "Simulator:: ...test_multiple_times_and_dates_hybrid\n";

    // # Note: we have to use relative paths, since these tests are relocatable
    // suite suite
    //   clock hybrid <todays date>
    //   family family
    //     task t1
    //       date  <today date>
    //       date  <tomrrows date>
    //       time <start>
    //       time <start>
    //   endfamily
    // endsuite
    Defs theDefs;
    {
        // Initialise clock with todays date  then create a time attribute + minutes
        // such that the task should only run once, in the next minute
        boost::posix_time::ptime theLocalTime = boost::posix_time::ptime(date(2012, 2, 22), time_duration(10, 20, 0));
        boost::gregorian::date todaysDate     = theLocalTime.date();
        boost::gregorian::date tomorrows_date = todaysDate + date_duration(1);
        boost::posix_time::time_duration td_plus_minute    = theLocalTime.time_of_day() + minutes(1);
        boost::posix_time::time_duration td_plus_10_minute = theLocalTime.time_of_day() + minutes(10);

        suite_ptr suite = theDefs.add_suite("test_multiple_times_and_dates_hybrid");
        suite->addClock(ClockAttr(theLocalTime, true)); // true means use hybrid clock

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        task->addDate(DateAttr(todaysDate));
        task->addDate(DateAttr(tomorrows_date));
        task->addTime(TimeAttr(TimeSlot(td_plus_minute)));
        task->addTime(TimeAttr(TimeSlot(td_plus_10_minute)));
        task->addVerify(VerifyAttr(NState::COMPLETE, 2)); // expect task to complete 2 time
                                                          // cout << theDefs << "\n";
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(
        simulator.run(theDefs, findTestDataLocation("test_multiple_times_and_dates_hybrid.def"), errorMsg),
        errorMsg << "\n"
                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_multiple_times_and_dates_hybrid.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_multiple_times_and_days) {
    cout << "Simulator:: ...test_multiple_times_and_days\n";

    // suite suite
    //   clock real <sunday>
    //   family family
    //     rep integer 0 1 1 # repeat twice
    //     task t1
    //       day  monday
    //       day  tuesday
    //       time <start>
    //       time <start>
    //   endfamily
    // endsuite

    // Initialise real clock on a sunday, such that task should run on Monday & tuesday twice
    Defs theDefs;
    {
        ClockAttr clockAttr(false /*false means use real clock*/);
        clockAttr.date(11, 10, 2009); // 11 October 2009 was a sunday
        suite_ptr suite = theDefs.add_suite("test_multiple_times_and_days");
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        // fam->addRepeat( RepeatInteger("rep",0,1,1) );
        task_ptr task = fam->add_task("t");
        task->addDay(DayAttr(DayAttr::MONDAY));
        task->addDay(DayAttr(DayAttr::TUESDAY));
        task->addTime(TimeAttr(TimeSlot(10, 0)));
        task->addTime(TimeAttr(TimeSlot(20, 0)));
        task->addVerify(VerifyAttr(NState::COMPLETE, 4)); // expect task to complete 4 time
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(simulator.run(theDefs, findTestDataLocation("test_multiple_times_and_days.def"), errorMsg),
                        errorMsg << "\n"
                                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_multiple_times_and_days.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_CASE(test_multiple_times_and_days_hybrid) {
    cout << "Simulator:: ...test_multiple_times_and_days_hybrid\n";

    // suite suite
    //   clock hybrid <monday>
    //   family family
    //     task t1
    //       day  monday
    //       day  tuesday
    //       time <start>
    //       time <start>
    //   endfamily
    // endsuite

    // Initialise real clock on a Monday, such that task should _only_ run
    // on Monday since we are using a hybrid clock
    Defs theDefs;
    {
        ClockAttr clockAttr(true /*false means use hybrid clock*/);
        clockAttr.date(12, 10, 2009); // 12 October 2009 was a Monday
        suite_ptr suite = theDefs.add_suite("test_multiple_times_and_days_hybrid");
        suite->addClock(clockAttr);

        family_ptr fam = suite->add_family("family");
        task_ptr task  = fam->add_task("t");
        task->addDay(DayAttr(DayAttr::MONDAY));
        task->addDay(DayAttr(DayAttr::TUESDAY));
        task->addTime(TimeAttr(TimeSlot(10, 0)));
        task->addTime(TimeAttr(TimeSlot(11, 0)));
        task->addTime(TimeAttr(TimeSlot(20, 0)));
        task->addVerify(VerifyAttr(NState::COMPLETE, 3)); // expect task to complete 3 times
    }

    Simulator simulator;
    std::string errorMsg;
    BOOST_CHECK_MESSAGE(
        simulator.run(theDefs, findTestDataLocation("test_multiple_times_and_days_hybrid.def"), errorMsg),
        errorMsg << "\n"
                 << theDefs);

    // remove generated log file. Comment out to debug
    std::string logFileName = findTestDataLocation("test_multiple_times_and_days_hybrid.def") + ".log";
    fs::remove(logFileName);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
