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

#include "ecflow/attribute/AutoArchiveAttr.hpp"
#include "ecflow/attribute/AutoCancelAttr.hpp"
#include "ecflow/attribute/LateAttr.hpp"
#include "ecflow/client/ClientInvoker.hpp"
#include "ecflow/node/AutoRestoreAttr.hpp"
#include "ecflow/node/AvisoAttr.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Expression.hpp"
#include "ecflow/node/Limit.hpp"
#include "ecflow/node/MirrorAttr.hpp"
#include "ecflow/node/MiscAttrs.hpp"
#include "ecflow/node/Node.hpp"
#include "ecflow/node/NodeContainer.hpp"
#include "ecflow/python/DefsDoc.hpp"
#include "ecflow/python/NodeAttrDoc.hpp"
#include "ecflow/python/NodeUtil.hpp"
#include "ecflow/python/PythonBinding.hpp"
#include "ecflow/python/PythonUtil.hpp"

// See: http://wiki.python.org/moin/boost.python/HowTo#boost.function_objects

Defs* get_defs(node_ptr self) {
    return self->defs();
}

node_ptr add_variable(node_ptr self, const std::string& name, const std::string& value) {
    self->add_variable(name, value);
    return self;
}
node_ptr add_variable_int(node_ptr self, const std::string& name, int value) {
    self->add_variable_int(name, value);
    return self;
}
node_ptr add_variable_var(node_ptr self, const Variable& var) {
    self->addVariable(var);
    return self;
}

node_ptr add_event(node_ptr self, const Event& e) {
    self->addEvent(e);
    return self;
}
node_ptr add_event_1(node_ptr self, int number) {
    self->addEvent(Event(number));
    return self;
}
node_ptr add_event_2(node_ptr self, int number, const std::string& name) {
    self->addEvent(Event(number, name));
    return self;
}
node_ptr add_event_3(node_ptr self, const std::string& name) {
    self->addEvent(Event(name));
    return self;
}

node_ptr add_meter(node_ptr self, const Meter& m) {
    self->addMeter(m);
    return self;
}
node_ptr add_meter_1(node_ptr self, const std::string& meter_name, int min, int max, int color_change) {
    self->addMeter(Meter(meter_name, min, max, color_change));
    return self;
}
node_ptr add_meter_2(node_ptr self, const std::string& meter_name, int min, int max) {
    self->addMeter(Meter(meter_name, min, max));
    return self;
}

node_ptr add_queue(node_ptr self, const QueueAttr& m) {
    self->add_queue(m);
    return self;
}
node_ptr add_queue1(node_ptr self, const std::string& name, const py::list& list) {
    std::vector<std::string> vec;
    pyutil_list_to_str_vec(list, vec);
    QueueAttr queue_attr(name, vec);
    self->add_queue(queue_attr);
    return self;
}

node_ptr add_generic(node_ptr self, const GenericAttr& m) {
    self->add_generic(m);
    return self;
}
node_ptr add_generic1(node_ptr self, const std::string& name, const py::list& list) {
    std::vector<std::string> vec;
    pyutil_list_to_str_vec(list, vec);
    GenericAttr attr(name, vec);
    self->add_generic(attr);
    return self;
}

node_ptr add_label(node_ptr self, const std::string& name, const std::string& value) {
    self->addLabel(Label(name, value));
    return self;
}
node_ptr add_label_1(node_ptr self, const Label& label) {
    self->addLabel(label);
    return self;
}

node_ptr add_aviso(node_ptr self, const ecf::AvisoAttr& attr) {
    self->addAviso(attr);
    return self;
}

node_ptr add_mirror(node_ptr self, const ecf::MirrorAttr& attr) {
    self->addMirror(attr);
    return self;
}

