/*
 * 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 <string>

#include <boost/date_time/posix_time/time_formatters.hpp>
#include <boost/test/unit_test.hpp>

#include "ecflow/core/Calendar.hpp"
#include "ecflow/core/Str.hpp"
#include "ecflow/core/TimeSeries.hpp"

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

using namespace boost;

BOOST_AUTO_TEST_SUITE(U_Core)

BOOST_AUTO_TEST_SUITE(T_TimeSeries)

BOOST_AUTO_TEST_CASE(test_default_boost_time_duration) {
    cout << "ACore:: ...test_default_boost_time_duration\n";

    boost::posix_time::time_duration td;
    BOOST_CHECK_MESSAGE(td.is_special() == false,
                        "Default constructed boost::posix_time::time_duration, should not be special");
    BOOST_CHECK_MESSAGE(td.hours() == 0, "Expected 0 for hours");
    BOOST_CHECK_MESSAGE(td.minutes() == 0, "Expected 0 for minutes");
    BOOST_CHECK_MESSAGE(td.seconds() == 0, "Expected 0 for seconds ");
}

BOOST_AUTO_TEST_CASE(test_time_series_min_max_time_slots) {
    cout << "ACore:: ...test_time_series_min_max_time_slots\n";

    TimeSlot the_min;
    TimeSlot the_max;

    TimeSeries a(9, 0, true);
    a.min_max_time_slots(the_min, the_max);
    BOOST_CHECK_MESSAGE(the_min == TimeSlot(9, 0), "Max min time slot failed");
    BOOST_CHECK_MESSAGE(the_max == TimeSlot(9, 0), "Max min time slot failed");

    TimeSeries b(TimeSlot(10, 12), false);
    b.min_max_time_slots(the_min, the_max);
    BOOST_CHECK_MESSAGE(the_min == TimeSlot(9, 0), "Max min time slot failed");
    BOOST_CHECK_MESSAGE(the_max == TimeSlot(10, 12), "Max min time slot failed");

    TimeSeries c(TimeSlot(0, 10), TimeSlot(10, 13), TimeSlot(0, 1));
    c.min_max_time_slots(the_min, the_max);
    BOOST_CHECK_MESSAGE(the_min == TimeSlot(0, 10), "Max min time slot failed");
    BOOST_CHECK_MESSAGE(the_max == TimeSlot(10, 13), "Max min time slot failed");

    TimeSeries x;
    x.min_max_time_slots(the_min, the_max);
    BOOST_CHECK_MESSAGE(the_min == TimeSlot(), "Max min time slot failed");
    BOOST_CHECK_MESSAGE(the_max == TimeSlot(10, 13), "Max min time slot failed");
}

BOOST_AUTO_TEST_CASE(test_time_series_constrcution) {
    cout << "ACore:: ...test_time_series_constrcution\n";

    {
        TimeSeries x;
        TimeSeries y;
        BOOST_CHECK_MESSAGE(x == y, "Equality operator expected to succeed");

        TimeSeries a(10, 12, true);
        TimeSeries b(10, 12, true);
        BOOST_CHECK_MESSAGE(a == b, "Equality operator expected to succeed");

        TimeSeries c(10, 12, false);
        TimeSeries d(10, 12, false);
        BOOST_CHECK_MESSAGE(c == d, "Equality operator expected to succeed");

        TimeSeries e(TimeSlot(14, 59), false);
        TimeSeries f(TimeSlot(14, 59), false);
        BOOST_CHECK_MESSAGE(e == f, "Equality operator expected to succeed");

        TimeSeries g(TimeSlot(0, 10), TimeSlot(10, 4), TimeSlot(0, 1));
        TimeSeries h(TimeSlot(0, 10), TimeSlot(10, 4), TimeSlot(0, 1));
        BOOST_CHECK_MESSAGE(g == h, "Equality operator expected to succeed");
    }
    {
        TimeSeries a(10, 12, false);
        TimeSeries b(10, 12, true);
        BOOST_CHECK_MESSAGE(a != b, "Equality operator expected to fail");

        TimeSeries c(10, 13, false);
        TimeSeries d(10, 12, false);
        BOOST_CHECK_MESSAGE(c != d, "Equality operator expected to fail");

        TimeSeries e(TimeSlot(14, 59), false);
        TimeSeries f(TimeSlot(14, 10), false);
        BOOST_CHECK_MESSAGE(e != f, "Equality operator expected to fail");

        TimeSeries g(TimeSlot(0, 10), TimeSlot(10, 4), TimeSlot(0, 1), true);
        TimeSeries h(TimeSlot(0, 10), TimeSlot(10, 4), TimeSlot(0, 1), false);
        BOOST_CHECK_MESSAGE(g != h, "Equality operator expected to fail");
    }

    /// Basic test for time series of getters and setters
    TimeSeries timeSeries(TimeSlot(0, 1), TimeSlot(0, 4), TimeSlot(0, 1));
    BOOST_CHECK_MESSAGE(timeSeries.hasIncrement(), "expected increment");
    std::string errMsg;
    BOOST_CHECK_MESSAGE(timeSeries.checkInvariants(errMsg), errMsg);

    TimeSeries another = timeSeries;
    BOOST_CHECK_MESSAGE(another == timeSeries, "copy constructor failed for TimeSeries ");
    BOOST_CHECK_MESSAGE(another.checkInvariants(errMsg), errMsg);
    BOOST_CHECK_MESSAGE(timeSeries.checkInvariants(errMsg), errMsg);

    TimeSeries timeSeries2(TimeSlot(0, 1), TimeSlot(0, 4), TimeSlot(0, 1));
    BOOST_CHECK_MESSAGE(timeSeries2.checkInvariants(errMsg), errMsg);
    another = timeSeries2;
    BOOST_CHECK_MESSAGE(another == timeSeries2, "assignment operator failed for TimeSeries ");
    BOOST_CHECK_MESSAGE(another.checkInvariants(errMsg), errMsg);

    TimeSeries timeSeries3(TimeSlot(0, 1));
    BOOST_CHECK_MESSAGE(timeSeries3.checkInvariants(errMsg), errMsg);
}

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

    /// Basic test for time series of getters and setters
    TimeSeries timeSeries(TimeSlot(0, 1), TimeSlot(0, 4), TimeSlot(0, 1));
    BOOST_CHECK_MESSAGE(timeSeries.hasIncrement(), "expected increment");

    std::string expected = "00:01:00";
    std::string actual   = to_simple_string(timeSeries.start().duration());
    BOOST_CHECK_MESSAGE(expected == actual, " expected " << expected << " but found " << actual);

    expected = "00:04:00";
    actual   = to_simple_string(timeSeries.finish().duration());
    BOOST_CHECK_MESSAGE(expected == actual, " expected " << expected << " but found " << actual);

    expected = "00:01:00";
    actual   = to_simple_string(timeSeries.incr().duration());
    BOOST_CHECK_MESSAGE(expected == actual, " expected " << expected << " but found " << actual);

    TimeSeries timeSeries2(TimeSlot(0, 1));
    BOOST_CHECK_MESSAGE(!timeSeries2.hasIncrement(), "Not expecting time series");
}

BOOST_AUTO_TEST_CASE(test_time_series_increment_real) {
    cout << "ACore:: ...test_time_series_increment_real\n";

    // Test time series with  a calendar, we update calendar then
    // test time series isFree(), and checkForRequeue
    Calendar c;
    c.init(ptime(date(2010, 2, 10), minutes(0)), Calendar::REAL);

    // Create a test when we can match a time series. Need to sync hour with suite time
    // at hour 1, suite time should also be 01:00, for test to work
    //
    // Create the time series: start  10:00
    //                         finish 20:00
    //                         incr    1:00
    TimeSeries timeSeries(TimeSlot(10, 0), TimeSlot(20, 0), TimeSlot(1, 0), true /* relative */);
    TimeSeries timeSeries2(TimeSlot(11, 0), TimeSlot(15, 0), TimeSlot(1, 0), true /* relative */);
    TimeSeries timeSeries3(TimeSlot(15, 0), true /* relative */);

    TimeSlot t1_min, t1_max, t2_min, t2_max, t3_min, t3_max;
    timeSeries.min_max_time_slots(t1_min, t1_max);
    timeSeries2.min_max_time_slots(t2_min, t2_max);
    timeSeries3.min_max_time_slots(t3_min, t3_max);
    BOOST_CHECK_MESSAGE(t1_min == TimeSlot(10, 0) && t1_max == TimeSlot(20, 0), "Not as expected");
    BOOST_CHECK_MESSAGE(t2_min == TimeSlot(11, 0) && t2_max == TimeSlot(15, 0), "Not as expected");
    BOOST_CHECK_MESSAGE(t3_min == TimeSlot(15, 0) && t3_max == TimeSlot(15, 0), "Not as expected");

    // Follow normal process
    timeSeries.reset(c);
    timeSeries2.reset(c);
    timeSeries3.reset(c);

    bool cmd_context = true;
    for (int hour = 1; hour < 24; hour++) {
        // Update calendar every hour, then see we can match time series, *RELATIVE* to suite start
        c.update(time_duration(hours(1)));
        timeSeries.calendarChanged(c);
        timeSeries2.calendarChanged(c);
        timeSeries3.calendarChanged(c);

        //		cerr << "hour = " << hour << " calendar_duration " << to_simple_string(timeSeries.duration(c))
        //		    << " timeSeries=" << timeSeries.toString() << " timeSeries2=" << timeSeries2.toString() << "
        // timeSeries3=" << timeSeries3.toString() << "\n";
        if (hour < timeSeries.start().hour()) {
            BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(c, t1_min, t1_max, cmd_context),
                                " Time series " << timeSeries.toString() << " checkForRequeue should pass at "
                                                << c.suite_time_str());
            BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(c, t1_min, t1_max),
                                " Time series " << timeSeries.toString() << " checkForRequeue should fail at "
                                                << c.suite_time_str());
            BOOST_CHECK_MESSAGE(!timeSeries.isFree(c),
                                "Time series " << timeSeries.toString() << " should NOT be free at hour " << hour);
        }
        else if (hour >= timeSeries.start().hour() && hour <= timeSeries.finish().hour()) {
            BOOST_CHECK_MESSAGE(timeSeries.isFree(c),
                                "Time series " << timeSeries.toString() << " should be free at hour " << hour);

            /// At the last hour checkForRequeue should return false; This ensures that value will
            /// not get incremented and so, should leave node in the complete state.
            if (hour < timeSeries.finish().hour()) {
                BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(c, t1_min, t1_max),
                                    "Time series " << timeSeries.toString()
                                                   << " checkForRequeue should be free at hour " << hour);
            }
            else {
                BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(c, t1_min, t1_max),
                                    "Time series " << timeSeries.toString()
                                                   << "checkForRequeue should Not free at hour " << hour);
            }
        }
        else {
            BOOST_CHECK_MESSAGE(!timeSeries.isFree(c),
                                "Time series " << timeSeries.toString() << " should NOT be free at hour " << hour);
            BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(c, t1_min, t1_max),
                                " Time series " << timeSeries.toString() << " should fail at " << hour);
        }

        if (hour < timeSeries2.start().hour()) {
            BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(c, t1_min, t1_max),
                                " Time series " << timeSeries2.toString() << "checkForRequeue should pass at " << hour);
            BOOST_CHECK_MESSAGE(!timeSeries2.isFree(c),
                                "Time series " << timeSeries2.toString() << " should NOT be free at hour " << hour);
        }
        else if (hour >= timeSeries2.start().hour() && hour <= timeSeries2.finish().hour()) {
            BOOST_CHECK_MESSAGE(timeSeries2.isFree(c),
                                "Time series " << timeSeries2.toString() << " should be free at hour " << hour);

            /// At the last hour checkForRequeue should return false;
            if (hour < timeSeries2.finish().hour()) {
                BOOST_CHECK_MESSAGE(timeSeries2.checkForRequeue(c, t2_min, t2_max),
                                    "Time series " << timeSeries2.toString()
                                                   << " checkForRequeue should be free at hour " << hour);
            }
            else {
                BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(c, t2_min, t2_max),
                                    "Time series " << timeSeries2.toString()
                                                   << "checkForRequeue should Not free at hour " << hour);
            }
        }
        else {
            BOOST_CHECK_MESSAGE(!timeSeries2.isFree(c),
                                "Time series " << timeSeries2.toString() << " not be free at hour " << hour);
            BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(c, t2_min, t2_max),
                                " Time series " << timeSeries2.toString() << " should fail at " << hour);
        }

        if (hour == timeSeries3.start().hour()) {
            BOOST_CHECK_MESSAGE(timeSeries3.isFree(c),
                                "Time series " << timeSeries3.toString() << " should be free at hour " << hour);
        }
        else if (hour < timeSeries3.start().hour()) {
            BOOST_CHECK_MESSAGE(!timeSeries3.isFree(c),
                                "Time series " << timeSeries3.toString() << " isFree should fail at hour " << hour);
        }
        else if (hour > timeSeries3.start().hour()) {
            BOOST_CHECK_MESSAGE(!timeSeries3.isFree(c),
                                "Time series " << timeSeries3.toString() << " is Free should fail at hour " << hour);
        }
        BOOST_CHECK_MESSAGE(!timeSeries3.checkForRequeue(c, t3_min, t3_max),
                            " Time series " << timeSeries3.toString() << " checkForRequeue should fail at " << hour);
    }
}

