/*
 * 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 "ecflow/core/PrintStyle.hpp"
#include "ecflow/node/Alias.hpp"
#include "ecflow/node/Task.hpp"
#include "ecflow/node/formatter/DefsWriter.hpp"
#include "ecflow/python/DefsDoc.hpp"
#include "ecflow/python/ExportCollections.hpp"
#include "ecflow/python/NodeUtil.hpp"
#include "ecflow/python/PythonBinding.hpp"
#include "ecflow/python/PythonUtil.hpp"

namespace {

// Task

///
/// @brief Construct a Task with the given name and optional attributes.
///
/// @param name The task name.
/// @param args Positional attributes to add to the task (forwarded to `NodeUtil::add`).
/// @param kwargs Keyword arguments added as variables on the task.
/// @return The newly created Task.
///
std::shared_ptr<Task> Task_init(const std::string& name, const py::args& args, const py::kwargs& kwargs) {
    auto node = std::make_shared<Task>(name);
    NodeUtil::add(*node, args);
    NodeUtil::add(*node, kwargs);
    return node;
}

///
/// @brief Implement the Python context manager entry protocol.
///
/// @param self The task.
/// @return \p self, enabling use in a `with` statement.
///
task_ptr Task_enter(task_ptr self) {
    return self;
}

///
/// @brief Implement the Python context manager exit protocol.
///
/// @return false (exceptions are not suppressed).
///
bool Task_exit(task_ptr self, const py::object& type, const py::object& value, const py::object& traceback) {
    return false;
}

///
/// @brief Return a string representation of the task.
///
/// @param self The task.
/// @return The task serialised using the current print style.
///
std::string Task_str(task_ptr self) {
    return ecf::as_string(*self, PrintStyleHolder::getStyle());
}

///
/// @brief Return the number of aliases owned by the task.
///
/// @param self The task.
/// @return The number of aliases.
///
auto Task_len(task_ptr self) {
    return self->aliases().size();
}

// Alias

///
/// @brief Return a string representation of the alias.
///
/// @param self The alias.
/// @return The alias serialised using the current print style.
///
std::string Alias_str(alias_ptr self) {
    return ecf::as_string(*self, PrintStyleHolder::getStyle());
}

} // namespace

void export_Task(py::module& m) {

    py::class_<Submittable, Node, std::shared_ptr<Submittable>>(
        m, "Submittable", py::dynamic_attr(), DefsDoc::submittable_doc())

        .def("__init__",
             [](py::object& /*self*/, const std::string&) {
                 throw std::runtime_error("Submittable cannot be instantiated directly from Python");
             })
        .def("get_jobs_password",
             &Submittable::jobsPassword,
             py::return_value_policy::reference,
             "The password. This generated by server")
        .def("get_process_or_remote_id",
             &Submittable::process_or_remote_id,
             py::return_value_policy::reference,
             "The process or remote id of the running job")
        .def("get_try_no", &Submittable::tryNo, "The current try number as a string.")
        .def("get_int_try_no", &Submittable::try_no, "The current try number as integer.")
        .def("get_aborted_reason",
             &Submittable::abortedReason,
             py::return_value_policy::reference,
             "If node was aborted and a reason was provided, return the string");

    py::class_<Task, Submittable, std::shared_ptr<Task>>(m, "Task", py::dynamic_attr(), DefsDoc::task_doc())

        .def(py::init(&Task_init))
        .def(py::init<const std::string&>(), py::arg("name"))
        .def(py::self == py::self)
        .def("__hash__", &py_hash)
        .def("__enter__", &Task_enter)
        .def("__exit__", &Task_exit)
        .def("__str__", &Task_str)
        .def("__copy__", pyutil_copy_object<Task>)
        .def("__len__", &Task_len)
        .def(
            "__iter__",
            [](const Task& t) { return py::make_iterator(t.aliases().begin(), t.aliases().end()); },
            py::keep_alive<0, 1>())
        .def_property_readonly("aliases", &Task::aliases, "Returns a list of aliases")
        .def_property_readonly("nodes", &Task::aliases, "Returns a list of aliases");

    py::class_<Alias, Submittable, std::shared_ptr<Alias>>(m, "Alias", py::dynamic_attr(), DefsDoc::alias_doc())

        .def("__init__",
             [](py::object& /*self*/, const std::string&) {
                 throw std::runtime_error("Alias cannot be instantiated directly from Python");
             })
        .def(py::self == py::self)
        .def("__hash__", &py_hash)
        .def("__str__", &Alias_str)
        .def("__copy__", pyutil_copy_object<Alias>);
}
