/*
 * 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/base/cts/user/AlterCmd.hpp"

#include <stdexcept>

#include "ecflow/attribute/GenericAttr.hpp"
#include "ecflow/attribute/LateAttr.hpp"
#include "ecflow/attribute/QueueAttr.hpp"
#include "ecflow/base/AbstractClientEnv.hpp"
#include "ecflow/base/AbstractServer.hpp"
#include "ecflow/base/cts/user/CtsApi.hpp"
#include "ecflow/core/Converter.hpp"
#include "ecflow/core/Enumerate.hpp"
#include "ecflow/core/Extract.hpp"
#include "ecflow/core/Log.hpp"
#include "ecflow/core/Str.hpp"
#include "ecflow/node/Defs.hpp"
#include "ecflow/node/Expression.hpp"
#include "ecflow/node/Limit.hpp"
#include "ecflow/node/Node.hpp"
#include "ecflow/node/Suite.hpp"
#include "ecflow/node/SuiteChanged.hpp"

using namespace ecf;
using namespace std;
using namespace boost;
namespace po = boost::program_options;

static std::string dump_args(const std::vector<std::string>& options, const std::vector<std::string>& paths) {
    std::string the_args;
    for (const auto& option : options) {
        the_args += option;
        the_args += " ";
    }
    for (const auto& path : paths) {
        the_args += path;
        the_args += " ";
    }
    return the_args;
}

namespace ecf::detail {

template <>
struct EnumTraits<AlterCmd::Delete_attr_type>
{
    using underlying_t = std::underlying_type_t<AlterCmd::Delete_attr_type>;

    static constexpr std::array map = std::array{
        // clang-format off
        std::make_pair(AlterCmd::DEL_VARIABLE, "variable"),
        std::make_pair(AlterCmd::DEL_TIME, "time"),
        std::make_pair(AlterCmd::DEL_TODAY, "today"),
        std::make_pair(AlterCmd::DEL_DATE, "date"),
        std::make_pair(AlterCmd::DEL_DAY, "day"),
        std::make_pair(AlterCmd::DEL_CRON, "cron"),
        std::make_pair(AlterCmd::DEL_EVENT, "event"),
        std::make_pair(AlterCmd::DEL_METER, "meter"),
        std::make_pair(AlterCmd::DEL_LABEL, "label"),
        std::make_pair(AlterCmd::DEL_TRIGGER, "trigger"),
        std::make_pair(AlterCmd::DEL_COMPLETE, "complete"),
        std::make_pair(AlterCmd::DEL_REPEAT, "repeat"),
        std::make_pair(AlterCmd::DEL_LIMIT, "limit"),
        std::make_pair(AlterCmd::DEL_LIMIT_PATH, "limit_path"),
        std::make_pair(AlterCmd::DEL_INLIMIT, "inlimit"),
        std::make_pair(AlterCmd::DEL_ZOMBIE, "zombie"),
        std::make_pair(AlterCmd::DEL_LATE, "late"),
        std::make_pair(AlterCmd::DEL_QUEUE, "queue"),
        std::make_pair(AlterCmd::DEL_GENERIC, "generic")
        // clang-format on
    };
    static constexpr size_t size = map.size();

    static_assert(EnumTraits<AlterCmd::Delete_attr_type>::size == map.back().first);
};

template <>
struct EnumTraits<AlterCmd::Add_attr_type>
{
    using underlying_t = std::underlying_type_t<AlterCmd::Add_attr_type>;

    static constexpr std::array map = std::array{
        // clang-format off
        std::make_pair(AlterCmd::ADD_TIME, "time"),
        std::make_pair(AlterCmd::ADD_TODAY, "today"),
        std::make_pair(AlterCmd::ADD_DATE, "date"),
        std::make_pair(AlterCmd::ADD_DAY, "day"),
        std::make_pair(AlterCmd::ADD_ZOMBIE, "zombie"),
        std::make_pair(AlterCmd::ADD_VARIABLE, "variable"),
        std::make_pair(AlterCmd::ADD_LATE, "late"),
        std::make_pair(AlterCmd::ADD_LIMIT, "limit"),
        std::make_pair(AlterCmd::ADD_INLIMIT, "inlimit"),
        std::make_pair(AlterCmd::ADD_LABEL, "label")
        // clang-format on
    };
    static constexpr size_t size = map.size();

    static_assert(EnumTraits<AlterCmd::Add_attr_type>::size == map.back().first);
};

template <>
struct EnumTraits<AlterCmd::Change_attr_type>
{
    using underlying_t = std::underlying_type_t<AlterCmd::Change_attr_type>;

    static constexpr std::array map = std::array{
        // clang-format off
        std::make_pair(AlterCmd::VARIABLE, "variable"),
        std::make_pair(AlterCmd::CLOCK_TYPE, "clock_type"),
        std::make_pair(AlterCmd::CLOCK_DATE, "clock_date"),
        std::make_pair(AlterCmd::CLOCK_GAIN, "clock_gain"),
        std::make_pair(AlterCmd::CLOCK_SYNC, "clock_sync"),
        std::make_pair(AlterCmd::EVENT, "event"),
        std::make_pair(AlterCmd::METER, "meter"),
        std::make_pair(AlterCmd::LABEL, "label"),
        std::make_pair(AlterCmd::TRIGGER, "trigger"),
        std::make_pair(AlterCmd::COMPLETE, "complete"),
        std::make_pair(AlterCmd::REPEAT, "repeat"),
        std::make_pair(AlterCmd::LIMIT_MAX, "limit_max"),
        std::make_pair(AlterCmd::LIMIT_VAL, "limit_value"),
        std::make_pair(AlterCmd::DEFSTATUS, "defstatus"),
        std::make_pair(AlterCmd::LATE, "late"),
        std::make_pair(AlterCmd::TIME, "time"),
        std::make_pair(AlterCmd::TODAY, "today")
        // clang-format on
    };
    static constexpr size_t size = map.size();

    static_assert(EnumTraits<AlterCmd::Change_attr_type>::size == map.back().first);
};

} // namespace ecf::detail

static AlterCmd::Delete_attr_type deleteAttrType(const std::string& s) {
    if (auto found = ecf::Enumerate<AlterCmd::Delete_attr_type>::to_enum(s); found) {
        return found.value();
    }
    return AlterCmd::DELETE_ATTR_ND;
}

static std::string to_string(AlterCmd::Delete_attr_type d) {
    if (auto found = ecf::Enumerate<AlterCmd::Delete_attr_type>::to_string(d); found) {
        return std::string{found.value()};
    }
    return {};
}

static void validDeleteAttr(std::vector<std::string>& vec) {
    vec = ecf::Enumerate<AlterCmd::Delete_attr_type>::designations();
}

static AlterCmd::Add_attr_type addAttrType(const std::string& s) {
    if (auto found = ecf::Enumerate<AlterCmd::Add_attr_type>::to_enum(s); found) {
        return found.value();
    }
    return AlterCmd::ADD_ATTR_ND;
}

static std::string to_string(AlterCmd::Add_attr_type a) {
    if (auto found = ecf::Enumerate<AlterCmd::Add_attr_type>::to_string(a); found) {
        return std::string{found.value()};
    }
    return {};
}

static void validAddAttr(std::vector<std::string>& vec) {
    vec = Enumerate<AlterCmd::Add_attr_type>::designations();
}

static AlterCmd::Change_attr_type changeAttrType(const std::string& s) {
    if (auto found = Enumerate<AlterCmd::Change_attr_type>::to_enum(s); found) {
        return found.value();
    }
    return AlterCmd::CHANGE_ATTR_ND;
}

static std::string to_string(AlterCmd::Change_attr_type c) {
    if (auto found = Enumerate<AlterCmd::Change_attr_type>::to_string(c); found) {
        return std::string{found.value()};
    }
    return {};
}

static void validChangeAttr(std::vector<std::string>& vec) {
    vec = ecf::Enumerate<AlterCmd::Change_attr_type>::designations();
}

//=======================================================================================

bool AlterCmd::equals(ClientToServerCmd* rhs) const {
    auto* the_rhs = dynamic_cast<AlterCmd*>(rhs);
    if (!the_rhs)
        return false;
    if (paths_ != the_rhs->paths()) {
        return false;
    }
    if (name_ != the_rhs->name()) {
        return false;
    }
    if (value_ != the_rhs->value()) {
        return false;
    }
    if (del_attr_type_ != the_rhs->delete_attr_type()) {
        return false;
    }
    if (change_attr_type_ != the_rhs->change_attr_type()) {
        return false;
    }
    if (add_attr_type_ != the_rhs->add_attr_type()) {
        return false;
    }
    if (flag_type_ != the_rhs->flag_type()) {
        return false;
    }
    if (flag_ != the_rhs->flag()) {
        return false;
    }
    return UserCmd::equals(rhs);
}

void AlterCmd::alter_and_attr_type(std::string& alter_type, std::string& attr_type) const {
    if (del_attr_type_ != AlterCmd::DELETE_ATTR_ND) {
        alter_type = "delete";
        attr_type  = to_string(del_attr_type_);
    }
    else if (change_attr_type_ != AlterCmd::CHANGE_ATTR_ND) {
        alter_type = "change";
        attr_type  = to_string(change_attr_type_);
    }
    else if (add_attr_type_ != AlterCmd::ADD_ATTR_ND) {
        alter_type = "add";
        attr_type  = to_string(add_attr_type_);
    }
    else if (flag_type_ != ecf::Flag::NOT_SET) {
        if (flag_)
            alter_type = "set_flag";
        else
            alter_type = "clear_flag";
        attr_type = ecf::Flag::enum_to_string(flag_type_);
    }
    else {
        alter_type = "sort";
    }
}

void AlterCmd::print_only(std::string& os) const {
    std::string alter_type, attr_type;
    alter_and_attr_type(alter_type, attr_type);
    if (paths_.empty())
        os += CtsApi::to_string(CtsApi::alter(std::vector<std::string>(1, " "), alter_type, attr_type, name_, value_));
    else
        os += CtsApi::to_string(
            CtsApi::alter(std::vector<std::string>(1, paths_[0]), alter_type, attr_type, name_, value_));
}

void AlterCmd::print(std::string& os) const {
    std::string alter_type, attr_type;
    alter_and_attr_type(alter_type, attr_type);
    user_cmd(os, CtsApi::to_string(CtsApi::alter(paths_, alter_type, attr_type, name_, value_)));
}

void AlterCmd::print(std::string& os, const std::string& path) const {
    std::string alter_type, attr_type;
    alter_and_attr_type(alter_type, attr_type);
    user_cmd(os,
             CtsApi::to_string(CtsApi::alter(std::vector<std::string>(1, path), alter_type, attr_type, name_, value_)));
}

STC_Cmd_ptr AlterCmd::alter_server_state(AbstractServer* as) const {
    Defs* defs = as->defs().get();

    if (del_attr_type_ == AlterCmd::DEL_VARIABLE) {
        defs->set_server().delete_user_variable(name_);
    }
    else if (change_attr_type_ == AlterCmd::VARIABLE || add_attr_type_ == AlterCmd::ADD_VARIABLE) {

        // ECFLOW-380: Some variable should be read only
        if (name_ == Str::ECF_HOST() || name_ == Str::ECF_PORT() || name_ == "ECF_PID" || name_ == "ECF_VERSION" ||
            name_ == "ECF_LISTS") {
            std::stringstream ss;
            ss << "AlterCmd:: Cannot add or change read only server variable " << name_;
            throw std::runtime_error(ss.str());
        }
        defs->set_server().add_or_update_user_variables(name_, value_);
    }

    // Update defs flag state
    if (flag_type_ != Flag::NOT_SET) {
        if (flag_)
            defs->flag().set(flag_type_);
        else {
            defs->flag().clear(flag_type_);
            if (flag_type_ == Flag::LOG_ERROR)
                defs->set_server().delete_user_variable("ECF_LOG_ERROR");
            if (flag_type_ == Flag::CHECKPT_ERROR)
                defs->set_server().delete_user_variable("ECF_CHECKPT_ERROR");
        }
    }

    // sort
    ecf::Attr::Type attr = Attr::to_attr(name_);
    if (attr != ecf::Attr::UNKNOWN) {
        bool recursive = (value_ == "recursive") ? true : false;
        defs->sort_attributes(attr, recursive);
    }

    return doJobSubmission(as);
}

STC_Cmd_ptr AlterCmd::doHandleRequest(AbstractServer* as) const {
    as->update_stats().alter_cmd_++;
    Defs* defs = as->defs().get();

    std::stringstream ss;
    size_t vec_size = paths_.size();
    for (size_t i = 0; i < vec_size; i++) {

        /// For root node user means to change server state
        if (paths_[i] == "/") {
            return alter_server_state(as);
        }

        node_ptr node = find_node_for_edit_no_throw(defs, paths_[i]);
        if (!node.get()) {
            ss << "AlterCmd: Could not find node at path " << paths_[i] << "\n";
            LOG(Log::ERR, "AlterCmd: Failed: Could not find node at path " << paths_[i]);
            continue;
        }

        SuiteChangedPtr changed(node.get());
        try {
            switch (del_attr_type_) {
                case AlterCmd::DEL_VARIABLE:
                    node->deleteVariable(name_);
                    break;
                case AlterCmd::DEL_TIME:
                    node->deleteTime(name_);
                    break;
                case AlterCmd::DEL_TODAY:
                    node->deleteToday(name_);
                    break;
                case AlterCmd::DEL_DATE:
                    node->deleteDate(name_);
                    break;
                case AlterCmd::DEL_DAY:
                    node->deleteDay(name_);
                    break;
                case AlterCmd::DEL_CRON:
                    node->deleteCron(name_);
                    break;
                case AlterCmd::DEL_EVENT:
                    node->deleteEvent(name_);
                    break;
                case AlterCmd::DEL_METER:
                    node->deleteMeter(name_);
                    break;
                case AlterCmd::DEL_LABEL:
                    node->deleteLabel(name_);
                    break;
                case AlterCmd::DEL_TRIGGER:
                    node->deleteTrigger();
                    break;
                case AlterCmd::DEL_COMPLETE:
                    node->deleteComplete();
                    break;
                case AlterCmd::DEL_REPEAT:
                    node->deleteRepeat();
                    break;
                case AlterCmd::DEL_LIMIT:
                    node->deleteLimit(name_);
                    break;
                case AlterCmd::DEL_LIMIT_PATH:
                    node->delete_limit_path(name_, value_);
                    break;
                case AlterCmd::DEL_INLIMIT:
                    node->deleteInlimit(name_);
                    break;
                case AlterCmd::DEL_ZOMBIE:
                    node->deleteZombie(name_);
                    break;
                case AlterCmd::DEL_LATE:
                    node->deleteLate();
                    break;
                case AlterCmd::DEL_QUEUE:
                    node->delete_queue(name_);
                    break;
                case AlterCmd::DEL_GENERIC:
                    node->delete_generic(name_);
                    break;
                case AlterCmd::DELETE_ATTR_ND:
                    break;
                default:
                    break;
            }
        }
        catch (std::exception& e) {
            ss << "Alter (delete) failed for " << paths_[i] << " : " << e.what() << "\n";
        }

        try {
            switch (change_attr_type_) {
                case AlterCmd::VARIABLE:
                    node->changeVariable(name_, value_);
                    break;
                case AlterCmd::CLOCK_TYPE:
                    node->suite()->changeClockType(name_);
                    break; // node must be a suite, value must [hybrid|real| virtual]
                case AlterCmd::CLOCK_DATE:
                    node->suite()->changeClockDate(name_);
                    break; // Expecting day.month.year: node must be a suite, value must [hybrid|real| virtual]
                case AlterCmd::CLOCK_GAIN:
                    node->suite()->changeClockGain(name_);
                    break; // node must be a suite, value must be int
                case AlterCmd::CLOCK_SYNC:
                    node->suite()->changeClockSync();
                    break; // node must be a suite, sync clock with computer
                case AlterCmd::EVENT:
                    node->changeEvent(name_, value_);
                    break; // if value is empty just set, [1|0] or name [set | clear]
                case AlterCmd::METER:
                    node->changeMeter(name_, value_);
                    break;
                case AlterCmd::LABEL:
                    node->changeLabel(name_, value_);
                    break;
                case AlterCmd::TRIGGER:
                    node->changeTrigger(name_);
                    break; // expression must parse
                case AlterCmd::COMPLETE:
                    node->changeComplete(name_);
                    break; // expression must parse
                case AlterCmd::REPEAT:
                    node->changeRepeat(name_);
                    break; //
                case AlterCmd::LIMIT_MAX:
                    node->changeLimitMax(name_, value_);
                    break; // value must be convertible to int
                case AlterCmd::LIMIT_VAL:
                    node->changeLimitValue(name_, value_);
                    break; // value < limit max, & value must be convertible to an int
                case AlterCmd::DEFSTATUS:
                    node->changeDefstatus(name_);
                    break; // must be a valid state
                case AlterCmd::LATE:
                    node->changeLate(LateAttr::create(name_));
                    break; // must be a valid late
                case AlterCmd::TIME:
                    node->change_time(name_, value_);
                    break;
                case AlterCmd::TODAY:
                    node->change_today(name_, value_);
                    break;
                case AlterCmd::CHANGE_ATTR_ND:
                    break;
                default:
                    break;
            }
        }
        catch (std::exception& e) {
            ss << "Alter (change) failed for " << paths_[i] << " : " << e.what() << "\n";
        }

        try {
            switch (add_attr_type_) {
                case AlterCmd::ADD_TIME:
                    node->addTime(TimeAttr(TimeSeries::create(name_)));
                    break;
                case AlterCmd::ADD_TODAY:
                    node->addToday(TodayAttr(TimeSeries::create(name_)));
                    break;
                case AlterCmd::ADD_DATE:
                    node->addDate(DateAttr::create(name_));
                    break;
                case AlterCmd::ADD_DAY:
                    node->addDay(DayAttr::create(name_));
                    break;
                case AlterCmd::ADD_ZOMBIE:
                    node->addZombie(ZombieAttr::create(name_));
                    break;
                case AlterCmd::ADD_VARIABLE:
                    node->add_variable(name_, value_);
                    break;
                case AlterCmd::ADD_LATE:
                    node->addLate(LateAttr::create(name_));
                    break;
                case AlterCmd::ADD_LABEL:
                    node->addLabel(Label(name_, value_));
                    break;
                case AlterCmd::ADD_LIMIT: {
                    int int_value = 0;
                    try {
                        int_value = ecf::convert_to<int>(value_);
                    }
                    catch (const ecf::bad_conversion&) {
                        std::stringstream mss;
                        mss << "AlterCmd: add_limit " << name_ << " " << value_ << " failed. Expected '" << value_
                            << "' to be convertible to an integer";
                        throw std::runtime_error(mss.str());
                    }
                    node->addLimit(Limit(name_, int_value));
                    break;
                }
                case AlterCmd::ADD_INLIMIT: {
                    string path_to_limit; // This can be empty
                    string limitName;
                    if (!Extract::pathAndName(name_, path_to_limit, limitName)) {
                        throw std::runtime_error("AlterCmd::ADD_INLIMIT: Invalid inlimit : " + name_);
                    }
                    int token_value = 1;
                    if (!value_.empty()) {
                        try {
                            token_value = ecf::convert_to<int>(value_);
                        }
                        catch (const ecf::bad_conversion&) {
                            ss << "AlterCmd: add_inlimit expected '" << value_ << "' to be convertible to an integer";
                            throw std::runtime_error(ss.str());
                        }
                    }
                    node->addInLimit(InLimit(limitName, path_to_limit, token_value)); // will throw if not valid
                    break;
                }
                case AlterCmd::ADD_ATTR_ND:
                    break;
            }
        }
        catch (std::exception& e) {
            ss << "Alter (add) failed for " << paths_[i] << ": Could not parse " << name_ << " : " << e.what() << "\n";
        }

        // Change flags
        if (flag_type_ != Flag::NOT_SET) {
            if (flag_)
                node->flag().set(flag_type_);
            else
                node->flag().clear(flag_type_);
        }

        // sort
        ecf::Attr::Type attr = Attr::to_attr(name_);
        if (attr != ecf::Attr::UNKNOWN) {
            bool recursive = (value_ == "recursive") ? true : false;
            node->sort_attributes(attr, recursive);
        }
    }

    std::string error_msg = ss.str();
    if (!error_msg.empty()) {
        throw std::runtime_error(error_msg);
    }

    return doJobSubmission(as);
}

bool AlterCmd::authenticate(AbstractServer* as, STC_Cmd_ptr& cmd) const {
    return do_authenticate(as, cmd, paths_);
}

const char* AlterCmd::arg() {
    return CtsApi::alterArg();
}
const char* AlterCmd::desc() {
    /////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
    return "Alter the node according to the options.\n"
           "To add/delete/change server variables use '/' for the path.\n"
           "arg1 =   [ delete | change | add | set_flag | clear_flag | sort ]\n"
           "         one option must be specified\n"
           "arg2 = For delete:\n"
           "         [ variable | time | today | date  | day | cron | event | meter | late | generic | queue |\n"
           "           label | trigger | complete | repeat | limit | inlimit | limit_path | zombie ]\n"
           "       For change:\n"
           "         [ variable | clock_type | clock_gain | clock_date | clock_sync  | event | meter | label |\n"
           "           trigger  | complete   | repeat     | limit_max  | limit_value | defstatus | late | time | today "
           "]\n"
           "         *NOTE* If the clock is changed, then the suite will need to be re-queued in order for\n"
           "         the change to take effect fully.\n"
           "       For add:\n"
           "         [ variable | time | today | date | day | zombie | late | limit | inlimit | label ]\n"
           "       For set_flag and clear_flag:\n"
           "         [ force_aborted | user_edit | task_aborted | edit_failed | ecfcmd_failed \n"
           "           statuscmd_failed | killcmd_failed | no_script | killed | status | late | message | \n"
           "           complete | queue_limit | task_waiting | locked | zombie | archived | restored |\n"
           "           threshold | log_error | checkpt_error]\n"
           "       For sort:\n"
           "         [ event | meter | label | variable| limit | all ]\n"
           "arg3 = name/value\n"
           "       when changing, attributes like variable,meter,event,label,limits,late\n"
           "       we expect arguments to be quoted. For sort this argument can be called 'recursive'\n"
           "arg4 = new_value\n"
           "       specifies the new value only used for 'change'/'add'\n"
           "       values with spaces must be quoted\n"
           "arg5 = paths : At least one node path required.The paths must start with a leading '/' character\n\n"
           "\nUsage:\n\n"
           "   ecflow_client --alter=add variable GLOBAL \"value\" /           # add server variable\n"
           "   ecflow_client --alter=add variable FRED \"value\" /path/to/node # add node variable\n"
           "   ecflow_client --alter=add time \"+00:20\" /path/to/node\n"
           "   ecflow_client --alter=add date \"01.*.*\" /path/to/node\n"
           "   ecflow_client --alter=add day \"sunday\"  /path/to/node\n"
           "   ecflow_client --alter=add label name \"label_value\" /path/to/node\n"
           "   ecflow_client --alter=add late \"-s 00:01 -a 14:30 -c +00:01\" /path/to/node\n"
           "   ecflow_client --alter=add limit mars \"100\" /path/to/node\n"
           "   ecflow_client --alter=add inlimit /path/to/node/withlimit:limit_name \"10\" /path/to/node\n"
           "   # zombie attributes have the following structure:\n"
           "     `zombie_type`:(`client_side_action` | `server_side_action`):`child`:`zombie_life_time`\n"
           "      zombie_type        = \"user\" | \"ecf\" | \"path\" | \"ecf_pid\" | \"ecf_passwd\" | "
           "\"ecf_pid_passwd\"\n"
           "      client_side_action = \"fob\" | \"fail\" | \"block\"\n"
           "      server_side_action = \"adopt\" | \"delete\" | \"kill\"\n"
           "      child              = \"init\" | \"event\" | \"meter\" | \"label\" | \"wait\" | \"abort\" | "
           "\"complete\" | \"queue\"\n"
           "      zombie_life_time   = unsigned integer default: user(300), ecf(3600), path(900)  minimum is 60\n"
           "   ecflow_client --alter=add zombie \"ecf:fail::\" /path/to/node     # ask system zombies to fail\n"
           "   ecflow_client --alter=add zombie \"user:fail::\" /path/to/node    # ask user generated zombies to fail\n"
           "   ecflow_client --alter=add zombie \"path:fail::\" /path/to/node    # ask path zombies to fail\n\n"
           "   ecflow_client --alter=delete variable FRED /path/to/node    # delete variable FRED\n"
           "   ecflow_client --alter=delete variable      /path/to/node    # delete *ALL* variables on the specified "
           "node\n";
}

void AlterCmd::addOption(boost::program_options::options_description& desc) const {
    // Important: this option is, in practice, multi-token (and thus should use
    // po::value<vector<string>>()->multitoken()). However, because of the special handling
    // necessary to allow positional values, such as "--help", a custom `style_parser` is used
    // instead when parsing the CLI options -- see ClientOptions for details.
    desc.add_options()(AlterCmd::arg(), po::value<vector<string>>(), AlterCmd::desc());
}
void AlterCmd::create(Cmd_ptr& cmd, boost::program_options::variables_map& vm, AbstractClientEnv* ac) const {
    vector<string> args = vm[arg()].as<vector<string>>();

    if (ac->debug())
        dumpVecArgs(AlterCmd::arg(), args);

    std::vector<std::string> options, paths;
    split_args_to_options_and_paths(args, options, paths); // relative order is still preserved
    if (paths.empty()) {
        std::stringstream ss;
        ss << "AlterCmd: No paths specified. Paths must begin with a leading '/' character\n"
           << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }
    if (options.empty()) {
        std::stringstream ss;
        ss << "AlterCmd: Invalid argument list:\n" << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }
    if (options.size() < 2) {
        std::stringstream ss;
        ss << "Alter: At least three arguments expected. Found " << args.size() << "\n"
           << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }

    // arg[0] should one of [ add | delete | change | set_flag | clear_flag | sort ]
    std::string alterType = options[0];

    if (alterType == "add") {

        createAdd(cmd, options, paths);
        return;
    }
    else if (alterType == "change") {

        createChange(cmd, options, paths);
        return;
    }
    else if (alterType == "delete") {

        createDelete(cmd, options, paths);
        return;
    }
    else if (alterType == "set_flag") {

        create_flag(cmd, options, paths, true /*set */);
        return;
    }
    else if (alterType == "clear_flag") {

        create_flag(cmd, options, paths, false /*clear */);
        return;
    }
    else if (alterType == "sort") {

        create_sort_attributes(cmd, options, paths);
        return;
    }

    std::stringstream ss;
    ss << "Alter: The first argument must be one of [ change | delete | add | set_flag | clear_flag | sort ] but found "
          "'"
       << alterType << "'\n"
       << dump_args(options, paths) << "\n";
    throw std::runtime_error(ss.str());
}