BOOST_AUTO_TEST_CASE(test_time_series_requeueable_and_compute_next_time_slot) {
    cout << "ACore:: ...test_time_series_requeueable_and_compute_next_time_slot\n";

    // Test time series with  a calendar, we update calendar then
    // test time series requeueable(), and compute_next_time_slot
    // This are used with the WHY command
    Calendar c;
    c.init(ptime(date(2010, 2, 10), minutes(0)), Calendar::REAL);

    // Create a test when we can match a time series. Need to sync hour with suite time
    // at hour 1, suite time should also be 01:00, for test to work
    //
    // Create the time series: start  10:00
    //                         finish 20:00
    //                         incr    1:00
    TimeSeries timeSeries(TimeSlot(10, 0), TimeSlot(20, 0), TimeSlot(1, 0), true /* relative */);
    TimeSeries timeSeries2(TimeSlot(11, 0), TimeSlot(15, 0), TimeSlot(1, 0), true /* relative */);
    TimeSeries timeSeries3(TimeSlot(15, 0), true /* relative */);

    for (int hour = 1; hour < 24; hour++) {
        // Update calendar every hour, then see we can match time series,
        c.update(time_duration(hours(1)));
        timeSeries.calendarChanged(c);
        timeSeries2.calendarChanged(c);
        timeSeries3.calendarChanged(c);

        //    cerr << "hour = " << hour << " calendar_duration " << to_simple_string(timeSeries.duration(c))
        //        << " timeSeries=" << timeSeries.toString() << " timeSeries2=" << timeSeries2.toString() << "
        //        timeSeries3=" << timeSeries3.toString() << "\n";
        if (hour < timeSeries.start().hour()) {
            TimeSlot next_time_slot = timeSeries.compute_next_time_slot(c);
            TimeSlot expected(10, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries.requeueable(c),
                                "Time series " << timeSeries.toString() << " should be requeueable at hour " << hour);
        }
        else if (hour == timeSeries.start().hour()) {
            TimeSlot next_time_slot = timeSeries.compute_next_time_slot(c);
            TimeSlot expected(11, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries.requeueable(c),
                                "Time series " << timeSeries.toString() << " should be requeueable at hour " << hour);
        }
        else if (hour > timeSeries.start().hour() && hour < timeSeries.finish().hour()) {
            TimeSlot next_time_slot = timeSeries.compute_next_time_slot(c);
            TimeSlot expected(hour + 1, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries.requeueable(c),
                                "Time series " << timeSeries.toString() << " should be requeueable at hour " << hour);
        }
        else if (hour == timeSeries.finish().hour()) {
            TimeSlot next_time_slot = timeSeries.compute_next_time_slot(c);
            BOOST_CHECK_MESSAGE(next_time_slot.isNULL(),
                                " Time series " << timeSeries.toString() << " at " << hour
                                                << " expected next time slot  to be NULL");
            BOOST_CHECK_MESSAGE(!timeSeries.requeueable(c),
                                "Time series " << timeSeries.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }
        else if (hour > timeSeries.finish().hour()) {
            TimeSlot next_time_slot = timeSeries.compute_next_time_slot(c);
            BOOST_CHECK_MESSAGE(next_time_slot.isNULL(),
                                " Time series " << timeSeries.toString() << " at " << hour
                                                << " expected next time slot to be NULL");
            BOOST_CHECK_MESSAGE(!timeSeries.requeueable(c),
                                "Time series " << timeSeries.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }

        if (hour < timeSeries2.start().hour()) {
            TimeSlot next_time_slot = timeSeries2.compute_next_time_slot(c);
            TimeSlot expected(11, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries2.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries2.requeueable(c),
                                "Time series " << timeSeries2.toString() << " should be requeueable at hour " << hour);
        }
        else if (hour == timeSeries2.start().hour()) {
            TimeSlot next_time_slot = timeSeries2.compute_next_time_slot(c);
            TimeSlot expected(12, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries2.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries2.requeueable(c),
                                "Time series " << timeSeries2.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }
        else if (hour > timeSeries2.start().hour() && hour < timeSeries2.finish().hour()) {
            TimeSlot next_time_slot = timeSeries2.compute_next_time_slot(c);
            TimeSlot expected(hour + 1, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries2.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries2.requeueable(c),
                                "Time series " << timeSeries2.toString() << " should be requeueable at hour " << hour);
        }
        else if (hour == timeSeries2.finish().hour()) {
            TimeSlot next_time_slot = timeSeries2.compute_next_time_slot(c);
            BOOST_CHECK_MESSAGE(next_time_slot.isNULL(),
                                " Time series " << timeSeries2.toString() << " at " << hour
                                                << " expected next time slot  to be NULL");
            BOOST_CHECK_MESSAGE(!timeSeries2.requeueable(c),
                                "Time series " << timeSeries2.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }
        else if (hour > timeSeries2.finish().hour()) {
            TimeSlot next_time_slot = timeSeries2.compute_next_time_slot(c);
            BOOST_CHECK_MESSAGE(next_time_slot.isNULL(),
                                " Time series " << timeSeries2.toString() << " at " << hour
                                                << " expected next time slot to be NULL");
            BOOST_CHECK_MESSAGE(!timeSeries2.requeueable(c),
                                "Time series " << timeSeries2.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }

        if (hour < timeSeries3.start().hour()) {
            TimeSlot next_time_slot = timeSeries3.compute_next_time_slot(c);
            TimeSlot expected(15, 0);
            BOOST_CHECK_MESSAGE(next_time_slot == expected,
                                " Time series " << timeSeries3.toString() << " at " << hour
                                                << " expected next time slot at " << expected.toString()
                                                << " but found " << next_time_slot.toString());
            BOOST_CHECK_MESSAGE(timeSeries3.requeueable(c),
                                "Time series " << timeSeries3.toString() << " should be requeueable at hour " << hour);
        }
        else if (hour == timeSeries3.start().hour()) {
            TimeSlot next_time_slot = timeSeries3.compute_next_time_slot(c);
            BOOST_CHECK_MESSAGE(next_time_slot.isNULL(),
                                " Time series " << timeSeries3.toString() << " at " << hour
                                                << " expected next time slot to be NULL");
            BOOST_CHECK_MESSAGE(!timeSeries3.requeueable(c),
                                "Time series " << timeSeries3.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }
        else if (hour > timeSeries3.start().hour()) {
            TimeSlot next_time_slot = timeSeries3.compute_next_time_slot(c);
            BOOST_CHECK_MESSAGE(next_time_slot.isNULL(),
                                " Time series " << timeSeries3.toString() << " at " << hour
                                                << " expected next time slot to be NULL");
            BOOST_CHECK_MESSAGE(!timeSeries3.requeueable(c),
                                "Time series " << timeSeries3.toString() << " should NOT be requeueable at hour "
                                               << hour);
        }
    }
}

BOOST_AUTO_TEST_CASE(test_time_series_finish_not_divisble_by_increment) {
    cout << "ACore:: ...test_time_series_finish_not_divisble_by_increment\n";

    // HANDLE CASE WHERE FINISH MINUTES IS NOT DIVISIBLE BY THE INCREMENT

    // Test time series with  a calendar, we update calendar then
    // test time series isFree(), and checkForRequeue
    Calendar calendar;
    calendar.init(ptime(date(2008, 10, 8), hours(0)), Calendar::REAL);

    // Create a test when we can match a time series.
    // NOTE: Finish minute is not a multiple of INCREMENT hence last valid time slot is 23:50
    //
    // Create the time series: start  00:00
    //                         finish 23:59
    //                         incr   00:10
    TimeSeries timeSeries(TimeSlot(0, 0), TimeSlot(23, 59), TimeSlot(0, 10), false /* relative */);
    TimeSeries timeSeries2(TimeSlot(0, 30), TimeSlot(23, 59), TimeSlot(4, 0), false /* relative */);

    TimeSlot t1_min, t1_max, t2_min, t2_max;
    timeSeries.min_max_time_slots(t1_min, t1_max);
    timeSeries2.min_max_time_slots(t2_min, t2_max);
    BOOST_CHECK_MESSAGE(t1_min == TimeSlot(0, 0) && t1_max == TimeSlot(23, 59), "Not as expected");
    BOOST_CHECK_MESSAGE(t2_min == TimeSlot(0, 30) && t2_max == TimeSlot(23, 59), "Not as expected");

    time_duration last  = hours(23) + minutes(50); // last valid time is 23:50
    time_duration last2 = hours(20) + minutes(30); // last valid time is 20:30

    // follow normal process
    timeSeries.reset(calendar);
    timeSeries2.reset(calendar);

    for (int hour = 0; hour < 24; hour++) {
        for (int minute = 0; minute < 60; minute++) {

            calendar.update(minutes(1));
            timeSeries.calendarChanged(calendar);
            timeSeries2.calendarChanged(calendar);

            // cout << to_simple_string(calendar.suiteTime()) << "\n";

            if (calendar.dayChanged()) {
                BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                                    " expected " << timeSeries.toString() << " checkForRequeue to pass at "
                                                 << to_simple_string(calendar.suiteTime()));
            }
            else if (calendar.suiteTime().time_of_day() < timeSeries.start().duration()) {
                BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                                    " expected " << timeSeries.toString() << " checkForRequeue to pass at "
                                                 << to_simple_string(calendar.suiteTime()));
            }
            else if (calendar.suiteTime().time_of_day() >= timeSeries.start().duration() &&
                     calendar.suiteTime().time_of_day() < last) {
                BOOST_CHECK_MESSAGE(timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                                    " expected " << timeSeries.toString() << " checkForRequeue to pass at "
                                                 << to_simple_string(calendar.suiteTime()));
            }
            else {
                BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                                    " expected " << timeSeries.toString() << " checkForRequeue to fail at "
                                                 << to_simple_string(calendar.suiteTime()));
            }

            if (calendar.dayChanged()) {
                BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max),
                                    " expected " << timeSeries2.toString() << " checkForRequeue to pass at "
                                                 << to_simple_string(calendar.suiteTime()));
            }
            else if (calendar.suiteTime().time_of_day() < timeSeries2.start().duration()) {
                BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max),
                                    " expected " << timeSeries2.toString() << " checkForRequeue to pass at "
                                                 << to_simple_string(calendar.suiteTime()));
            }
            else if (calendar.suiteTime().time_of_day() >= timeSeries2.start().duration() &&
                     calendar.suiteTime().time_of_day() < last2) {
                BOOST_CHECK_MESSAGE(timeSeries2.checkForRequeue(calendar, t2_min, t2_max),
                                    " expected " << timeSeries2.toString() << " checkForRequeue to pass at "
                                                 << to_simple_string(calendar.suiteTime()));
            }
            else {
                BOOST_CHECK_MESSAGE(!timeSeries2.checkForRequeue(calendar, t2_min, t2_max),
                                    " expected " << timeSeries2.toString() << " checkForRequeue to fail at "
                                                 << to_simple_string(calendar.suiteTime()));
            }

            //         if (calendar.dayChanged()) {
            //            timeSeries.reset(calendar);
            //            timeSeries2.reset(calendar);
            //         }
        }
    }
}