node_ptr add_limit(node_ptr self, const std::string& name, int limit) {
    self->addLimit(Limit(name, limit));
    return self;
}
node_ptr add_limit_1(node_ptr self, const Limit& limit) {
    self->addLimit(limit);
    return self;
}
node_ptr add_in_limit(node_ptr self,
                      const std::string& name,
                      const std::string& pathToNode,
                      int tokens,
                      bool limit_this_node_only) {
    self->addInLimit(InLimit(name, pathToNode, tokens, limit_this_node_only));
    return self;
}
node_ptr add_in_limit_1(node_ptr self, const InLimit& inlimit) {
    self->addInLimit(inlimit);
    return self;
}
node_ptr add_time(node_ptr self, int hour, int minute) {
    self->addTime(ecf::TimeAttr(hour, minute));
    return self;
}
node_ptr add_time_1(node_ptr self, int hour, int minute, bool relative) {
    self->addTime(ecf::TimeAttr(hour, minute, relative));
    return self;
}
node_ptr add_time_2(node_ptr self, const std::string& ts) {
    self->addTime(ecf::TimeAttr(ecf::TimeSeries::create(ts)));
    return self;
}
node_ptr add_time_3(node_ptr self, const ecf::TimeAttr& ts) {
    self->addTime(ts);
    return self;
}
node_ptr add_today(node_ptr self, int hour, int minute) {
    self->addToday(ecf::TodayAttr(hour, minute));
    return self;
}
node_ptr add_today_1(node_ptr self, int hour, int minute, bool relative) {
    self->addToday(ecf::TodayAttr(hour, minute, relative));
    return self;
}
node_ptr add_today_2(node_ptr self, const std::string& ts) {
    self->addToday(ecf::TodayAttr(ecf::TimeSeries::create(ts)));
    return self;
}
node_ptr add_today_3(node_ptr self, const ecf::TodayAttr& ts) {
    self->addToday(ts);
    return self;
}
node_ptr add_date(node_ptr self, int day, int month, int year) {
    self->addDate(DateAttr(day, month, year));
    return self;
}
node_ptr add_date_1(node_ptr self, const DateAttr& d) {
    self->addDate(d);
    return self;
}
node_ptr add_day(node_ptr self, DayAttr::Day_t day) {
    self->addDay(DayAttr(day));
    return self;
}
node_ptr add_day_1(node_ptr self, const std::string& day) {
    self->addDay(DayAttr(DayAttr::getDay(day)));
    return self;
}
node_ptr add_day_2(node_ptr self, const DayAttr& day) {
    self->addDay(day);
    return self;
}

node_ptr add_autocancel(node_ptr self, int days) {
    self->addAutoCancel(ecf::AutoCancelAttr(days));
    return self;
}
node_ptr add_autocancel_1(node_ptr self, int hour, int min, bool relative) {
    self->addAutoCancel(ecf::AutoCancelAttr(hour, min, relative));
    return self;
}
node_ptr add_autocancel_2(node_ptr self, const ecf::TimeSlot& ts, bool relative) {
    self->addAutoCancel(ecf::AutoCancelAttr(ts, relative));
    return self;
}
node_ptr add_autocancel_3(node_ptr self, const ecf::AutoCancelAttr& attr) {
    self->addAutoCancel(attr);
    return self;
}
node_ptr add_autoarchive(node_ptr self, int days, bool idle) {
    self->add_autoarchive(ecf::AutoArchiveAttr(days, idle));
    return self;
}
node_ptr add_autoarchive_1(node_ptr self, int hour, int min, bool relative, bool idle) {
    self->add_autoarchive(ecf::AutoArchiveAttr(hour, min, relative, idle));
    return self;
}
node_ptr add_autoarchive_2(node_ptr self, const ecf::TimeSlot& ts, bool relative, bool idle) {
    self->add_autoarchive(ecf::AutoArchiveAttr(ts, relative, idle));
    return self;
}
node_ptr add_autoarchive_3(node_ptr self, const ecf::AutoArchiveAttr& attr) {
    self->add_autoarchive(attr);
    return self;
}

node_ptr add_zombie(node_ptr self, const ZombieAttr& attr) {
    self->addZombie(attr);
    return self;
}

node_ptr add_autorestore(node_ptr self, const ecf::AutoRestoreAttr& attr) {
    self->add_autorestore(attr);
    return self;
}
node_ptr add_autorestore1(node_ptr self, const py::list& list) {
    std::vector<std::string> vec;
    pyutil_list_to_str_vec(list, vec);
    self->add_autorestore(ecf::AutoRestoreAttr(vec));
    return self;
}

node_ptr add_cron(node_ptr self, const ecf::CronAttr& attr) {
    self->addCron(attr);
    return self;
}
node_ptr add_late(node_ptr self, const ecf::LateAttr& attr) {
    self->addLate(attr);
    return self;
}
std::string get_state_change_time(node_ptr self, const std::string& format) {
    if (format == "iso_extended") {
        return to_iso_extended_string(self->state_change_time());
    }
    else if (format == "iso") {
        return to_iso_string(self->state_change_time());
    }
    return to_simple_string(self->state_change_time());
}