AlterCmd::Add_attr_type AlterCmd::get_add_attr_type(const std::string& attr_type) const {
    AlterCmd::Add_attr_type theAttrType = addAttrType(attr_type);
    if (theAttrType == AlterCmd::ADD_ATTR_ND) {
        std::stringstream ss;
        ss << "AlterCmd: add: The second argument must be one of [ ";
        std::vector<std::string> valid;
        validAddAttr(valid);
        for (size_t i = 0; i < valid.size(); ++i) {
            if (i != 0)
                ss << " | ";
            ss << valid[i];
        }
        ss << "] but found " << attr_type << "\n" << AlterCmd::desc();
        throw std::runtime_error(ss.str());
    }
    return theAttrType;
}

void AlterCmd::createAdd(Cmd_ptr& cmd, std::vector<std::string>& options, std::vector<std::string>& paths) const {
    // options[0] - add
    // options[1] - [ time | today | date | day | zombie | variable | late | limit | inlimit | label ]
    // options[2] - [ time_string | date_string | day_string | zombie_string | variable_name | limit_name |
    //                path_to_limit ]
    // options[3] - variable_value

    AlterCmd::Add_attr_type theAttrType = get_add_attr_type(options[1]);

    std::stringstream ss;
    if (options.size() < 3) {
        ss << "AlterCmd: add: At least four arguments expected. Found " << (options.size() + paths.size()) << "\n"
           << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }

    string name, value;
    try {
        extract_name_and_value_for_add(theAttrType, name, value, options, paths);
        check_for_add(theAttrType, name, value);
    }
    catch (std::exception& e) {
        ss << "AlterCmd: add: Could not parse " << name << ". Error: " << e.what()
           << "\n for time,today and date the new value should be a quoted string "
           << "\n for add expected: --alter add variable <name> <value> <paths>\n"
           << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }

    cmd = std::make_shared<AlterCmd>(paths, theAttrType, name, value);
}

