/*
 * 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 <limits> // for std::numeric_limits<int>::max()

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

#include "ServerTestHarness.hpp"
#include "TestFixture.hpp"
#include "ecflow/attribute/VerifyAttr.hpp"
#include "ecflow/base/cts/ClientToServerCmd.hpp"
#include "ecflow/core/AssertTimer.hpp"
#include "ecflow/core/DurationTimer.hpp"
#include "ecflow/core/PrintStyle.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Family.hpp"
#include "ecflow/node/Suite.hpp"
#include "ecflow/node/Task.hpp"

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

///
/// \note This is used to INVOKE a SINGLE test.
/// Making it easier for Easier for debugging and development
///

BOOST_AUTO_TEST_SUITE(S_Test)

BOOST_AUTO_TEST_SUITE(T_OrderCmd)

std::vector<std::string> to_string_vec(const std::vector<suite_ptr>& sv) {
    std::vector<std::string> r;
    r.reserve(sv.size());
    for (size_t i = 0; i < sv.size(); i++)
        r.push_back(sv[i]->name());
    return r;
}

std::vector<std::string> to_string_vec(const std::vector<node_ptr>& sv) {
    std::vector<std::string> r;
    r.reserve(sv.size());
    for (size_t i = 0; i < sv.size(); i++)
        r.push_back(sv[i]->name());
    return r;
}

void test_ordering() {

    TestFixture::client().set_throw_on_error(true);
    std::vector<std::string> str_a_b_c;
    str_a_b_c.push_back("a");
    str_a_b_c.push_back("b");
    str_a_b_c.push_back("c");
    std::vector<std::string> str_c_b_a;
    str_c_b_a.push_back("c");
    str_c_b_a.push_back("b");
    str_c_b_a.push_back("a");
    std::vector<std::string> str_b_c_a;
    str_b_c_a.push_back("b");
    str_b_c_a.push_back("c");
    str_b_c_a.push_back("a");
    std::vector<std::string> str_b_a_c;
    str_b_a_c.push_back("b");
    str_b_a_c.push_back("a");
    str_b_a_c.push_back("c");
    std::vector<std::string> str_a_c_b;
    str_a_c_b.push_back("a");
    str_a_c_b.push_back("c");
    str_a_c_b.push_back("b");
    TestFixture::client().sync_local(); // First sync_local will do a full sync
    {
        // TEST SUITE ORDERING
        TestFixture::client().order("/a", NOrder::toString(NOrder::ORDER));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_c_b_a,
                            "Order not as expected");

        TestFixture::client().order("/a", NOrder::toString(NOrder::ALPHA));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_a_b_c,
                            "Order not as expected");

        TestFixture::client().order("/a", NOrder::toString(NOrder::BOTTOM));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_b_c_a,
                            "Order not as expected");

        TestFixture::client().order("/a", NOrder::toString(NOrder::ALPHA));
        TestFixture::client().order("/a", NOrder::toString(NOrder::DOWN));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_b_a_c,
                            "Order not as expected");

        TestFixture::client().order("/a", NOrder::toString(NOrder::ALPHA));
        TestFixture::client().order("/c", NOrder::toString(NOrder::UP));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()) == str_a_c_b,
                            "Order not as expected");
    }
    {
        // TEST FAMILY ordering
        TestFixture::client().order("/a/a", NOrder::toString(NOrder::ORDER));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_c_b_a,
                            "Order not as expected");

        TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_a_b_c,
                            "Order not as expected");

        TestFixture::client().order("/a/a", NOrder::toString(NOrder::BOTTOM));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_b_c_a,
                            "Order not as expected");

        TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA));
        TestFixture::client().order("/a/a", NOrder::toString(NOrder::DOWN));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_b_a_c,
                            "Order not as expected");

        TestFixture::client().order("/a/a", NOrder::toString(NOrder::ALPHA));
        TestFixture::client().order("/a/c", NOrder::toString(NOrder::UP));
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental sync");
        BOOST_CHECK_MESSAGE(to_string_vec(TestFixture::client().defs()->suiteVec()[0]->nodeVec()) == str_a_c_b,
                            "Order not as expected");
    }
}

BOOST_AUTO_TEST_CASE(test_change_order) {
    DurationTimer timer;
    cout << "Test:: ...test_change_order " << flush;
    TestClean clean_at_start_and_end;

    /// NOT: We *DONT* need to run the jobs for this TEST
    std::vector<std::string> str_a_b_c;
    str_a_b_c.push_back("a");
    str_a_b_c.push_back("b");
    str_a_b_c.push_back("c");
    Defs theDefs;
    {
        for (size_t s = 0; s < str_a_b_c.size(); s++) {
            suite_ptr suite = theDefs.add_suite(str_a_b_c[s]);
            suite->addDefStatus(DState::SUSPENDED); // NO NEED to run jobs for this test:
            for (size_t f = 0; f < str_a_b_c.size(); f++) {
                family_ptr fam = suite->add_family(str_a_b_c[f]);
                for (size_t t = 0; t < str_a_b_c.size(); t++) {
                    fam->add_task(str_a_b_c[t]);
                }
            }
        }
    }

    // The test harness will create corresponding directory structure & default ecf file
    ServerTestHarness serverTestHarness;
    serverTestHarness.run(theDefs,
                          ServerTestHarness::testDataDefsLocation("test_change_order.def"),
                          1 /*timeout*/,
                          false /* don't wait for test to finish */);

    test_ordering();

    cout << timer.duration() << " update-calendar-count(" << serverTestHarness.serverUpdateCalendarCount() << ")\n";
}