node_ptr add_repeat_date(node_ptr self, const RepeatDate& d) {
    self->addRepeat(d);
    return self;
}
node_ptr add_repeat_datetime(node_ptr self, const RepeatDateTime& d) {
    self->addRepeat(d);
    return self;
}
node_ptr add_repeat_date_list(node_ptr self, const RepeatDateList& d) {
    self->addRepeat(d);
    return self;
}
node_ptr add_repeat_integer(node_ptr self, const RepeatInteger& d) {
    self->addRepeat(d);
    return self;
}
node_ptr add_repeat_string(node_ptr self, const RepeatString& d) {
    self->addRepeat(d);
    return self;
}
node_ptr add_repeat_enum(node_ptr self, const RepeatEnumerated& d) {
    self->addRepeat(d);
    return self;
}
node_ptr add_repeat_day(node_ptr self, const RepeatDay& d) {
    self->addRepeat(d);
    return self;
}

void sort_attributes(node_ptr self, ecf::Attr::Type attr) {
    self->sort_attributes(attr);
}
void sort_attributes1(node_ptr self, ecf::Attr::Type attr, bool recursive) {
    self->sort_attributes(attr, recursive);
}
void sort_attributes2(node_ptr self, ecf::Attr::Type attr, bool recursive, const py::list& list) {
    std::vector<std::string> no_sort;
    pyutil_list_to_str_vec(list, no_sort);
    self->sort_attributes(attr, recursive, no_sort);
}

void sort_attributes3(node_ptr self, const std::string& attribute_name, bool recursive, const py::list& list) {
    std::string attribute = attribute_name;
    boost::algorithm::to_lower(attribute);
    ecf::Attr::Type attr = ecf::Attr::to_attr(attribute_name);
    if (attr == ecf::Attr::UNKNOWN) {
        std::stringstream ss;
        ss << "sort_attributes: the attribute " << attribute_name << " is not valid";
        throw std::runtime_error(ss.str());
    }
    std::vector<std::string> no_sort;
    pyutil_list_to_str_vec(list, no_sort);
    self->sort_attributes(attr, recursive, no_sort);
}

std::vector<node_ptr> get_all_nodes(node_ptr self) {
    std::vector<node_ptr> nodes;
    self->get_all_nodes(nodes);
    return nodes;
}

node_ptr add_trigger(node_ptr self, const std::string& expr) {
    self->add_trigger(expr);
    return self;
}
node_ptr add_trigger_expr(node_ptr self, const Expression& expr) {
    self->add_trigger_expr(expr);
    return self;
}
node_ptr add_complete(node_ptr self, const std::string& expr) {
    self->add_complete(expr);
    return self;
}
node_ptr add_complete_expr(node_ptr self, const Expression& expr) {
    self->add_complete_expr(expr);
    return self;
}
node_ptr add_part_trigger(node_ptr self, const PartExpression& expr) {
    self->add_part_trigger(PartExpression(expr));
    return self;
}
node_ptr add_part_trigger_1(node_ptr self, const std::string& expression) {
    self->add_part_trigger(PartExpression(expression));
    return self;
}
node_ptr add_part_trigger_2(node_ptr self, const std::string& expression, bool and_expr) {
    self->add_part_trigger(PartExpression(expression, and_expr));
    return self;
}
node_ptr add_part_complete(node_ptr self, const PartExpression& expr) {
    self->add_part_complete(PartExpression(expr));
    return self;
}
node_ptr add_part_complete_1(node_ptr self, const std::string& expression) {
    self->add_part_complete(PartExpression(expression));
    return self;
}
node_ptr add_part_complete_2(node_ptr self, const std::string& expression, bool and_expr) {
    self->add_part_complete(PartExpression(expression, and_expr));
    return self;
}
bool evaluate_trigger(node_ptr self) {
    Ast* t = self->triggerAst();
    if (t) {
        return t->evaluate();
    }
    return false;
}
bool evaluate_complete(node_ptr self) {
    Ast* t = self->completeAst();
    if (t) {
        return t->evaluate();
    }
    return false;
}

node_ptr add_defstatus(node_ptr self, DState::State s) {
    self->addDefStatus(s);
    return self;
}
node_ptr add_defstatus1(node_ptr self, const Defstatus& ds) {
    self->addDefStatus(ds.state());
    return self;
}