BOOST_AUTO_TEST_CASE(test_time_series_miss_time_slot) {
    cout << "ACore:: ...test_time_series_miss_time_slot\n";

    Calendar calendar;
    calendar.init(ptime(date(2008, 10, 8), hours(10)), Calendar::REAL);

    // Create a test when we can match a time series.
    // NOTE: Finish minute is not a multiple of INCREMENT hence last valid time slot is 23:50
    //
    // Create the time series: start  10:00
    //                         finish 23:59
    //                         incr   00:10
    TimeSeries timeSeries(TimeSlot(10, 0), TimeSlot(23, 59), TimeSlot(0, 10), false /* relative */);
    timeSeries.miss_next_time_slot(); // will increment next_time_slot
    BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 10), "miss time slot not working ");

    timeSeries.miss_next_time_slot(); // will increment next_time_slot
    BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 20), "miss time slot not working ");

    // requeue time series,
    timeSeries.requeue(calendar); // calendar time is at midnight 10:00
    BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 10), "Expected requeue to reset time series");
}

BOOST_AUTO_TEST_CASE(test_time_series_miss_time_slot_1) {
    cout << "ACore:: ...test_time_series_miss_time_slot_1\n";

    // Create calendar before time series
    {
        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(9)), Calendar::REAL); // 09:00

        TimeSeries timeSeries(10, 0, false /* relative */); // 10:00

        TimeSlot t1_min, t1_max;
        timeSeries.min_max_time_slots(t1_min, t1_max);
        BOOST_CHECK_MESSAGE(t1_min == TimeSlot(10, 0) && t1_max == TimeSlot(10, 0), "Not as expected");

        BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), "Expected time holding at 10:00 since calendar is 09:00");
        BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                            "Expected checkForRequeue to return false always");

        timeSeries.miss_next_time_slot();
        BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), "Expected time to hold");
        BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                            "Expected checkForRequeue to return false");

        timeSeries.requeue(calendar);
        BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), "Expected time holding at 10:00 since calendar is 09:00");
        BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                            "Expected checkForRequeue to return false after Requeue");
    }
    {
        // Create a time after the time series
        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(11)), Calendar::REAL); // 11:00

        TimeSeries timeSeries(10, 0, false /* relative */); // 10:00

        TimeSlot t1_min, t1_max;
        timeSeries.min_max_time_slots(t1_min, t1_max);
        BOOST_CHECK_MESSAGE(t1_min == TimeSlot(10, 0) && t1_max == TimeSlot(10, 0), "Not as expected");

        BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar),
                            "Expected time NOT to be free, since calendar time is after time slot");
        BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                            "Expected checkForRequeue to return false, since calendar time > slot time");

        timeSeries.miss_next_time_slot();
        BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar), "Expected time to hold");
        BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                            "Expected checkForRequeue to return false");

        timeSeries.requeue(calendar);
        BOOST_CHECK_MESSAGE(!timeSeries.isFree(calendar),
                            "Expected time to hold, since calendar time is after time slot");
        BOOST_CHECK_MESSAGE(!timeSeries.checkForRequeue(calendar, t1_min, t1_max),
                            "Expected checkForRequeue to return false,since calendar time is after time slot");
    }
}

