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

#include <boost/test/unit_test.hpp>

#include "InvokeServer.hpp"
#include "SCPort.hpp"
#include "ecflow/client/ClientInvoker.hpp"
#include "ecflow/core/File.hpp"
#include "ecflow/node/Submittable.hpp"
#include "ecflow/node/Suite.hpp"

using namespace std;
using namespace ecf;

BOOST_AUTO_TEST_SUITE(S_Client)

BOOST_AUTO_TEST_SUITE(T_GroupCmd)

BOOST_AUTO_TEST_CASE(test_group_cmd) {
    // The previous test's has created/destroyed a server process
    // If two different process both try to use the same port number, you
    // get an "Address in use" error, even if one the process is dead. This is
    // because the kernel does not immediately release the resource and there is
    // time out period. To get round this we will start the new server/client
    // and use a different port number. the default is 3141

    // This will remove check pt and backup file before server start, to avoid the server from loading previous test
    // data
    InvokeServer invokeServer("Client:: ...test_group_cmd", SCPort::next());
    BOOST_REQUIRE_MESSAGE(invokeServer.server_started(),
                          "Server failed to start on " << invokeServer.host() << ":" << invokeServer.port());

    ClientInvoker theClient(invokeServer.host(), invokeServer.port());
    BOOST_REQUIRE_MESSAGE(theClient.group("shutdown yes;halt yes;restart") == 0,
                          "--group should return 0\n"
                              << theClient.errorMsg());
}

