/*
 * 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/Converter.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_Handle)

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

    Defs theDefs;
    {
        for (int s = 0; s < 7; s++) {
            suite_ptr suite = theDefs.add_suite("s" + ecf::convert_to<std::string>(s));
            suite->addDefStatus(DState::SUSPENDED); // NO NEED to run jobs for this test:
            for (int t = 0; t < 2; t++) {
                suite->add_task("t" + ecf::convert_to<std::string>(t));
            }
        }
    }

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

    std::vector<std::string> suites_s0_s1_s2;
    suites_s0_s1_s2.push_back("s0");
    suites_s0_s1_s2.push_back("s1");
    suites_s0_s1_s2.push_back("s2");
    std::vector<std::string> suites_s3_s4;
    suites_s3_s4.push_back("s3");
    suites_s3_s4.push_back("s4");
    std::vector<std::string> suites_s0_s1_s2_s3_s4;
    suites_s0_s1_s2_s3_s4.push_back("s0");
    suites_s0_s1_s2_s3_s4.push_back("s1");
    suites_s0_s1_s2_s3_s4.push_back("s2"), suites_s0_s1_s2_s3_s4.push_back("s3"), suites_s0_s1_s2_s3_s4.push_back("s4");
    ;
    std::vector<std::pair<unsigned int, std::vector<std::string>>> ch_suites;

    {
        // register suites s0,s1,s2
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);

        // get the handle associated with the registered suites
        unsigned int client_handle = TestFixture::client().server_reply().client_handle();

        // 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");

        // Now check the suites were registered, correctly
        TestFixture::client().ch_suites();
        ch_suites = TestFixture::client().server_reply().get_client_handle_suites();
        BOOST_CHECK_MESSAGE(!ch_suites.empty(), "Expected to have registered suites");
        BOOST_CHECK_MESSAGE(ch_suites.size() == 1, "Expected to have registered a single set");
        BOOST_CHECK_MESSAGE(ch_suites[0].first == client_handle,
                            "Expected first client handle to be: " << client_handle << ", but found "
                                                                   << ch_suites[0].first);
        BOOST_CHECK_MESSAGE(ch_suites[0].second == suites_s0_s1_s2, "Expected suites s0,s1,s2");

        // Now drop the handle and Check handle was dropped
        TestFixture::client().ch_drop(client_handle);
        TestFixture::client().ch_suites();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

    {
        // register suites s0,s1,s2
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);

        // get the handle associated with the registered suites
        unsigned int client_handle = TestFixture::client().server_reply().client_handle();
        BOOST_CHECK_MESSAGE(client_handle == 1, "Expected to have handle 1");

        // Check the sync_local() does a full sync for our handle. *THIS* should also clear the handle_changed flag
        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");

        // add additional suites & check suites were added to our handle. Sync_local should returna full_sync()
        std::vector<std::string> added_suites;
        added_suites.push_back("s3");
        added_suites.push_back("s4");
        TestFixture::client().ch_add(client_handle, added_suites);

        TestFixture::client().ch_suites();
        ch_suites = TestFixture::client().server_reply().get_client_handle_suites();
        BOOST_CHECK_MESSAGE(!ch_suites.empty(), "Expected to have registered suites");
        BOOST_CHECK_MESSAGE(ch_suites.size() == 1, "Expected to have registered a single set");
        BOOST_CHECK_MESSAGE(ch_suites[0].first == client_handle,
                            "Expected first client handle to be: " << client_handle << ", but found "
                                                                   << ch_suites[0].first);
        BOOST_CHECK_MESSAGE(ch_suites[0].second == suites_s0_s1_s2_s3_s4, "Expected suites s0,s1,s2,s3,s4");

        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() after adding suites.");

        // remove the added suites, and check they were removed
        TestFixture::client().ch_remove(client_handle, added_suites);

        TestFixture::client().ch_suites();
        ch_suites = TestFixture::client().server_reply().get_client_handle_suites();
        BOOST_CHECK_MESSAGE(!ch_suites.empty(), "Expected to have registered suites");
        BOOST_CHECK_MESSAGE(ch_suites.size() == 1, "Expected to have registered a single set");
        BOOST_CHECK_MESSAGE(ch_suites[0].first == client_handle,
                            "Expected first client handle to be: " << client_handle << ", but found "
                                                                   << ch_suites[0].first);
        BOOST_CHECK_MESSAGE(ch_suites[0].second == suites_s0_s1_s2, "Expected suites s0,s1,s2");

        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() after removing suites");

        // Now drop the handle and check handle was dropped
        TestFixture::client().ch_drop(client_handle);
        TestFixture::client().ch_suites();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

    {
        // register suites s0,s1,s2
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);

        // register suite s3,s4
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s3_s4);

        // get all the registered suites
        TestFixture::client().ch_suites();
        ch_suites = TestFixture::client().server_reply().get_client_handle_suites();
        BOOST_CHECK_MESSAGE(!ch_suites.empty(), "Expected to have registered suites");
        BOOST_CHECK_MESSAGE(ch_suites.size() == 2, "Expected to have 2 registered sets");
        BOOST_CHECK_MESSAGE(ch_suites[0].first == 1,
                            "Expected first client handle to be 1, but found " << ch_suites[0].first);
        BOOST_CHECK_MESSAGE(ch_suites[0].second == suites_s0_s1_s2, "Expected suites s0,s1,s2, in first handle");
        BOOST_CHECK_MESSAGE(ch_suites[1].first == 2,
                            "Expected second client handle to be 2, but found " << ch_suites[1].first);
        BOOST_CHECK_MESSAGE(ch_suites[1].second == suites_s3_s4, "Expected suites s3,s4 , in second handle");

        // Drop all handles
        for (size_t i = 0; i < ch_suites.size(); i++)
            TestFixture::client().ch_drop(ch_suites[i].first);
        TestFixture::client().ch_suites();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

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

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

    Defs theDefs;
    {
        for (int s = 0; s < 7; s++) {
            suite_ptr suite = theDefs.add_suite("s" + ecf::convert_to<std::string>(s));
            suite->addDefStatus(DState::SUSPENDED);
            for (int t = 0; t < 2; t++) {
                suite->add_task("t" + ecf::convert_to<std::string>(t));
            }
        }
    }

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

    std::vector<std::string> suites_s0_s1_s2;
    suites_s0_s1_s2.push_back("s0");
    suites_s0_s1_s2.push_back("s1");
    suites_s0_s1_s2.push_back("s2");
    std::vector<std::pair<unsigned int, std::vector<std::string>>> ch_suites;

    {
        // register suites s0,s1,s2
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);
        unsigned int client_handle = TestFixture::client().server_reply().client_handle();
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().get_news(), "Expected news after registering");
        TestFixture::client().sync_local(); // sync for any changes to get full update before test starts
        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 3 suites back from sync, after registering 3 suites "
                                << *TestFixture::client().defs());

        // make a change to a suite not in our handle, that does not cause state propagation.
        // State propagation changes the defs state. The defs state is sync regardless
        TestFixture::client().suspend("/s3");
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().get_news(),
                            "Expected no change since suite s3 is not in our handle");

        // make a change to a suite *not* in our handle, that *does* cause state propagation.
        TestFixture::client().force("/s3/t0", "aborted");
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().get_news(),
                            "Expected change via state propagation to defs, even though s3 not in our handle");

        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(),
                            "Expected incremental change, not a full update");

        // make a change to a suite in our handle
        TestFixture::client().force("/s0", "complete");
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().get_news(), "Expected news since state changed");

        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(),
                            "Expected incremental change, not a full update");

        TestFixture::client().ch_drop(client_handle);
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

    {
        // register suites s0,s1,s2
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);
        unsigned int client_handle = TestFixture::client().server_reply().client_handle();
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().get_news(), "Expected news after registering");
        TestFixture::client().sync_local(); // sync for any changes to get full update before test starts
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() after registering");

        // make a small change to a suite *IN* our handle
        TestFixture::client().force("/s1", "complete");
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(),
                            "Expected incremental change, not a full update");

        // Change the order
        TestFixture::client().order("/s0", "alpha");
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(), "Expected incremental update");

        TestFixture::client().ch_drop(client_handle);
        TestFixture::client().suites();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

    {
        // register suites s0,s1,s2
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);
        unsigned int client_handle = TestFixture::client().server_reply().client_handle();
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().get_news(), "Expected news after registering");
        TestFixture::client().sync_local(); // sync for any changes to get full update **before** test starts
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() after registering");

        // make a small change to a suite *IN* our handle
        TestFixture::client().force("/s1", "unknown");
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().full_sync(),
                            "Expected incremental change, not a full update");

        // DELETE suite s2, i.e make a change that should force a *FULL* update
        TestFixture::client().delete_node("/s2", true /*force*/);
        TestFixture::client().news_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().get_news(), "Expected news after deleting node");
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(), "Expected full update");

        // Check that suite s2 is STILL in our handle.
        // delete suites STAY registered until they are explicitly deleted
        TestFixture::client().ch_suites();
        ch_suites = TestFixture::client().server_reply().get_client_handle_suites();
        BOOST_CHECK_MESSAGE(ch_suites[0].second == suites_s0_s1_s2, "Expected suites s0,s1,s2, in handle");

        TestFixture::client().ch_drop(client_handle);
        TestFixture::client().ch_suites();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

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

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

    defs_ptr theDefs = Defs::create();
    {
        for (int s = 0; s < 3; s++) {
            suite_ptr suite = theDefs->add_suite("s" + ecf::convert_to<std::string>(s));
            suite->addDefStatus(DState::SUSPENDED); // NO NEED to run jobs for this test:
            for (int t = 0; t < 2; t++) {
                suite->add_task("t" + ecf::convert_to<std::string>(t));
            }
        }
    }

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

    TestFixture::client().set_throw_on_error(true);
    std::vector<std::string> suites_s0_s1_s2;
    suites_s0_s1_s2.push_back("s0");
    suites_s0_s1_s2.push_back("s1");
    suites_s0_s1_s2.push_back("s2");

    {
        // register suites s0
        TestFixture::client().ch_register(false /*add new suites to handle*/, suites_s0_s1_s2);
        unsigned int client_handle = TestFixture::client().server_reply().client_handle();
        BOOST_CHECK_MESSAGE(client_handle == 1, "Expected handle of value 1 but found " << client_handle);

        // Get the registered suites
        TestFixture::client().ch_suites();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have registered suites");

        // 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 3 suites back from sync " << *TestFixture::client().defs());

        // DELETE suites. They should stay *registered*
        TestFixture::client().delete_node("/s0");
        TestFixture::client().delete_node("/s1");
        TestFixture::client().delete_node("/s2");
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() after deleting suite");
        BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 0,
                            "Expected 0 suites back from sync " << *TestFixture::client().defs());

        // Check suites are still registered. Only explicit drop can remove registered suites
        TestFixture::client().ch_suites();
        BOOST_CHECK_MESSAGE(!TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have registered suites");

        // RELOAD the defs, which includes s0,s1,s2:: THIS SHOULD GET RE_ADDED TO OUR EXISTING HANDLE's
        // The handle references to the suites should get *refreshed*
        TestFixture::client().load(theDefs);
        TestFixture::client().sync_local();
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().full_sync(),
                            "Expected a full_sync() since client handle should be refreshed with new suite_pts");
        BOOST_CHECK_MESSAGE(TestFixture::client().defs()->suiteVec().size() == 3,
                            "Expected 3 suites back from sync " << *TestFixture::client().defs());

        TestFixture::client().ch_drop(client_handle);
        BOOST_CHECK_MESSAGE(TestFixture::client().server_reply().get_client_handle_suites().empty(),
                            "Expected to have no registered suites");
    }

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

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