void AlterCmd::extract_name_and_value_for_add(AlterCmd::Add_attr_type theAttrType,
                                              std::string& name,
                                              std::string& value,
                                              std::vector<std::string>& options,
                                              std::vector<std::string>& paths) const {
    // **** parse and check format, expect this argument to be single or double tick quoted ****
    // **** for time,date,day or zombie
    std::stringstream ss;
    name = options[2];
    switch (theAttrType) {
        case AlterCmd::ADD_TIME:
            break;
        case AlterCmd::ADD_TODAY:
            break;
        case AlterCmd::ADD_DATE:
            break;
        case AlterCmd::ADD_DAY:
            break;
        case AlterCmd::ADD_ZOMBIE:
            break;
        case AlterCmd::ADD_LATE:
            break;
        case AlterCmd::ADD_VARIABLE: {
            if (options.size() == 3 && paths.size() > 1) {
                // variable value may be a path, hence it will be in the paths parameter
                options.push_back(paths[0]);
                paths.erase(paths.begin());
            }
            if (options.size() < 4) {
                ss << "AlterCmd: add: Expected 'add variable <name> <value> <paths>. Not enough arguments\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            value = options[3];
            break;
        }
        case AlterCmd::ADD_LABEL: {
            if (options.size() == 3 && paths.size() > 1) {
                // label value may be a path, hence it will be in the paths parameter
                options.push_back(paths[0]);
                paths.erase(paths.begin());
            }
            if (options.size() < 4) {
                ss << "AlterCmd: add: Expected 'add label <name> <value> <paths>. Not enough arguments\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            value = options[3];
            break;
        }
        case AlterCmd::ADD_LIMIT: {
            if (options.size() < 4) {
                ss << "AlterCmd: add: Expected 'add limit <name> int. Not enough arguments\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            value = options[3];
            break;
        }
        case AlterCmd::ADD_INLIMIT: { // inlimit /obs/limits:hpcd 2 name=hpcd, path=/obs/limits, tokens=2(optional)
            // options[0]  - add
            // options[1]  - [ inlimit ]
            // options[2]  - [ path_to_limit:limit_name ]   --> name
            // options[3]  - integer (optional)             --> value
            if (options.size() < 3) {
                ss << "AlterCmd: add: Expected 'add inlimit <path-to-limit:limit_name> <int>(optional) <paths>. Not "
                      "enough arguments\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            if (options.size() == 4) {
                value = options[3];
            }
            break;
        }
        case AlterCmd::ADD_ATTR_ND:
            break;
    }
}

void AlterCmd::check_for_add(AlterCmd::Add_attr_type theAttrType,
                             const std::string& name,
                             const std::string& value) const {
    // **** parse and check format, expect this argument to be single or double tick quoted ****
    // **** for time,date,day or zombie
    if (name.empty())
        throw std::runtime_error("Alter: check_for_add : name is empty ?");

    std::stringstream ss;
    switch (theAttrType) {
        case AlterCmd::ADD_TIME:
            (void)TimeSeries::create(name);
            break;
        case AlterCmd::ADD_TODAY:
            (void)TimeSeries::create(name);
            break;
        case AlterCmd::ADD_DATE:
            (void)DateAttr::create(name);
            break;
        case AlterCmd::ADD_DAY:
            (void)DayAttr::create(name);
            break;
        case AlterCmd::ADD_ZOMBIE:
            (void)ZombieAttr::create(name);
            break;
        case AlterCmd::ADD_LATE:
            (void)LateAttr::create(name);
            break;
        case AlterCmd::ADD_VARIABLE: {
            // Create a Variable to check valid names
            Variable check(name, value);
            break;
        }
        case AlterCmd::ADD_LABEL: {
            // Create a Label to check valid names
            Label check(name, value);
            break;
        }
        case AlterCmd::ADD_LIMIT: {
            int int_value = 0;
            try {
                int_value = ecf::convert_to<int>(value);
            }
            catch (const ecf::bad_conversion&) {
                ss << "AlterCmd add_limit expected value(" << value << ") to be convertible to an integer\n";
                throw std::runtime_error(ss.str());
            }
            Limit check(name, int_value); // will throw if not valid
            break;
        }
        case AlterCmd::ADD_INLIMIT: { // inlimit /obs/limits:hpcd 2 name=hpcd, path=/obs/limits, tokens=2(optional)
            // options[0]  - add
            // options[1]  - [ inlimit ]
            // options[2]  - [ path_to_limit:limit_name ]  ---> name
            // options[3]  - integer (optional)            ---> value
            string path_to_limit; // This can be empty
            string limitName;
            if (!Extract::pathAndName(name, path_to_limit, limitName)) {
                throw std::runtime_error("AlterCmd add inlimit Invalid inlimit : " + name);
            }
            int token_value = 1;
            if (!value.empty()) {
                try {
                    token_value = ecf::convert_to<int>(value);
                }
                catch (const ecf::bad_conversion&) {
                    ss << "AlterCmd add inlimit expected optional limit token '" << value
                       << "' to be convertible to an integer\n";
                    throw std::runtime_error(ss.str());
                }
            }
            InLimit inlimit(limitName, path_to_limit, token_value); // will throw if not valid
            break;
        }
        case AlterCmd::ADD_ATTR_ND:
            break;
    }
}

AlterCmd::Delete_attr_type AlterCmd::get_delete_attr_type(const std::string& attr_type) const {
    AlterCmd::Delete_attr_type theAttrType = deleteAttrType(attr_type);
    if (theAttrType == AlterCmd::DELETE_ATTR_ND) {
        std::stringstream ss;
        ss << "Alter: delete: The second argument must be one of [ ";
        std::vector<std::string> valid;
        validDeleteAttr(valid);
        for (size_t i = 0; i < valid.size(); ++i) {
            if (i != 0)
                ss << " | ";
            ss << valid[i];
        }
        ss << "] but found " << attr_type << "\n" << AlterCmd::desc();
        throw std::runtime_error(ss.str());
    }
    return theAttrType;
}

void AlterCmd::createDelete(Cmd_ptr& cmd,
                            const std::vector<std::string>& options,
                            const std::vector<std::string>& paths) const {
    // options[0] = delete
    // options[1] = [ variable | time | today | date | day | cron | event | meter | label | trigger | complete |
    //                repeat | limit | limit_path | inlimit | zombie | late ]
    // options[2] = name ( of object to be deleted ) optional
    // options[3] = limit_path (optional *ONLY* applicable for limit_path), specifies the path to be deleted

    AlterCmd::Delete_attr_type theAttrType = get_delete_attr_type(options[1]);

    // Generally an empty third argument means delete all attributes, otherwise delete the specific one.
    std::string name, value;
    try {
        extract_name_and_value_for_delete(theAttrType, name, value, options, paths);
        check_for_delete(theAttrType, name, value);
    }
    catch (std::exception& e) {
        std::stringstream ss;
        ss << "AlterCmd: delete: Could not parse " << name << ". Error: " << e.what()
           << "\n for time,today and date the new value should be a quoted string\n"
           << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }
    cmd = std::make_shared<AlterCmd>(paths, theAttrType, name, value);
}

void AlterCmd::extract_name_and_value_for_delete(AlterCmd::Delete_attr_type theAttrType,
                                                 std::string& name,
                                                 std::string& value,
                                                 const std::vector<std::string>& options,
                                                 const std::vector<std::string>& paths) const {
    // Generally an empty third argument means delete all attributes, otherwise delete the specific one.
    if (options.size() >= 3)
        name = options[2];

    // Deleting the limit path requires an additional arg
    std::string path_value;

    // if specified make sure its parses
    if (theAttrType == AlterCmd::DEL_LIMIT_PATH) {
        if (name.empty()) {
            std::stringstream ss;
            ss << "Delete limit_path failed. No limit name provided. Expected 5 args: delete limit_path <limit_name> "
                  "<path-to-limit> <path_to_node>\n";
            ss << dump_args(options, paths) << "\n";
            throw std::runtime_error(ss.str());
        }

        std::vector<std::string> altered_path = paths;
        if (options.size() == 4) {
            // User has provided a limit path which does not start with '/'. Go with flow
            path_value = options[3];
        }
        else {
            // Since we have a limit path(i.e begins with'/') it will appear in the paths, as the first path
            if (paths.size() <= 1) {
                std::stringstream ss;
                ss << "Delete limit_path failed: No path to limit provided. Expected 5 args: delete limit_path "
                      "<limit_name> <path-to-limit> <path_to_node>\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            path_value = paths[0];

            // Change paths to remove the limit path.
            altered_path.erase(altered_path.begin());
        }
        value = path_value;
    }
}

void AlterCmd::check_for_delete(AlterCmd::Delete_attr_type theAttrType,
                                const std::string& name,
                                const std::string& value) const {
    switch (theAttrType) {
        case AlterCmd::DEL_VARIABLE: {
            if (!name.empty())
                Variable check(name, ""); // Create a Variable to check valid names
            break;
        }
        case AlterCmd::DEL_TIME: {
            if (!name.empty())
                (void)TimeSeries::create(name); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_TODAY: {
            if (!name.empty())
                (void)TimeSeries::create(name); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_DATE: {
            if (!name.empty())
                (void)DateAttr::create(name); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_DAY: {
            if (!name.empty())
                (void)DayAttr::create(name); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_CRON: {
            if (!name.empty()) {
                CronAttr parsedCron = CronAttr::create(name); // will throw if not valid

                // additional check since parsing is very forgiving. if parsed string is same as default
                // then no cron was specified.
                CronAttr emptyCron;
                if (emptyCron.structureEquals(parsedCron)) {
                    throw std::runtime_error("Delete cron Attribute failed. Check cron " + name);
                }
            }
            break;
        }
        case AlterCmd::DEL_EVENT: {
            if (!name.empty()) {
                Event check(name); // will throw if not valid
            }
            break;
        }
        case AlterCmd::DEL_METER: {
            if (!name.empty())
                Meter check(name, 0, 100); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_LABEL: {
            if (!name.empty())
                Label check(name, "value"); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_TRIGGER:
            break; // there can only be one trigger per node, so we delete by path
        case AlterCmd::DEL_COMPLETE:
            break; // there can only be one complete per node, so we delete by path
        case AlterCmd::DEL_REPEAT:
            break; // there can only be one repeat per node, so we delete by path
        case AlterCmd::DEL_LATE:
            break; // there can only be one late per node, so we delete by path
        case AlterCmd::DEL_QUEUE: {
            if (!name.empty()) {
                std::vector<std::string> vec;
                vec.emplace_back("a");
                QueueAttr check(name, vec); // will throw if not valid
            }
            break;
        }
        case AlterCmd::DEL_GENERIC: {
            if (!name.empty())
                GenericAttr check(name); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_LIMIT: {
            if (!name.empty())
                Limit check(name, 10); // will throw if not valid
            break;
        }
        case AlterCmd::DEL_INLIMIT: {
            if (!name.empty()) {
                // name can be:
                //    limit_name
                //    /path/to/limit:limit_name
                string path_to_limit; // This can be empty
                string limitName;
                if (!Extract::pathAndName(name, path_to_limit, limitName)) {
                    throw std::runtime_error("AlterCmd::DEL_INLIMIT : Invalid inlimit : " + name);
                }
                InLimit check(limitName, path_to_limit); // will throw if not valid
            }
            break;
        }
        case AlterCmd::DEL_ZOMBIE: {
            if (!Child::valid_zombie_type(name)) {
                throw std::runtime_error(
                    "Delete Zombie Attribute failed. Expected one of [ ecf | path | user ] but found " + name);
            }
            break;
        }
        case AlterCmd::DEL_LIMIT_PATH: {
            if (name.empty()) {
                throw std::runtime_error("Delete limit_path failed. No limit name provided");
            }
            return;
        }
        case AlterCmd::DELETE_ATTR_ND:
            break;
    }
}

// =====================================================================================

AlterCmd::Change_attr_type AlterCmd::get_change_attr_type(const std::string& attr_type) const {
    AlterCmd::Change_attr_type theAttrType = changeAttrType(attr_type);
    if (theAttrType == AlterCmd::CHANGE_ATTR_ND) {
        std::stringstream ss;
        ss << "AlterCmd: change: The third argument(" << attr_type << ") must be one of [ ";
        std::vector<std::string> valid;
        validChangeAttr(valid);
        for (size_t i = 0; i < valid.size(); ++i) {
            if (i != 0)
                ss << " | ";
            ss << valid[i];
        }
        ss << "]\n" << AlterCmd::desc();
        throw std::runtime_error(ss.str());
    }
    return theAttrType;
}

void AlterCmd::createChange(Cmd_ptr& cmd, std::vector<std::string>& options, std::vector<std::string>& paths) const {
    // options[0] = change
    // options[1] = [ variable | clock_type | clock_gain | clock_date | clock_sync | event | meter | label | trigger |
    //                complete | repeat | limit_max | limit_value | defstatus | late ]
    // options[2] = name
    // options[3] = value

    AlterCmd::Change_attr_type theAttrType = get_change_attr_type(options[1]);

    std::string name, value;
    extract_name_and_value_for_change(theAttrType, name, value, options, paths);
    cmd = std::make_shared<AlterCmd>(paths, theAttrType, name, value);
}

void AlterCmd::extract_name_and_value_for_change(AlterCmd::Change_attr_type theAttrType,
                                                 std::string& name,
                                                 std::string& value,
                                                 std::vector<std::string>& options,
                                                 std::vector<std::string>& paths) const {
    std::stringstream ss;
    switch (theAttrType) {
        case AlterCmd::VARIABLE: {
            if (options.size() == 3 && paths.size() > 1) {
                // The variable value may be a path, and hence it will be paths and not options parameter
                options.push_back(paths[0]);
                paths.erase(paths.begin()); // remove first path, since it has been added to options
            }
            if (options.size() < 3 || options.size() > 4) {
                ss << "AlterCmd: change: expected 5 args : change variable <variable_name> <new_value> <path_to_node>";
                ss << " but found only " << (options.size() + paths.size())
                   << " arguments.\nThe value should be quoted if there are spaces\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            if (options.size() == 4)
                value = options[3];
            break;
        }

        case AlterCmd::CLOCK_TYPE: {
            if (options.size() != 3) {
                ss << "AlterCmd: change: expected at least four args i.e. change clock_type [ hybrid | real ] "
                      "<path_to_suite>";
                ss << " but found only " << (options.size() + paths.size()) << " arguments\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::CLOCK_DATE: {
            if (options.size() != 3) {
                ss << "AlterCmd: change clock_date : expected at least four args :  change clock_date day.month.year "
                      "<path_to_suite>";
                ss << " but found only " << (options.size() + paths.size()) << " arguments\n"
                   << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::CLOCK_GAIN: {
            if (options.size() != 3) {
                ss << "AlterCmd: change clock_gain : expected four args i.e. change clock_gain <int> <path_to_suite> ";
                ss << " but found " << (options.size() + paths.size())
                   << " arguments. The actual gain must be convertible to an integer\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::CLOCK_SYNC: {
            if (options.size() != 2) {
                ss << "AlterCmd: change clock_sync : expected three args i.e. change clock_sync  <path_to_suite> ";
                ss << " but found " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::EVENT: {
            if (options.size() != 3 && options.size() != 4) {
                ss << "AlterCmd: Change event : expected four/five args:  change event <name_or_number> <[set | clear "
                      "| <nothing>]> <path_to_node>";
                ss << " but found only " << (options.size() + paths.size()) << " arguments\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            if (options.size() == 4) {
                value = options[3];
            }
            break;
        }

        case AlterCmd::METER: {
            if (options.size() != 4) {
                ss << "AlterCmd: change: expected five args: change meter meter_name meter_value  <path_to_node>";
                ss << " but found only " << (options.size() + paths.size())
                   << " arguments. The meter value must be convertible to an integer\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name  = options[2];
            value = options[3];
            break;
        }

        case AlterCmd::LABEL: {
            if (options.size() == 3 && paths.size() == 1) {
                // ECFLOW-648 allow label value to be empty
                // HOWEVER , we cannot cope multiple paths, and setting value to empty.
                // since empty quotes are removed by boost program options, hence if we have a label value which is
                // path, and multiple paths
                value.clear();
            }
            else {
                // ECFLOW-480 take into account label values that is a path, adding quotes around the value does not
                // help: Note boost program options will remove the quotes around the value
                //      hence it's difficult to say what is an option and what is a path.
                //      However since we expect 4(change,label,<label_name>,<label_value>) options, work around the
                //      problem
                if (options.size() == 3 && paths.size() > 1) {
                    options.push_back(paths[0]);
                    paths.erase(paths.begin()); // remove first path, since it has been added to options
                }
                if (options.size() != 4) {
                    ss << "AlterCmd: change label expected at least five args : change label <label_name> "
                          "<label_value> <path_to_node> ";
                    ss << " but found  " << (options.size() + paths.size())
                       << " arguments. the label value should be quoted\n";
                    ss << dump_args(options, paths) << "\n";
                    throw std::runtime_error(ss.str());
                }
                value = options[3];
                if (value.find("\\n") != std::string::npos) {
                    Str::replaceall(value, "\\n", "\n");
                }
            }
            name = options[2];
            break;
        }

        case AlterCmd::LATE: {
            if (options.size() != 3) {
                ss << "AlterCmd: change: expected three args: change late \"late -s +00:15  -a  20:00  -c +02:00\" "
                      "<path_to_node>";
                ss << " but found only " << (options.size() + paths.size()) << " arguments\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::TRIGGER: {
            if (options.size() != 3) {
                ss << "AlterCmd: change: expected four args : change trigger 'expression' <path_to_node>";
                ss << " but found " << (options.size() + paths.size())
                   << " arguments. The trigger expression must be quoted\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::COMPLETE: {
            if (options.size() != 3) {
                ss << "AlterCmd: change complete: expected four args: change complete 'expression'  <path_to_node> ";
                ss << " but found " << (options.size() + paths.size()) << " arguments. The expression must be quoted\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::REPEAT: {
            // *NOTE* a Node can only have *ONE* repeat, hence no need to provide name
            if (options.size() != 3) {
                ss << "AlterCmd: change repeat: expected four arg's : change repeat [ integer | string ] "
                      "<path_to_node>";
                ss << " but found only " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::LIMIT_MAX: {
            if (options.size() != 4) {
                ss << "AlterCmd: change: limit_max: : expected five arguments : change limit_max <limit_name> <int> "
                      "<path_to_node>";
                ss << " but found  " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name  = options[2];
            value = options[3];
            break;
        }

        case AlterCmd::LIMIT_VAL: {
            if (options.size() != 4) {
                ss << "AlterCmd: change: limit-value: expected five arguments : change limit_value <limit_name> <int> "
                      "<path_to_node>";
                ss << " but found  " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name  = options[2];
            value = options[3];
            break;
        }

        case AlterCmd::TIME: {
            if (options.size() != 4) {
                ss << "AlterCmd: change: time: expected five arguments : change time old_time new_time <path_to_node>";
                ss << " but found  " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name  = options[2];
            value = options[3];
            break;
        }

        case AlterCmd::TODAY: {
            if (options.size() != 4) {
                ss << "AlterCmd: change: today: expected five arguments : change time old_today new_today "
                      "<path_to_node>";
                ss << " but found  " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name  = options[2];
            value = options[3];
            break;
        }

        case AlterCmd::DEFSTATUS: {
            if (options.size() != 3) {
                ss << "AlterCmd: change defstatus expected four args : change defstatus [ queued | complete | unknown "
                      "| aborted | suspended ] <path_to_node>";
                ss << " but found  " << (options.size() + paths.size()) << " arguments.\n";
                ss << dump_args(options, paths) << "\n";
                throw std::runtime_error(ss.str());
            }
            name = options[2];
            break;
        }

        case AlterCmd::CHANGE_ATTR_ND:
            break;
        default:
            break;
    }
}

void AlterCmd::check_for_change(AlterCmd::Change_attr_type theAttrType,
                                const std::string& name,
                                const std::string& value) const {
    std::stringstream ss;
    switch (theAttrType) {
        case AlterCmd::VARIABLE:
            break;
        case AlterCmd::CLOCK_TYPE: {
            if (name != "hybrid" && name != "real") {
                ss << "AlterCmd: change clock_type: expected third argument to be one of [ hybrid | real ] but found "
                   << name << "\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }
        case AlterCmd::CLOCK_DATE: {
            // Check date is in correct format:
            try {
                int day, month, year;
                DateAttr::getDate(name, day, month, year);
                DateAttr::checkDate(day, month, year, false /* for clocks, we don't allow wild carding */);
            }
            catch (std::exception& e) {
                ss << "AlterCmd:change  clock_date " << name << " is not valid. " << e.what();
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::CLOCK_GAIN: {
            try {
                ecf::convert_to<int>(name);
            }
            catch (const ecf::bad_conversion&) {
                ss << "AlterCmd:change  clock_gain expected '" << name << "' to be convertible to an integer\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::CLOCK_SYNC:
            break;

        case AlterCmd::EVENT: {
            if (!value.empty() && value != Event::SET() && value != Event::CLEAR()) {
                ss << "AlterCmd: Change event : expected  <[set | clear | <nothing>]> for the value";
                throw std::runtime_error(ss.str());
            }
            // The name could be an integer
            try {
                ecf::convert_to<int>(name);
            }
            catch (const ecf::bad_conversion&) {
                // name is not an integer, check name is valid
                Event check_name(name); // will throw if name is not valid
            }
            break;
        }

        case AlterCmd::METER: {
            Meter check(name, 0, 100); // Check meter name , by creating a meter
            try {
                ecf::convert_to<int>(value);
            }
            catch (const ecf::bad_conversion&) {
                ss << "AlterCmd change meter : " << value << " to be convertible to an integer\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::LABEL: {
            Label check(name, value); // Check name , by creating
            break;
        }

        case AlterCmd::LATE: {
            (void)LateAttr::create(name); // Check we can create the late
            break;
        }

        case AlterCmd::TIME: {
            (void)TimeSeries::create(name);
            (void)TimeSeries::create(value);
            break;
        }

        case AlterCmd::TODAY: {
            (void)TimeSeries::create(name);
            (void)TimeSeries::create(value);
            break;
        }

        case AlterCmd::TRIGGER: {
            std::string error_msg       = "AlterCmd: change trigger:";
            std::unique_ptr<AstTop> ast = Expression::parse_no_throw(name, error_msg);
            if (!ast.get()) {
                ss << error_msg << "\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::COMPLETE: {
            std::string error_msg       = "AlterCmd: change complete:";
            std::unique_ptr<AstTop> ast = Expression::parse_no_throw(name, error_msg);
            if (!ast.get()) {
                ss << error_msg << "\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::REPEAT: {
            // *NOTE* a Node can only have *ONE* repeat, hence no need to provide name
            break;
        }

        case AlterCmd::LIMIT_MAX: {
            int limit = 0;
            try {
                limit = ecf::convert_to<int>(value);
            }
            catch (const ecf::bad_conversion&) {
                ss << "AlterCmd: change: limit-max: expected " << value << " to be convertible to an integer\n";
                throw std::runtime_error(ss.str());
            }
            Limit check(name, limit); // Check name , by creating
            break;
        }

        case AlterCmd::LIMIT_VAL: {
            try {
                ecf::convert_to<int>(value);
            }
            catch (const ecf::bad_conversion&) {
                ss << "AlterCmd: change: limit_value: expected " << value << " to be convertible to an integer\n";
                throw std::runtime_error(ss.str());
            }
            Limit check(name, 10); // Check name, by creating
            break;
        }

        case AlterCmd::DEFSTATUS: {
            if (!DState::isValid(name)) {
                ss << "AlterCmd change defstatus : expected " << name
                   << " to be a valid state,  i.e one of [ queued | complete | unknown | aborted | suspended ]\n";
                throw std::runtime_error(ss.str());
            }
            break;
        }

        case AlterCmd::CHANGE_ATTR_ND:
            break;
        default:
            break;
    }
}

ecf::Flag::Type AlterCmd::get_flag_type(const std::string& flag_type) const {
    Flag::Type theFlagType = Flag::string_to_flag_type(flag_type);
    if (theFlagType == Flag::NOT_SET) {
        std::stringstream ss;
        ss << "AlterCmd: set/clear_flag: The second argument(" << flag_type << ") must be one of [ ";
        std::vector<std::string> valid;
        Flag::valid_flag_type(valid);
        for (size_t i = 0; i < valid.size(); ++i) {
            if (i != 0)
                ss << " | ";
            ss << valid[i];
        }
        ss << "]\n" << AlterCmd::desc();
        throw std::runtime_error(ss.str());
    }
    return theFlagType;
}

void AlterCmd::create_flag(Cmd_ptr& cmd,
                           const std::vector<std::string>& options,
                           const std::vector<std::string>& paths,
                           bool flag) const {
    // options[0] = set_flag | clear_flag
    // options[1] = [ force_aborted | user_edit | task_aborted | edit_failed | ecfcmd_failed | no_script | killed |
    //                late | message | complete | queue_limit | task_waiting | locked | zombie ]

    Flag::Type theFlagType = get_flag_type(options[1]);
    cmd                    = std::make_shared<AlterCmd>(paths, theFlagType, flag);
}

void AlterCmd::check_sort_attr_type(const std::string& attr_type) const {
    ecf::Attr::Type theAttrType = Attr::to_attr(attr_type);
    if (theAttrType == Attr::UNKNOWN) {
        std::stringstream ss;
        ss << "AlterCmd: sort: The second argument must be one of [ ";
        std::vector<std::string> valid = Attr::all_attrs();
        for (size_t i = 0; i < valid.size(); ++i) {
            if (i != 0)
                ss << " | ";
            ss << valid[i];
        }
        ss << "] but found " << attr_type << "\n" << AlterCmd::desc();
        throw std::runtime_error(ss.str());
    }
}

void AlterCmd::create_sort_attributes(Cmd_ptr& cmd,
                                      const std::vector<std::string>& options,
                                      const std::vector<std::string>& paths) const {
    // options[0] - sort
    // options[1] - [ event | meter | label | limit | variable | all ]
    // options[2] - recursive
    std::stringstream ss;
    if (options.size() < 2) {
        ss << "AlterCmd: add: At least three arguments expected. Found " << (options.size() + paths.size()) << "\n"
           << dump_args(options, paths) << "\n";
        throw std::runtime_error(ss.str());
    }

    check_sort_attr_type(options[1]);
    std::string name = options[1];

    std::string value;
    if (options.size() == 3) {
        if (options[2] != "recursive") {
            ss << "AlterCmd: sort: Expected third argument to be 'recursive' but found '" << options[2] << "\n"
               << AlterCmd::desc();
            throw std::runtime_error(ss.str());
        }
        value = "recursive";
    }

    cmd = std::make_shared<AlterCmd>(paths, name, value);
}

AlterCmd::AlterCmd(const std::vector<std::string>& paths,
                   const std::string& alterType, /* one of [ add | change | delete | set_flag | clear_flag ] */
                   const std::string& attrType,
                   const std::string& name,
                   const std::string& value)
    : paths_(paths),
      name_(name),
      value_(value) {
    if (alterType == "add") {

        add_attr_type_ = get_add_attr_type(attrType);
        check_for_add(add_attr_type_, name, value);
        return;
    }
    else if (alterType == "change") {

        change_attr_type_ = get_change_attr_type(attrType);
        check_for_change(change_attr_type_, name, value);
        return;
    }
    else if (alterType == "delete") {

        del_attr_type_ = get_delete_attr_type(attrType);
        check_for_delete(del_attr_type_, name, value);
        return;
    }
    else if (alterType == "set_flag") {

        flag_type_ = get_flag_type(attrType);
        flag_      = true;
        return;
    }
    else if (alterType == "clear_flag") {

        flag_type_ = get_flag_type(attrType);
        return;
    }

    std::stringstream ss;
    ss << "AlterCmd constructor: The alterType argument must be one of [ change | delete | add | set_flag | clear_flag "
          "| sort ] but found '"
       << alterType << "'\n";
    throw std::runtime_error(ss.str());
}

std::ostream& operator<<(std::ostream& os, const AlterCmd& c) {
    std::string ret;
    c.print(ret);
    os << ret;
    return os;
}

CEREAL_REGISTER_TYPE(AlterCmd)
CEREAL_REGISTER_DYNAMIC_INIT(AlterCmd)