BOOST_AUTO_TEST_CASE(test_client_group_lifecyle) {
    /// *** This test is the same as in file TestServerAndLifeCyle.cpp only this time
    /// *** we use the group command where ever possible
    // This will remove check pt and backup file before server start, to avoid the server from loading previous test
    // data

    // ** NOTE: We disable job generation in the server **/
    InvokeServer invokeServer(
        "Client:: ...test_client_group_lifecyle", SCPort::next(), true /*disable job generation in server*/);
    BOOST_REQUIRE_MESSAGE(invokeServer.server_started(),
                          "Server failed to start on " << invokeServer.host() << ":" << invokeServer.port());

    ClientInvoker theClient(invokeServer.host(), invokeServer.port());

    size_t expected_var = 0;
    {
        // restart server, load lifecycle, and get the defs tree from the server
        std::string groupRequest = "--restart; load=";
        groupRequest += File::test_data("libs/client/test/data/lifecycle.txt", "libs/client");
        groupRequest += "; get";

        BOOST_REQUIRE_MESSAGE(theClient.group(groupRequest) == 0,
                              "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n"
                                               << theClient.errorMsg());
        defs_ptr serverDefs = theClient.defs();
        BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs");
        BOOST_REQUIRE_MESSAGE(serverDefs->suiteVec().size() >= 1, "  no suite ?");
        const std::vector<suite_ptr>& suites   = serverDefs->suiteVec();
        const std::vector<Variable>& variables = suites[0]->variables();
        expected_var                           = variables.size();
    }

    {
        // add some var's with spaces ECFLOW-1265
        expected_var += 1;
        std::string groupRequest = "alter add variable VAR 'space s' /suite1; get";

        BOOST_REQUIRE_MESSAGE(theClient.group(groupRequest) == 0,
                              "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n"
                                               << theClient.errorMsg());
        defs_ptr serverDefs = theClient.defs();
        BOOST_REQUIRE_MESSAGE(serverDefs.get(), "Server returned a NULL defs");
        const std::vector<suite_ptr>& suites = serverDefs->suiteVec();
        BOOST_REQUIRE_MESSAGE(suites.size() >= 1, "  no suite ?");
        const std::vector<Variable>& variables = suites[0]->variables();
        BOOST_REQUIRE_MESSAGE(variables.size() == expected_var,
                              "Expected " << expected_var << " variables but found " << variables.size());
        BOOST_REQUIRE_MESSAGE(variables[expected_var - 1].theValue() == "'space s'",
                              "Expected 'space s' but found " << variables[expected_var - 1].theValue());
    }

    // Now go through and simulate client request to change Node tree state.
    // This is **highly** dependent on lifecycle.txt
    //	suite suite1
    //	  family family1
    //	   	task a
    //	        event 1 myEvent
    //	        meter myMeter 0 100
    //	   	task b
    //	   		trigger a == complete
    //	   endfamily
    //	   family family2
    //	   		task aa
    //	   			trigger ../family1/a:myMeter >= 20 and ../family1/a:myEvent
    //	   		task bb
    //	   			trigger ../family1/a:myMeter >= 50 || ../family1/a:myEvent
    //	    endfamily
    //	endsuite
    string suite1_family1_a  = "suite1/family1/a";
    string suite1_family1_b  = "suite1/family1/b";
    string suite1_family2_aa = "suite1/family2/aa";
    string suite1_family2_bb = "suite1/family2/bb";

    // ***********************************************************************
    // Create a request to initialise Node: suite1/family1/a
    // Create a request to set the event on Node suite1/family1/a
    // This should place suite1_family2_bb immediately into submitted state
    // Since we at least one node in ACTIVE, suite should be in ACTIVE state
    {
        theClient.taskPath(suite1_family1_a);
        theClient.set_jobs_password(Submittable::DUMMY_JOBS_PASSWORD());

        // make sure to use same ECF_RID if its specified
        std::string remote_id = "process_or_remote_id";
        if (!theClient.process_or_remote_id().empty())
            remote_id = theClient.process_or_remote_id();
        std::string groupRequest = "begin=suite1;  init=";
        groupRequest += remote_id;
        groupRequest += "; event=myEvent; force-dep-eval; get";

        BOOST_REQUIRE_MESSAGE(theClient.group(groupRequest) == 0,
                              "Group request " << groupRequest << " failed should return 0\n"
                                               << theClient.errorMsg());

        defs_ptr serverDefs = theClient.defs();
        // PrintStyle style(PrintStyle::STATE);
        // cerr << *serverDefs.get() << "\n";
        BOOST_REQUIRE_MESSAGE(serverDefs.get(), "get command failed to get node tree from server");

        node_ptr node = serverDefs->findAbsNode(suite1_family1_a);
        BOOST_REQUIRE_MESSAGE(node->state() == NState::ACTIVE,
                              "Node expected NState::ACTIVE, but found to be " << NState::toString(node->state()));
        node_ptr nodeb = serverDefs->findAbsNode(suite1_family2_bb);
        BOOST_REQUIRE_MESSAGE(nodeb->state() == NState::ACTIVE,
                              "Node expected NState::ACTIVE, but found to be " << NState::toString(nodeb->state()));
    }

    //**********************************************************************
    // Create a request to set the Meter on Node suite1/family1/a
    // This should force suite1_family2_aa immediately into submitted state
    // This should force suite1_family2_bb immediately into submitted state
    {
        theClient.taskPath(suite1_family1_a);
        std::string groupRequest = "meter=myMeter 100; force-dep-eval; get";
        BOOST_REQUIRE_MESSAGE(theClient.group(groupRequest) == 0,
                              "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n"
                                               << theClient.errorMsg());

        defs_ptr serverDefs = theClient.defs();
        BOOST_REQUIRE_MESSAGE(serverDefs.get(), "get command failed to get node tree from server");

        node_ptr nodeaa = serverDefs->findAbsNode(suite1_family2_aa);
        BOOST_REQUIRE_MESSAGE(nodeaa->state() == NState::ACTIVE,
                              "Node expected NState::ACTIVE, but found to be " << NState::toString(nodeaa->state()));

        node_ptr nodebb = serverDefs->findAbsNode(suite1_family2_bb);
        BOOST_REQUIRE_MESSAGE(nodebb->state() == NState::ACTIVE,
                              "Node expected NState::ACTIVE, but found to be " << NState::toString(nodebb->state()));
    }

    //**********************************************************************
    // Create a request to complete task suite1/family1/a
    // This should force suite1_family1_b to complete
    // 	Resolve dependencies
    // 	We could either wait 60 second or
    // 	added a custom command that will force dependency evaluation
    {
        theClient.taskPath(suite1_family1_a);
        std::string groupRequest = "complete;force-dep-eval;get";
        BOOST_REQUIRE_MESSAGE(theClient.group(groupRequest) == 0,
                              "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n"
                                               << theClient.errorMsg());

        defs_ptr serverDefs = theClient.defs();
        BOOST_REQUIRE_MESSAGE(serverDefs.get(), "get command failed to get node tree from server");

        node_ptr node = serverDefs->findAbsNode(suite1_family1_b);
        BOOST_REQUIRE_MESSAGE(node->state() == NState::ACTIVE,
                              "Expected NState::ACTIVE, but found " << NState::toString(node->state()));
    }

    //********************************************************************************
    // Complete the remaining tasks. Should really call init first, but what the eck.
    //
    // Get the node tree back from the server, and check its node state
    // All node state should be complete. + check meter/event value was set properly

    {
        theClient.taskPath(suite1_family1_b);
        BOOST_REQUIRE_MESSAGE(theClient.completeTask() == 0,
                              TaskApi::complete() << " failed should return 0\n"
                                                  << theClient.errorMsg());

        theClient.taskPath(suite1_family2_aa);
        BOOST_REQUIRE_MESSAGE(theClient.completeTask() == 0,
                              TaskApi::complete() << " failed should return 0\n"
                                                  << theClient.errorMsg());

        theClient.taskPath(suite1_family2_bb);
        std::string groupRequest = "complete;force-dep-eval;get";
        BOOST_REQUIRE_MESSAGE(theClient.group(groupRequest) == 0,
                              "Group request " << CtsApi::group(groupRequest) << " failed should return 0\n"
                                               << theClient.errorMsg());

        defs_ptr serverDefs = theClient.defs();
        BOOST_REQUIRE_MESSAGE(serverDefs.get(), "get command failed to get node tree from server");

        std::string metername = "myMeter";
        int meterValue        = 100;
        node_ptr node         = serverDefs->findAbsNode(suite1_family1_a);
        const Meter& theMeter = node->findMeter(metername);
        BOOST_REQUIRE_MESSAGE(!theMeter.empty(), "Could not find the meter");
        BOOST_REQUIRE_MESSAGE(theMeter.value() == meterValue,
                              "Expected meter value " << meterValue << " but found " << theMeter.value());
        {
            std::string errorMsg;
            BOOST_REQUIRE_MESSAGE(serverDefs->checkInvariants(errorMsg), errorMsg);
        }

        std::string eventname = "myEvent";
        const Event& theEvent = node->findEventByNameOrNumber(eventname);
        BOOST_REQUIRE_MESSAGE(!theEvent.empty(), "Could not find the event myEvent");
        BOOST_REQUIRE_MESSAGE(theEvent.value(), "The event was not set");

        const std::vector<suite_ptr>& suiteVec = serverDefs->suiteVec();
        suite_ptr suite                        = suiteVec.back();
        BOOST_REQUIRE_MESSAGE(suite->state() == NState::COMPLETE,
                              "Suite expected NState::COMPLETE, but found to be " << NState::toString(suite->state()));
    }

    {
        // Check that a log file was created, by asking the server for it.
        BOOST_REQUIRE_MESSAGE(theClient.getLog() == 0, "get log  failed should return 0\n" << theClient.errorMsg());
        BOOST_REQUIRE_MESSAGE(!theClient.get_string().empty(), "The log file returned from the server is empty!!");

        // Clear the log
        BOOST_REQUIRE_MESSAGE(theClient.clearLog() == 0,
                              CtsApi::clearLog() << " failed should return 0\n"
                                                 << theClient.errorMsg());
    }
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