BOOST_AUTO_TEST_CASE(test_time_series_reset) {
    cout << "ACore:: ...test_time_series_reset\n";

    // Create a test when we can match a time series.
    // NOTE: Finish minute is not a multiple of INCREMENT hence last valid time slot is 23:50
    //
    // Create the time series: start  10:00
    //                         finish 23:59
    //                         incr   00:10
    TimeSeries timeSeries(TimeSlot(10, 0), TimeSlot(23, 59), TimeSlot(0, 10), false /* relative */);
    BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 0), "defaults not correct");

    { // set calendar before time series start & then reset

        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(9)), Calendar::REAL);
        timeSeries.reset(calendar);
        BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 0),
                            "Reset should set value(next_valid_time_slot) to start." << timeSeries.dump());

        timeSeries.miss_next_time_slot();

        timeSeries.requeue(calendar);
        BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 0),
                            "requeue should set value(next_valid_time_slot) to start." << timeSeries.dump());
    }
    { // set calendar at time series start & then reset

        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(10)), Calendar::REAL);
        timeSeries.reset(calendar);
        BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 0),
                            "Reset should set value(next_valid_time_slot) to start." << timeSeries.dump());

        timeSeries.miss_next_time_slot();

        timeSeries.requeue(calendar);
        BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(10, 10),
                            "requeue should set value(next_valid_time_slot) to start." << timeSeries.dump());
    }
    { // set calendar after time series start & before time series end, then reset

        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(11)), Calendar::REAL);
        timeSeries.reset(calendar);
        BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(11, 0),
                            "Reset should update free slot." << timeSeries.dump());

        timeSeries.miss_next_time_slot();

        timeSeries.requeue(calendar);
        BOOST_CHECK_MESSAGE(timeSeries.value() == TimeSlot(11, 10),
                            "requeue should update to first time slot." << timeSeries.dump());
    }
}

