/*
 * 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/test/unit_test.hpp>

#include "TestHelper.hpp"
#include "ecflow/base/cts/task/LabelCmd.hpp"
#include "ecflow/base/cts/user/PathsCmd.hpp"
#include "ecflow/base/cts/user/QueryCmd.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Family.hpp"
#include "ecflow/node/Limit.hpp"
#include "ecflow/node/Suite.hpp"
#include "ecflow/node/System.hpp"
#include "ecflow/node/Task.hpp"

using namespace std;
using namespace ecf;

BOOST_AUTO_TEST_SUITE(U_Base)

BOOST_AUTO_TEST_SUITE(T_QueryCmd)

BOOST_AUTO_TEST_CASE(test_query_cmd) {
    cout << "Base:: ...test_query_cmd\n";
    TestLog test_log("test_query_cmd.log"); // will create log file, and destroy log and remove file at end of scope

    // Create the defs file.
    // suite suite
    //    limit limit_x 12
    //    repeat date YMD 20090916 20200916
    //    family f
    //          edit var2 var2
    //          task t1
    //              edit var1 var1
    //              meter m 0 100 100
    //              event event
    //              trigger t2 == complete
    //          task t2
    //              label name value new_value
    //              label name2 value2
    //    endfamily
    //    task task
    // endsuite
    Defs defs;
    string suite_f_t1 = "suite/f/t1";
    task_ptr t1       = Task::create("t1");
    task_ptr t2;
    task_ptr task          = Task::create("task");
    string suite_task      = "suite/task";
    std::string meter_name = "m";
    std::string event_name = "event";
    std::string label_name = "name";
    std::string limit_name = "limit_x";
    {
        t1->addMeter(Meter(meter_name, 0, 100, 100));
        t1->addEvent(Event(event_name));
        t1->add_variable("var1", "var1");

        suite_ptr s = defs.add_suite("suite");
        s->addLimit(Limit(limit_name, 12));

        s->addRepeat(RepeatDate("YMD", 20090916, 20200916, 1));
        family_ptr f = s->add_family("f");
        f->add_variable("var2", "var2");
        f->addTask(t1);
        t2 = f->add_task("t2");
        t2->add_label(label_name, "value", "");
        s->addTask(task);
    }

    defs.beginAll(); // this will clear label new_value

    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("state", "/suite/f/t11", "", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("dstate", "/suite/f/t11", "", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(
        &defs, Cmd_ptr(new QueryCmd("repeat", "/suite/f/t1", "", "/suite/f/t1"))); // no repeat on /suite/f/t1
    TestHelper::invokeFailureRequest(
        &defs, Cmd_ptr(new QueryCmd("repeat", "/suite", "fred", "/suite"))); // Only next | prev are valid
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("event", "/suite/f/t1", "eventxx", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("event", "/suite", event_name, "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("event", "xxxx/f/t1", event_name, "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("limit", "xxxx/f/t1", limit_name, "/suite")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("limit_max", "xxxx/f/t1", limit_name, "/suite")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("meter", "/suite/f/t1", "meterxx", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("meter", "/suite", meter_name, "/suite/f/t1")));
    TestHelper::invokeFailureRequest(
        &defs, Cmd_ptr(new QueryCmd("label", "/suite/f/t1", label_name, "/suite/f/t1"))); // no label on t1
    TestHelper::invokeFailureRequest(
        &defs, Cmd_ptr(new QueryCmd("label", "/suite/f/t2", "fred", "/suite/f/t2"))); // wrong label name
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("trigger", "/suite", "1 == ", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "1 == ", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("variable", "/suite/f/t1", "XXXX", "/suite/f/t1")));
    TestHelper::invokeFailureRequest(&defs, Cmd_ptr(new QueryCmd("xxxxx", "/suite/f/t1", event_name, "/suite/f/t1")));

    // t3 does not exist
    TestHelper::invokeFailureRequest(&defs,
                                     Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "t3 == complete", "/suite/f/t1")));

    // QueryCmd is read only, hence change numbers should not change
    std::string res;
    res = TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("state", "/suite", "", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "queued", "expected query state to return queued but found: " << res);

    res = TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("dstate", "/suite/f", "", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "queued", "expected query state to return queued but found: " << res);

    res = TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("repeat", "/suite", "", "/suite")), false);
    BOOST_CHECK_MESSAGE(res == "20090916", "expected query repeat to return '20090916' but found : " << res);

    res = TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("repeat", "/suite", "prev", "/suite")), false);
    BOOST_CHECK_MESSAGE(res == "20090916", "expected query repeat to return '20090916' but found : " << res);

    res = TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("repeat", "/suite", "next", "/suite")), false);
    BOOST_CHECK_MESSAGE(res == "20090917", "expected query repeat to return '20090917' but found : " << res);

    // Note: we pick a task outside of a repeat, since setting a task to complete, inside a repeat will cause it to
    // requeue Avoid using ForceCmd to avoid side affects
    std::vector<std::string> states = NState::allStates();
    for (const auto& state : states) {
        task->setStateOnly(NState::toState(state));
        res = TestHelper::invokeRequest(
            &defs, Cmd_ptr(new QueryCmd("state", task->absNodePath(), "", task->absNodePath())), false);
        BOOST_CHECK_MESSAGE(res == state, "expected query state to return " << state << " but found: " << res);
    }

    TestHelper::invokeRequest(&defs, Cmd_ptr(new PathsCmd(PathsCmd::SUSPEND, task->absNodePath())));
    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("dstate", task->absNodePath(), "", task->absNodePath())), false);
    BOOST_CHECK_MESSAGE(res == "suspended", "expected query state to return suspend but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("event", "/suite/f/t1", event_name, "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "clear", "expected query event to return clear but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("meter", "/suite/f/t1", meter_name, "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "0", "expected query meter to return 0 but found: " << res);

    res = TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("limit", "/suite", limit_name, "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "0", "expected query limit to return 0 but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("limit_max", "/suite", limit_name, "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "12", "expected query limit_max to return 12 but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("label", "/suite/f/t2", label_name, "/suite/f/t2")), false);
    BOOST_CHECK_MESSAGE(res == "value", "expected query label to return 'value' but found: " << res);
    res = TestHelper::invokeRequest(&defs,
                                    Cmd_ptr(new LabelCmd("/suite/f/t2",
                                                         Submittable::DUMMY_JOBS_PASSWORD(),
                                                         Submittable::DUMMY_PROCESS_OR_REMOTE_ID(),
                                                         1,
                                                         label_name,
                                                         "new_value")));
    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("label", "/suite/f/t2", label_name, "/suite/f/t2")), false);
    BOOST_CHECK_MESSAGE(res == "new_value", "expected query label to return 'new_value' but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "t2 == complete", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "false", "expected query trigger to return false but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "1 == 1", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "true", "expected query trigger to return true but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "/suite/f/t1:var1 == 0", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "true", "expected query trigger to return true but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "/suite/f:var2 == 0", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "true", "expected query trigger to return true but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "/suite/f/t1:m == 0", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "true", "expected query trigger to return true but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite/f/t1", "/suite/f/t1:event", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "false", "expected query trigger to return false but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("trigger", "/suite", "/suite:YMD == 20090916", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "true", "expected query trigger to return true but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("variable", "/suite/f/t1", "var1", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "var1", "expected query variable to return var2 but found: " << res);

    res = TestHelper::invokeRequest(
        &defs, Cmd_ptr(new QueryCmd("variable", "/suite/f/t1", "var2", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "var2", "expected query variable to return var2 but found: " << res);

    res =
        TestHelper::invokeRequest(&defs, Cmd_ptr(new QueryCmd("variable", "/suite/f/t1", "YMD", "/suite/f/t1")), false);
    BOOST_CHECK_MESSAGE(res == "20090916", "expected query variable to return 20090916 but found: " << res);

    /// Destroy System singleton to avoid valgrind from complaining
    System::destroy();
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()