py::list generated_variables_using_python_list(node_ptr self) {
    py::list list;
    std::vector<Variable> vec;
    self->gen_variables(vec);
    for (const auto& i : vec) {
        list.append(i);
    }
    return list;
}

void generated_variables_using_variablelist(node_ptr self, std::vector<Variable>& vec) {
    self->gen_variables(vec);
}

/////////////////////////////////////////////////////////////////////////////////////////

static py::object do_rshift(node_ptr self, const py::object& arg) {
    // std::cout << "do_rshift\n";
    (void)NodeUtil::do_add(self, arg);

    if (py::extract<node_ptr>(arg).check()) {
        NodeContainer* nc = self->isNodeContainer();
        if (!nc) {
            throw std::runtime_error("ExportNode::do_rshift() : Can only add a child to Suite or Family");
        }
        node_ptr child = py::extract<node_ptr>(arg);

        std::vector<node_ptr> children;
        nc->immediateChildren(children);
        node_ptr previous_child;
        for (auto& i : children) {
            if (previous_child && i == child) {
                // if existing trigger, add new trigger as AND
                if (child->get_trigger()) {
                    child->add_part_trigger(
                        PartExpression(previous_child->name() + " == complete", PartExpression::AND));
                }
                else {
                    child->add_trigger_expr(Expression(previous_child->name() + " == complete"));
                }
            }
            if (i->defStatus() != DState::COMPLETE) {
                previous_child = i;
            }
        }
    }
    return py::object(self);
}
static py::object do_lshift(node_ptr self, const py::object& arg) {
    // std::cout << "do_lshift : " << self->name() << "\n"; cout << flush;
    (void)NodeUtil::do_add(self, arg);

    if (py::extract<node_ptr>(arg).check()) {

        NodeContainer* nc = self->isNodeContainer();
        if (!nc) {
            throw std::runtime_error("ExportNode::do_lshift() : Can only add a child to Suite or Family");
        }
        node_ptr child = py::extract<node_ptr>(arg);

        std::vector<node_ptr> children;
        nc->immediateChildren(children);
        node_ptr previous_child;
        for (size_t i = 0; i < children.size(); i++) {
            if (i == 0) {
                continue;
            }
            if (children[i - 1]->defStatus() != DState::COMPLETE) {
                previous_child = children[i - 1];
            }

            if (previous_child && previous_child != child && children[i] == child) {
                // if existing trigger, add new trigger as AND
                if (previous_child->get_trigger()) {
                    previous_child->add_part_trigger(
                        PartExpression(child->name() + " == complete", PartExpression::AND));
                }
                else {
                    previous_child->add_trigger_expr(Expression(child->name() + " == complete"));
                }
            }
        }
    }
    return py::object(self);
}

static py::object add(py::tuple args, py::dict kwargs) {
    int the_list_size = len(args);
    node_ptr self     = py::extract<node_ptr>(args[0]); // self
    if (!self) {
        throw std::runtime_error("ExportNode::add() : first argument is not a node");
    }

    for (int i = 1; i < the_list_size; ++i) {
        (void)NodeUtil::do_add(self, args[i]);
    }

    // key word arguments are use for adding variable only
    (void)NodeUtil::add_variable_dict(self, kwargs);

    return py::object(self); // return node_ptr as python object, relies class_<Node>... for type registration
}