BOOST_AUTO_TEST_CASE(test_time_series_parsing) {
    cout << "ACore:: ...test_time_series_parsing\n";

    BOOST_CHECK_MESSAGE(TimeSeries::create("00:30") == ecf::TimeSeries(0, 30), "Error ");
    BOOST_CHECK_MESSAGE(TimeSeries::create("+00:30") == ecf::TimeSeries(0, 30, true), "Error ");

    ecf::TimeSlot start(0, 30);
    ecf::TimeSlot finish(21, 03);
    ecf::TimeSlot incr(1, 30);
    BOOST_CHECK_MESSAGE(TimeSeries::create("00:30 21:03 01:30") == ecf::TimeSeries(start, finish, incr), "Error");
    BOOST_CHECK_MESSAGE(TimeSeries::create("+00:30 21:03 01:30") == ecf::TimeSeries(start, finish, incr, true),
                        "Error");
}

BOOST_AUTO_TEST_CASE(test_time_series_state_parsing) {
    cout << "ACore:: ...test_time_series_state_parsing\n";

    /// extract string like
    ///     time +00:00 20:00 00:10 # this is a comment which will be ignored. index = 1
    ///     time +20:00        // index = 1
    ///     today 20:00        // index = 1
    ///     +00:00 20:00 00:10 // index = 0
    ///     +20:00             // index = 0
    /// will throw std:runtime_error for errors
    /// will assert if index >= lineTokens.size()
    {
        size_t index         = 0;
        std::string the_time = "+00:00 20:00 00:10";
        std::vector<std::string> lineTokens;
        Str::split(the_time, lineTokens);
        BOOST_CHECK_MESSAGE(TimeSeries::create(index, lineTokens) ==
                                ecf::TimeSeries(TimeSlot(0, 0), TimeSlot(20, 0), TimeSlot(0, 10), true),
                            "Error");
    }
    {
        size_t index         = 0;
        std::string the_time = "+10:10";
        std::vector<std::string> lineTokens;
        Str::split(the_time, lineTokens);
        BOOST_CHECK_MESSAGE(TimeSeries::create(index, lineTokens) == ecf::TimeSeries(TimeSlot(10, 10), true), "Error");
    }
    {
        size_t index         = 0;
        std::string the_time = "+10:10 # free isValid:false";
        ecf::TimeSeries expected(TimeSlot(10, 10), true);
        expected.set_isValid(false);

        std::vector<std::string> lineTokens;
        Str::split(the_time, lineTokens);
        ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true);
        BOOST_CHECK_MESSAGE(parsed_ts == expected,
                            "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'"
                                           << " But found \n'" << parsed_ts.toString()
                                           << parsed_ts.state_to_string(false) << "'");
    }
    {
        size_t index         = 0;
        std::string the_time = "+10:10 # free isValid:false nextTimeSlot/10:10 relativeDuration/00:00:00";
        ecf::TimeSeries expected(TimeSlot(10, 10), true);
        expected.set_isValid(false);

        std::vector<std::string> lineTokens;
        Str::split(the_time, lineTokens);
        ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true);
        BOOST_CHECK_MESSAGE(parsed_ts == expected,
                            "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'"
                                           << " But found\n'" << parsed_ts.toString()
                                           << parsed_ts.state_to_string(false) << "'");
    }
    {
        // Update relative duration
        size_t index         = 0;
        std::string the_time = "+10:10 # free isValid:false nextTimeSlot/10:10 relativeDuration/01:00:00";
        ecf::TimeSeries expected(TimeSlot(10, 10), true);
        expected.set_isValid(false);

        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(0)), Calendar::REAL);
        calendar.update(time_duration(hours(1)));
        expected.calendarChanged(calendar);

        std::vector<std::string> lineTokens;
        Str::split(the_time, lineTokens);
        ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true);
        BOOST_CHECK_MESSAGE(parsed_ts == expected,
                            "Expected \n'" << expected.toString() << expected.state_to_string(false) << "'"
                                           << " But found\n'" << parsed_ts.toString()
                                           << parsed_ts.state_to_string(false) << "'");
    }

    {
        // Update nextTimeSlot, create calendar at 09:00 and increment time series.
        std::string the_time = "09:00 12:00 01:00 # isValid:false nextTimeSlot/10:00 relativeDuration/00:00:00";
        ecf::TimeSeries expected(TimeSlot(9, 0), TimeSlot(12, 0), TimeSlot(1, 0), false);
        expected.set_isValid(false);

        Calendar calendar;
        calendar.init(ptime(date(2008, 10, 8), hours(9)), Calendar::REAL);
        expected.requeue(calendar); // this will reset isValid to true
        expected.set_isValid(false);

        std::vector<std::string> lineTokens;
        Str::split(the_time, lineTokens);
        size_t index              = 0;
        ecf::TimeSeries parsed_ts = TimeSeries::create(index, lineTokens, true);

        BOOST_CHECK_MESSAGE(parsed_ts == expected,
                            "Expected \n'" << expected.dump() << expected.state_to_string(false) << "'"
                                           << " But found\n'" << parsed_ts.dump() << parsed_ts.state_to_string(false)
                                           << "'");
    }
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