BOOST_AUTO_TEST_CASE(test_handle_change_order) {
    DurationTimer timer;
    cout << "Test:: ...test_handle_change_order " << flush;
    TestClean clean_at_start_and_end;

    std::vector<std::string> str_a_b_c;
    str_a_b_c.push_back("a");
    str_a_b_c.push_back("b");
    str_a_b_c.push_back("c");
    std::vector<std::string> suite_a_b_c_d_e;
    suite_a_b_c_d_e.push_back("a");
    suite_a_b_c_d_e.push_back("b");
    suite_a_b_c_d_e.push_back("c");
    suite_a_b_c_d_e.push_back("d");
    suite_a_b_c_d_e.push_back("e");
    Defs theDefs;
    {
        for (size_t s = 0; s < suite_a_b_c_d_e.size(); s++) {
            suite_ptr suite = theDefs.add_suite(suite_a_b_c_d_e[s]);
            suite->addDefStatus(DState::SUSPENDED); // NO NEED to run jobs for this test:
            for (size_t f = 0; f < str_a_b_c.size(); f++) {
                family_ptr fam = suite->add_family(str_a_b_c[f]);
                for (size_t t = 0; t < str_a_b_c.size(); t++) {
                    fam->add_task(str_a_b_c[t]);
                }
            }
        }
    }

    // The test harness will create corresponding directory structure & default ecf file
    ServerTestHarness serverTestHarness;
    serverTestHarness.run(theDefs,
                          ServerTestHarness::testDataDefsLocation("test_change_order.def"),
                          1 /*timeout*/,
                          false /* don't wait for test to finish */);

    TestFixture::client().set_throw_on_error(true);
    TestFixture::client().sync_local(); // First sync_local will do a full sync
    {
        // register suites a,b,c
        std::vector<std::string> suites_a_b_c;
        suites_a_b_c.push_back("a");
        suites_a_b_c.push_back("b");
        suites_a_b_c.push_back("c");
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_a_b_c);

        // Check the sync_local() does a full sync for our handle
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().in_sync(), "Expected to be in sync after syn_local()");
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() after registering");
        BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 3, "Expected sync to return 3 suites.");
    }

    // Do same test, this time sync_local will only return suite a,b,c (i.e suite d,e not registered and hence left out)
    // Ording should still be applied to the whole suite.
    test_ordering();

    cout << timer.duration() << " update-calendar-count(" << serverTestHarness.serverUpdateCalendarCount() << ")\n";
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