static py::object node_getattr(node_ptr self, const std::string& attr) {
    // cout << " node_getattr  self.name() : " << self->name() << "  attr " << attr << "\n";
    size_t pos     = 0;
    node_ptr child = self->findImmediateChild(attr, pos);
    if (child) {
        return py::object(child);
    }

    const Variable& var = self->findVariable(attr);
    if (!var.empty()) {
        return py::object(var);
    }

    const Variable& gvar = self->findGenVariable(attr);
    if (!gvar.empty()) {
        return py::object(gvar);
    }

    const Event& event = self->findEventByNameOrNumber(attr);
    if (!event.empty()) {
        return py::object(event);
    }

    const Meter& meter = self->findMeter(attr);
    if (!meter.empty()) {
        return py::object(meter);
    }

    limit_ptr limit = self->find_limit(attr);
    if (limit.get()) {
        return py::object(limit);
    }

    std::stringstream ss;
    ss << "ExportNode::node_getattr: function of name '" << attr
       << "' does not exist *OR* child node,variable,meter,event or limit on node " << self->absNodePath();
    throw std::runtime_error(ss.str());
    return py::object();
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct null_deleter
{
    void operator()(void const*) const {}
};
void do_replace_on_server(node_ptr self, ClientInvoker& theClient, bool suspend_node_first, bool force_replace) {
    // cout << "ExportNode: do_replace_on_server " << self->absNodePath() << "\n";
    // cout << " suspend_node_first " << suspend_node_first << "\n";
    // cout << " force_replace  " << force_replace  << "\n";

    // Need to make a defs_ptr from a Defs*  to avoid double delete use null_deletor
    defs_ptr defs                   = defs_ptr(self->defs(), null_deleter());
    bool create_parents_as_required = true;
    if (suspend_node_first) {
        theClient.suspend(self->absNodePath());
    }
    theClient.replace_1(self->absNodePath(), defs, create_parents_as_required, force_replace); // this can throw
}
void replace_on_server(node_ptr self, bool suspend_node_first, bool force_replace) {
    ClientInvoker theClient; // assume HOST and PORT found from environment
    do_replace_on_server(self, theClient, suspend_node_first, force_replace);
}
void replace_on_server1(node_ptr self,
                        const std::string& host,
                        const std::string& port,
                        bool suspend_node_first,
                        bool force_replace) {
    ClientInvoker theClient(host, port);
    do_replace_on_server(self, theClient, suspend_node_first, force_replace);
}
void replace_on_server2(node_ptr self, const std::string& host_port, bool suspend_node_first, bool force_replace) {
    ClientInvoker theClient(host_port);
    do_replace_on_server(self, theClient, suspend_node_first, force_replace);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

const ecf::LateAttr* get_late_attr(node_ptr self) {
    return self->get_late();
}

const ecf::AutoArchiveAttr* get_autoarchive_attr(node_ptr self) {
    return self->get_autoarchive();
}

const ecf::AutoCancelAttr* get_autocancel_attr(node_ptr self) {
    return self->get_autocancel();
}

const ecf::AutoRestoreAttr* get_autorestore_attr(node_ptr self) {
    return self->get_autorestore();
}

const ecf::Flag& get_flag_attr(node_ptr self) {
    return self->get_flag();
}

void export_Node() {
    // Turn off proxies by passing true as the NoProxy template parameter.
    // shared_ptrs don't need proxies because calls on one a copy of the
    // shared_ptr will affect all of them (duh!).
    py::class_<std::vector<node_ptr>>("NodeVec", "Hold a list of Nodes (i.e `suite`_, `family`_ or `task`_\\ s)")
        .def(py::vector_indexing_suite<std::vector<node_ptr>, true>());

    py::class_<Node, boost::noncopyable, node_ptr>("Node", DefsDoc::node_doc(), py::no_init)
        .def("name", &Node::name, py::return_value_policy<py::copy_const_reference>())
        .def("add", raw_function(add, 1), DefsDoc::add())  // a.add(b) & a.add([b])
        .def(py::self < py::self)                          // __lt__
        .def("__add__", &NodeUtil::do_add, DefsDoc::add()) // a + b
        .def("__rshift__",
             &do_rshift) // nc >> a >> b >> c     a + (b.add(Trigger('a==complete')) + (c.add(Trigger('b==complete')))
        .def("__lshift__",
             &do_lshift) // nc << a << b << c     (a.add(Trigger('b==complete')) + (b.add(Trigger('c==complete'))) + c
        .def("__iadd__", &NodeUtil::do_add)    // a += b
        .def("__iadd__", &NodeUtil::node_iadd) // a += [ b ]
        .def("__getattr__", &node_getattr)     /* Any attempt to resolve a property, method, or field name that doesn't
                                                  actually exist on the object itself will be passed to __getattr__*/
        .def("remove", &Node::remove, "Remove the node from its parent. and returns it")
        .def("add_trigger", &add_trigger, DefsDoc::add_trigger_doc())
        .def("add_trigger", &add_trigger_expr)
        .def("add_complete", &add_complete, DefsDoc::add_trigger_doc())
        .def("add_complete", &add_complete_expr)
        .def("add_part_trigger", &add_part_trigger, DefsDoc::add_trigger_doc())
        .def("add_part_trigger", &add_part_trigger_1)
        .def("add_part_trigger", &add_part_trigger_2)
        .def("add_part_complete", &add_part_complete, DefsDoc::add_trigger_doc())
        .def("add_part_complete", &add_part_complete_1)
        .def("add_part_complete", &add_part_complete_2)
        .def("evaluate_trigger", &evaluate_trigger, "evaluate trigger expression")
        .def("evaluate_complete", &evaluate_complete, "evaluate complete expression")
        .def("add_variable", &add_variable, DefsDoc::add_variable_doc())
        .def("add_variable", &add_variable_int)
        .def("add_variable", &add_variable_var)
        .def("add_variable", &NodeUtil::add_variable_dict)
        .def("add_label", &add_label, DefsDoc::add_label_doc())
        .def("add_label", &add_label_1)
        .def("add_aviso", &add_aviso, DefsDoc::add_aviso_doc())
        .def("add_mirror", &add_mirror, DefsDoc::add_mirror_doc())
        .def("add_limit", &add_limit, DefsDoc::add_limit_doc())
        .def("add_limit", &add_limit_1)
        .def("add_inlimit",
             &add_in_limit,
             (py::arg("limit_name"),
              py::arg("path_to_node_containing_limit") = "",
              py::arg("tokens")                        = 1,
              py::arg("limit_this_node_only")          = false),
             DefsDoc::add_inlimit_doc())
        .def("add_inlimit", &add_in_limit_1)
        .def("add_event", &add_event, DefsDoc::add_event_doc())
        .def("add_event", &add_event_1)
        .def("add_event", &add_event_2)
        .def("add_event", &add_event_3)
        .def("add_meter", &add_meter, DefsDoc::add_meter_doc())
        .def("add_meter", &add_meter_1)
        .def("add_meter", &add_meter_2)
        .def("add_queue", &add_queue)
        .def("add_queue", &add_queue1)
        .def("add_generic", &add_generic)
        .def("add_generic", &add_generic1)
        .def("add_date", &add_date, DefsDoc::add_date_doc())
        .def("add_date", &add_date_1)
        .def("add_day", &add_day, DefsDoc::add_day_doc())
        .def("add_day", &add_day_1)
        .def("add_day", &add_day_2)
        .def("add_today", &add_today, DefsDoc::add_today_doc())
        .def("add_today", &add_today_1)
        .def("add_today", &add_today_2)
        .def("add_today", &add_today_3)
        .def("add_time", &add_time, DefsDoc::add_time_doc())
        .def("add_time", &add_time_1)
        .def("add_time", &add_time_2)
        .def("add_time", &add_time_3)
        .def("add_cron", &add_cron, DefsDoc::add_cron_doc())
        .def("add_late", &add_late, DefsDoc::add_late_doc())
        .def("add_autocancel", &add_autocancel, DefsDoc::add_autocancel_doc())
        .def("add_autocancel", &add_autocancel_1)
        .def("add_autocancel", &add_autocancel_2)
        .def("add_autocancel", &add_autocancel_3)
        .def("add_autoarchive",
             &add_autoarchive,
             (py::arg("days"), py::arg("idle") = false),
             DefsDoc::add_autoarchive_doc())
        .def("add_autoarchive",
             &add_autoarchive_1,
             (py::arg("hour"), py::arg("min"), py::arg("relative"), py::arg("idle") = false))
        .def("add_autoarchive", &add_autoarchive_2, (py::arg("TimeSlot"), py::arg("relative"), py::arg("idle") = false))
        .def("add_autoarchive", &add_autoarchive_3)
        .def("add_autorestore", &add_autorestore, DefsDoc::add_autorestore_doc())
        .def("add_autorestore", &add_autorestore1)
        .def("add_verify", &Node::addVerify, DefsDoc::add_verify_doc())
        .def("add_repeat", &add_repeat_date, DefsDoc::add_repeat_date_doc())
        .def("add_repeat", &add_repeat_datetime, DefsDoc::add_repeat_datetime_doc())
        .def("add_repeat", &add_repeat_date_list, DefsDoc::add_repeat_date_list_doc())
        .def("add_repeat", &add_repeat_integer, DefsDoc::add_repeat_integer_doc())
        .def("add_repeat", &add_repeat_string, DefsDoc::add_repeat_string_doc())
        .def("add_repeat", &add_repeat_enum, DefsDoc::add_repeat_enumerated_doc())
        .def("add_repeat", &add_repeat_day, DefsDoc::add_repeat_day_doc())
        .def("add_defstatus", &add_defstatus, DefsDoc::add_defstatus_doc())
        .def("add_defstatus", &add_defstatus1, DefsDoc::add_defstatus_doc())
        .def("add_zombie", &add_zombie, NodeAttrDoc::zombie_doc())
        .def("delete_variable", &Node::deleteVariable)
        .def("delete_event", &Node::deleteEvent)
        .def("delete_meter", &Node::deleteMeter)
        .def("delete_label", &Node::deleteLabel)
        .def("delete_queue", &Node::delete_queue)
        .def("delete_generic", &Node::delete_generic)
        .def("delete_trigger", &Node::deleteTrigger)
        .def("delete_complete", &Node::deleteComplete)
        .def("delete_repeat", &Node::deleteRepeat)
        .def("delete_limit", &Node::deleteLimit)
        .def("delete_inlimit", &Node::deleteInlimit)
        .def("delete_time", &Node::deleteTime)
        .def("delete_time", &Node::delete_time)
        .def("delete_today", &Node::deleteToday)
        .def("delete_today", &Node::delete_today)
        .def("delete_date", &Node::deleteDate)
        .def("delete_date", &Node::delete_date)
        .def("delete_day", &Node::deleteDay)
        .def("delete_day", &Node::delete_day)
        .def("delete_cron", &Node::deleteCron)
        .def("delete_cron", &Node::delete_cron)
        .def("delete_zombie", &Node::deleteZombie)
        .def("delete_zombie", &Node::delete_zombie)
        .def("change_trigger", &Node::changeTrigger)
        .def("change_complete", &Node::changeComplete)
        .def("sort_attributes", &sort_attributes)
        .def("sort_attributes", &sort_attributes1)
        .def("sort_attributes", &sort_attributes2)
        .def("sort_attributes",
             &sort_attributes3,
             (py::arg("attribute_type"), py::arg("recursive") = true, py::arg("no_sort") = py::list()))
        .def("sort_attributes", &Node::sort_attributes, (py::arg("attribute_type"), py::arg("recursive") = true))
        .def("get_abs_node_path", &Node::absNodePath, DefsDoc::abs_node_path_doc())
        .def("has_time_dependencies", &Node::hasTimeDependencies)
        .def("update_generated_variables", &Node::update_generated_variables)
        .def("get_generated_variables",
             &generated_variables_using_python_list,
             "Returns the list of generated variables.")
        .def("get_generated_variables",
             &generated_variables_using_variablelist,
             "Retrieves the list of generated variables. Pass in ecflow.VariableList as argument to hold variables.")
        .def("is_suspended", &Node::isSuspended, "Returns true if the `node`_ is in a `suspended`_ state")
        .def("find_variable",
             &Node::findVariable,
             py::return_value_policy<py::copy_const_reference>(),
             "Find user variable on the node only. Returns an object")
        .def("find_gen_variable",
             &Node::findGenVariable,
             py::return_value_policy<py::copy_const_reference>(),
             "Find generated variable on the node only. Returns an object")
        .def("find_parent_variable",
             &Node::find_parent_variable,
             py::return_value_policy<py::copy_const_reference>(),
             "Find user variable variable up the parent hierarchy. Returns an object")
        .def("find_parent_variable_sub_value",
             &Node::find_parent_variable_sub_value,
             "Find user variable *up* node tree, then variable substitute the value, otherwise return empty string")
        .def("find_meter",
             &Node::findMeter,
             py::return_value_policy<py::copy_const_reference>(),
             "Find the `meter`_ on the node only. Returns an object")
        .def("find_event",
             &Node::findEventByNameOrNumber,
             py::return_value_policy<py::copy_const_reference>(),
             "Find the `event`_ on the node only. Returns a object")
        .def("find_label",
             &Node::find_label,
             py::return_value_policy<py::copy_const_reference>(),
             "Find the `label`_ on the node only. Returns a object")
        .def("find_queue",
             &Node::find_queue,
             py::return_value_policy<py::copy_const_reference>(),
             "Find the queue on the node only. Returns a queue object")
        .def("find_generic",
             &Node::find_generic,
             py::return_value_policy<py::copy_const_reference>(),
             "Find the `generic`_ on the node only. Returns a Generic object")
        .def("find_limit", &Node::find_limit, "Find the `limit`_ on the node only. returns a limit ptr")
        .def("find_node_up_the_tree", &Node::find_node_up_the_tree, "Search immediate node, then up the node hierarchy")
        .def("get_state", &Node::state, "Returns the state of the node. This excludes the suspended state")
        .def("get_state_change_time",
             &get_state_change_time,
             (py::arg("format") = "iso_extended"),
             "Returns the time of the last state change as a string. Default format is iso_extended, (iso_extended, "
             "iso, simple)")
        .def("get_dstate", &Node::dstate, "Returns the state of node. This will include suspended state")
        .def("get_defstatus", &Node::defStatus)
        .def("get_repeat", &Node::repeat, py::return_value_policy<py::copy_const_reference>())
        .def("get_late", &get_late_attr, py::return_internal_reference<>())
        .def("get_autocancel", &get_autocancel_attr, py::return_internal_reference<>())
        .def("get_autoarchive", &get_autoarchive_attr, py::return_internal_reference<>())
        .def("get_autorestore", &get_autorestore_attr, py::return_internal_reference<>())
        .def("get_trigger", &Node::get_trigger, py::return_internal_reference<>())
        .def("get_complete", &Node::get_complete, py::return_internal_reference<>())
        .def("get_defs", get_defs, py::return_internal_reference<>())
        .def("get_parent", &Node::parent, py::return_internal_reference<>())
        .def("get_all_nodes", &get_all_nodes, "Returns all the child nodes")
        .def("get_flag",
             &get_flag_attr,
             py::return_value_policy<py::copy_const_reference>(),
             "Return additional state associated with a node.")
        .def("replace_on_server",
             &replace_on_server,
             (py::arg("suspend_node_first") = true, py::arg("force") = true),
             "replace node on the server.")
        .def("replace_on_server",
             &replace_on_server1,
             (py::arg("suspend_node_first") = true, py::arg("force") = true),
             "replace node on the server.")
        .def("replace_on_server",
             &replace_on_server2,
             (py::arg("suspend_node_first") = true, py::arg("force") = true),
             "replace node on the server.")
        .def("replace_on_server",
             &do_replace_on_server,
             (py::arg("suspend_node_first") = true, py::arg("force") = true),
             "replace node on the server.")
        .add_property("meters", py::range(&Node::meter_begin, &Node::meter_end), "Returns a list of `meter`_\\ s")
        .add_property("events", py::range(&Node::event_begin, &Node::event_end), "Returns a list of `event`_\\ s")
        .add_property("variables",
                      py::range(&Node::variable_begin, &Node::variable_end),
                      "Returns a list of user defined `variable`_\\ s")
        .add_property("labels", py::range(&Node::label_begin, &Node::label_end), "Returns a list of `label`_\\ s")
        .add_property("avisos", py::range(&Node::aviso_begin, &Node::aviso_end), "Returns a list of `aviso`_\\ s")
        .add_property("mirrors", py::range(&Node::mirror_begin, &Node::mirror_end), "Returns a list of `mirror`_\\ s")
        .add_property("limits", py::range(&Node::limit_begin, &Node::limit_end), "Returns a list of `limit`_\\ s")
        .add_property(
            "inlimits", py::range(&Node::inlimit_begin, &Node::inlimit_end), "Returns a list of `inlimit`_\\ s")
        .add_property("verifies", py::range(&Node::verify_begin, &Node::verify_end), "Returns a list of Verify's")
        .add_property("times", py::range(&Node::time_begin, &Node::time_end), "Returns a list of `time`_\\ s")
        .add_property("todays", py::range(&Node::today_begin, &Node::today_end), "Returns a list of `today`_\\ s")
        .add_property("dates", py::range(&Node::date_begin, &Node::date_end), "Returns a list of `date`_\\ s")
        .add_property("days", py::range(&Node::day_begin, &Node::day_end), "Returns a list of `day`_\\ s")
        .add_property("crons", py::range(&Node::cron_begin, &Node::cron_end), "Returns a list of `cron`_\\ s")
        .add_property("zombies", py::range(&Node::zombie_begin, &Node::zombie_end), "Returns a list of `zombie`_\\ s")
        .add_property("queues", py::range(&Node::queue_begin, &Node::queue_end), "Returns a list of `queue`_\\ s")
        .add_property(
            "generics", py::range(&Node::generic_begin, &Node::generic_end), "Returns a list of `generic`_\\ s");
#if ECF_ENABLE_PYTHON_PTR_REGISTER
    py::register_ptr_to_python<node_ptr>(); // needed for mac and boost 1.6
#endif
}
