/*
 * 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/core/Converter.hpp"
#include "ecflow/core/Ecf.hpp"
#include "ecflow/core/Message.hpp"
#include "ecflow/core/Stl.hpp"
#include "ecflow/node/AutoRestoreAttr.hpp"
#include "ecflow/node/AvisoAttr.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"

using namespace ecf;
using namespace std;

void Node::replace_variables(const std::vector<Variable>& vars) {
    state_change_no_ = Ecf::incr_state_change_no();
    vars_            = vars;
}

void Node::replace_labels(const std::vector<Label>& labels) {
    state_change_no_ = Ecf::incr_state_change_no();
    labels_          = labels;
}

void Node::replace_meters(const std::vector<Meter>& meters) {
    state_change_no_ = Ecf::incr_state_change_no();
    meters_          = meters;
}

void Node::replace_events(const std::vector<Event>& events) {
    state_change_no_ = Ecf::incr_state_change_no();
    events_          = events;
}

bool Node::update_variable(const std::string& name, const std::string& value) {
    auto found = ecf::algorithm::find_by_name(vars_, name);

    if (found == std::end(vars_)) {
        return false;
    }

    found->set_value(value);
    if (0 == Ecf::debug_level()) {
        std::cerr << "Node::addVariable: Variable of name '" << name << "' already exist for node " << debugNodePath()
                  << " updating with value '" << value << "'\n";
    }
    return true;
}

void Node::addVariable(const Variable& v) {
    state_change_no_ = Ecf::incr_state_change_no();
    if (update_variable(v.name(), v.theValue()))
        return;
    if (vars_.capacity() == 0)
        vars_.reserve(5);
    vars_.push_back(v);
}

void Node::add_variable(const std::string& name, const std::string& value) {
    state_change_no_ = Ecf::incr_state_change_no();
    if (update_variable(name, value))
        return;
    if (vars_.capacity() == 0)
        vars_.reserve(5);
    vars_.emplace_back(name, value);
}

void Node::add_variable_bypass_name_check(const std::string& name, const std::string& value) {
    state_change_no_ = Ecf::incr_state_change_no();
    if (update_variable(name, value))
        return;
    if (vars_.capacity() == 0)
        vars_.reserve(5);
    vars_.emplace_back(name, value, false);
}

void Node::add_variable_int(const std::string& name, int some_int) {
    std::string value = ecf::convert_to<std::string>(some_int);
    add_variable(name, value);
}

void Node::add_trigger(const std::string& string_expression) {
    add_trigger_expression(Expression(string_expression));
}
void Node::add_complete(const std::string& string_expression) {
    add_complete_expression(Expression(string_expression));
}
void Node::add_trigger_expr(const Expression& expr) {
    add_trigger_expression(expr);
}
void Node::add_complete_expr(const Expression& expr) {
    add_complete_expression(expr);
}
void Node::add_trigger_expression(const Expression& t) {
    if (t_expr_) {
        std::stringstream ss;
        ss << "Node::add_trigger_expression. A Node(" << absNodePath() << " can only have one trigger ";
        ss << "to add large triggers use multiple calls to Node::add_part_trigger( PartExpression('t1 == complete') )";
        throw std::runtime_error(ss.str());
    }
    if (isSuite())
        throw std::runtime_error("Cannot add trigger on a suite");

    t_expr_          = std::make_unique<Expression>(t);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::add_complete_expression(const Expression& t) {
    if (c_expr_) {
        std::stringstream ss;
        ss << "Node::add_complete_expression. A Node(" << absNodePath() << " can only have one complete expression ";
        ss << "to add large complete expressions use multiple calls to Node::add_part_complete( PartExpression('t1 == "
              "complete') )";
        throw std::runtime_error(ss.str());
    }
    if (isSuite())
        throw std::runtime_error("Cannot add complete trigger on a suite");

    c_expr_          = std::make_unique<Expression>(t);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::py_add_trigger_expr(const std::vector<PartExpression>& vec) {
    if (t_expr_) {
        if (isSuite())
            throw std::runtime_error("Cannot add trigger on a suite");
        t_expr_->add_expr(vec);
        state_change_no_ = Ecf::incr_state_change_no();
    }
    else {
        Expression expr;
        for (const auto& i : vec)
            expr.add(i);
        add_trigger_expression(expr);
    }
}

void Node::py_add_complete_expr(const std::vector<PartExpression>& vec) {
    if (c_expr_) {
        if (isSuite())
            throw std::runtime_error("Cannot add complete on a suite");
        c_expr_->add_expr(vec);
        state_change_no_ = Ecf::incr_state_change_no();
    }
    else {
        Expression expr;
        for (const auto& i : vec)
            expr.add(i);
        add_complete_expression(expr);
    }
}

void Node::add_part_trigger(const PartExpression& part) {
    if (isSuite())
        throw std::runtime_error("Cannot add trigger on a suite");

    if (!t_expr_)
        t_expr_ = std::make_unique<Expression>();
    t_expr_->add(part);
    state_change_no_ = Ecf::incr_state_change_no();
}
void Node::add_part_complete(const PartExpression& part) {
    if (isSuite())
        throw std::runtime_error("Cannot add complete trigger on a suite");

    if (!c_expr_)
        c_expr_ = std::make_unique<Expression>();
    c_expr_->add(part);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addTime(const ecf::TimeAttr& t) {
#ifdef DEBUG_STATE_CHANGE_NO
    std::cout << "Node::addTime\n";
#endif

    if (isSuite()) {
        throw std::runtime_error("Cannot add time based dependency on a suite");
    }

    times_.push_back(t);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addToday(const ecf::TodayAttr& t) {
#ifdef DEBUG_STATE_CHANGE_NO
    std::cout << "Node::addToday()\n";
#endif

    if (isSuite()) {
        throw std::runtime_error("Cannot add time based dependency on a suite");
    }

    todays_.push_back(t);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addDate(const DateAttr& d) {
#ifdef DEBUG_STATE_CHANGE_NO
    std::cout << "Node::addDate()\n";
#endif

    // By disallowing what effect would it have on existing suites ?
    if (isSuite()) {
        throw std::runtime_error("Cannot add time based dependency on a suite"); // Added at 4.0.2
    }

    dates_.push_back(d);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addDay(const DayAttr& d) {
#ifdef DEBUG_STATE_CHANGE_NO
    std::cout << "Node::addDay\n";
#endif

    // By disallowing what effect would it have on existing suites ?
    if (isSuite()) {
        throw std::runtime_error("Cannot add time based dependency on a suite"); // Added at 4.0.2
    }

    days_.push_back(d);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addCron(const CronAttr& d) {
#ifdef DEBUG_STATE_CHANGE_NO
    std::cout << "Node::addCron()\n";
#endif

    if (d.time().isNULL()) {
        throw std::runtime_error("Node::addCron: The cron is in-complete, no time specified");
    }
    if (d.time().hasIncrement() && !repeat_.empty()) {
        std::stringstream ss;
        ss << "Node::addCron: Node " << absNodePath()
           << " already has a repeat. Inappropriate to add two looping structures at the same level\n";
        throw std::runtime_error(ss.str());
    }

    crons_.push_back(d);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addLabel(const Label& l) {
    if (findLabel(l.name())) {
        std::stringstream ss;
        ss << "Add Label failed: Duplicate label of name '" << l.name() << "' already exist for node "
           << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    labels_.push_back(l);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::add_label(const std::string& name, const std::string& value, const std::string& new_value, bool check) {
    if (check && findLabel(name)) {
        std::stringstream ss;
        ss << "Add Label failed: Duplicate label of name '" << name << "' already exist for node " << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    labels_.emplace_back(name, value, new_value, check);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addMeter(const Meter& m, bool check) {
    if (check) {
        const Meter& meter = findMeter(m.name());
        if (!meter.empty()) {
            std::stringstream ss;
            ss << "Add Meter failed: Duplicate Meter of name '" << m.name() << "' already exist for node "
               << debugNodePath();
            throw std::runtime_error(ss.str());
        }
    }
    meters_.push_back(m);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::add_meter(const std::string& name, int min, int max, int color_change, int value, bool check) {
    if (check) {
        const Meter& meter = findMeter(name);
        if (!meter.empty()) {
            std::stringstream ss;
            ss << "Add Meter failed: Duplicate Meter of name '" << name << "' already exist for node "
               << debugNodePath();
            throw std::runtime_error(ss.str());
        }
    }
    meters_.emplace_back(name, min, max, color_change, value, check);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addEvent(const Event& e, bool check) {
    if (check) {
        const Event& event = findEvent(e);
        if (!event.empty()) {
            std::stringstream ss;
            ss << "Add Event failed: Duplicate Event of name '" << e.name_or_number() << "' already exist for node "
               << debugNodePath();
            throw std::runtime_error(ss.str());
        }
    }
    events_.push_back(e);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addAviso(const AvisoAttr& a) {
    if (!avisos_.empty()) {
        throw std::runtime_error(
            ecf::Message("Unable to add Aviso '", a.name(), "'. Only 1 Aviso allowed per node.").str());
    }
    avisos_.push_back(a);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addMirror(const MirrorAttr& m) {
    if (!mirrors_.empty()) {
        throw std::runtime_error(
            ecf::Message("Unable to add Mirror '", m.name(), "'. Only 1 Mirror allowed per node.").str());
    }
    mirrors_.push_back(m);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addLimit(const Limit& l, bool check) {
    if (check && findLimit(l)) {
        std::stringstream ss;
        ss << "Add Limit failed: Duplicate Limit of name '" << l.name() << "' already exist for node "
           << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    limit_ptr the_limit = std::make_shared<Limit>(l);
    the_limit->set_node(this);
    limits_.push_back(the_limit);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addInLimit(const InLimit& l, bool check) {
    inLimitMgr_.addInLimit(l, check);
    state_change_no_ = Ecf::incr_state_change_no();
}

static void throwIfRepeatAlreadyExists(Node* node) {
    if (!node->repeat().empty()) {
        std::stringstream ss;
        ss << "Add Repeat failed: Repeat of name '" << node->repeat().name() << "' already exist for node "
           << node->debugNodePath();
        throw std::runtime_error(ss.str());
    }

    if (!node->crons().empty()) {
        std::stringstream ss;
        ss << "Node::addRepeat: Node " << node->absNodePath()
           << " already has a cron. Inappropriate to add two looping structures at the same level\n";
        throw std::runtime_error(ss.str());
    }
}
void Node::addRepeat(Repeat&& r) {
    throwIfRepeatAlreadyExists(this);
    repeat_ = std::move(r);
    repeat_.update_repeat_genvar();
    state_change_no_ = Ecf::incr_state_change_no();
}
void Node::addRepeat(const Repeat& r) {
    throwIfRepeatAlreadyExists(this);
    repeat_ = r;
    repeat_.update_repeat_genvar();
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::addAutoCancel(const ecf::AutoCancelAttr& ac) {
    if (auto_archive_) {
        std::stringstream ss;
        ss << "Node::addAutoCancel: Cannot add autocancel and autoarchive on the same node " << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    if (auto_cancel_) {
        std::stringstream ss;
        ss << "Node::addAutoCancel: A node can only have one autocancel, see node " << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    auto_cancel_     = std::make_unique<ecf::AutoCancelAttr>(ac);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::add_autoarchive(const ecf::AutoArchiveAttr& aa) {
    if (auto_cancel_) {
        std::stringstream ss;
        ss << "Node::add_autoarchive: Cannot add autocancel and autoarchive on the same node " << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    if (auto_archive_) {
        std::stringstream ss;
        ss << "Node::add_autoarchive: A node can only have one autoarchive, see node " << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    auto_archive_    = std::make_unique<ecf::AutoArchiveAttr>(aa);
    state_change_no_ = Ecf::incr_state_change_no();
}

void Node::add_autorestore(const ecf::AutoRestoreAttr& ar) {
    if (auto_restore_) {
        std::stringstream ss;
        ss << "Node::add_auto_restore: Can only have one autorestore per node " << debugNodePath();
        throw std::runtime_error(ss.str());
    }
    auto_restore_ = std::make_unique<ecf::AutoRestoreAttr>(ar);
    auto_restore_->set_node(this);
    state_change_no_ = Ecf::incr_state_change_no(); // Only add where used in AlterCmd
}

void Node::addLate(const ecf::LateAttr& l) {
    if (!late_) {
        late_            = std::make_unique<ecf::LateAttr>(l);
        state_change_no_ = Ecf::incr_state_change_no();
        return;
    }
    throw std::runtime_error("Add Late failed: A node can only have one Late attribute, see node " + debugNodePath());
}

void Node::addVerify(const VerifyAttr& v) {
    if (misc_attrs_) {
        misc_attrs_->addVerify(v); // can throw
        return;
    }
    misc_attrs_ = std::make_unique<MiscAttrs>(this);
    misc_attrs_->addVerify(v);
}

void Node::addZombie(const ZombieAttr& z) {
#ifdef DEBUG_STATE_CHANGE_NO
    std::cout << "Node::addZombie()\n";
#endif

    if (misc_attrs_) {
        misc_attrs_->addZombie(z); // can throw
        return;
    }
    misc_attrs_ = std::make_unique<MiscAttrs>(this);
    misc_attrs_->addZombie(z);
}

void Node::add_queue(const QueueAttr& q) {
    if (!misc_attrs_)
        misc_attrs_ = std::make_unique<MiscAttrs>(this);
    misc_attrs_->add_queue(q);
}

void Node::add_generic(const GenericAttr& q) {
    if (!misc_attrs_)
        misc_attrs_ = std::make_unique<MiscAttrs>(this);
    misc_attrs_->add_generic(q);
}
