/*
 * Copyright 2020 Bloomberg Finance LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <buildboxworker_cmdlinespec.h>

#include <buildboxcommon_connectionoptions_commandline.h>
#include <buildboxcommon_digestgenerator.h>

namespace buildboxworker {

using ArgumentSpec = buildboxcommon::CommandLineTypes::ArgumentSpec;
using DataType = buildboxcommon::CommandLineTypes::DataType;
using TypeInfo = buildboxcommon::CommandLineTypes::TypeInfo;
using DefaultValue = buildboxcommon::CommandLineTypes::DefaultValue;

CmdLineSpec::CmdLineSpec()
{
    d_spec.emplace_back("instance",
                        "Instance name to pass to the Remote Workers API and "
                        "the CAS servers",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(""));
    d_spec.emplace_back(
        "digest-function",
        "Set a custom digest function. Supported functions: " +
            buildboxcommon::DigestGenerator::supportedDigestFunctionsList(),
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG,
        DefaultValue(buildboxcommon::DigestFunction_Value_Name(
            BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE)));
    d_spec.emplace_back(
        "concurrent-jobs",
        "Number of jobs to run at once, defaults to running one job at a time",
        TypeInfo(DataType::COMMANDLINE_DT_INT), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG, DefaultValue(1));
    d_spec.emplace_back("stop-after",
                        "Terminate after running the given number of jobs, "
                        "defaults to non-terminating",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(-1));
    d_spec.emplace_back("consecutive-errors-threshold",
                        "Mark worker as UNHEALTHY after N consecutive errors, "
                        "disabled by default (0 = disabled)",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(0));
    d_spec.emplace_back("priority",
                        "Worker priority for job scheduling. A lower value "
                        "means that the worker should be more prioritized "
                        "than workers having a greater priority value.",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(0));
    d_spec.emplace_back(
        "disk-usage-health-check",
        "Mark worker as UNHEALTHY if disk usage exceeds threshold.\n"
        "Format: --disk-usage-health-check=/path:80%\n"
        "Can be specified multiple times for different paths.",
        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    // default connection
    const auto connectionOptionsSpec =
        buildboxcommon::ConnectionOptionsCommandLine("", "");
    d_spec.insert(d_spec.end(), connectionOptionsSpec.spec().cbegin(),
                  connectionOptionsSpec.spec().cend());

    // Bots connection:
    const auto botsConnectionOptionsSpec =
        buildboxcommon::ConnectionOptionsCommandLine("Bots", "bots-");
    d_spec.insert(d_spec.end(), botsConnectionOptionsSpec.spec().cbegin(),
                  botsConnectionOptionsSpec.spec().cend());

    // CAS connection:
    const auto casConnectionOptionsSpec =
        buildboxcommon::ConnectionOptionsCommandLine("CAS", "cas-");
    d_spec.insert(d_spec.end(), casConnectionOptionsSpec.spec().cbegin(),
                  casConnectionOptionsSpec.spec().cend());

    // LogStream connection (optional):
    const auto logStreamConnectionOptionsSpec =
        buildboxcommon::ConnectionOptionsCommandLine("LogStream",
                                                     "logstream-");
    d_spec.insert(d_spec.end(), logStreamConnectionOptionsSpec.spec().cbegin(),
                  logStreamConnectionOptionsSpec.spec().cend());

    // Asset connection (optional):
    const auto assetConnectionOptionsSpec =
        buildboxcommon::ConnectionOptionsCommandLine("Asset", "ra-");
    d_spec.insert(d_spec.end(), assetConnectionOptionsSpec.spec().cbegin(),
                  assetConnectionOptionsSpec.spec().cend());

    const int DEFAULT_MAX_WAIT_TIME = 60;
    d_spec.emplace_back(
        "max-wait-time",
        "Sets the maximum amount of time in seconds between worker "
        "polling the Bots service.",
        TypeInfo(DataType::COMMANDLINE_DT_INT), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG, DefaultValue(DEFAULT_MAX_WAIT_TIME));

    // Graceful shutdown timeout to upper-limit wait time on jobs running
    // after shutdown is requested (optional)
    d_spec.emplace_back(
        "graceful-shutdown-timeout",
        "Sets the amount of time in seconds to wait for ongoing executions to "
        "finish during a graceful shutdown, before escalating to a forceful "
        "shutdown. If not provided, ongoing executions will run to completion "
        "during graceful shutdown.",
        TypeInfo(DataType::COMMANDLINE_DT_INT), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);

    // Preserve UpdateBotSession RPC on execution completion.
    // Mainly for when a client depends heavily on proactive fetching,
    // in which case UpdateBotSession is expensive and prone to concurrency
    // bugs
    d_spec.emplace_back(
        "preserve-update-bot-session",
        "When an execution is completed, the previous UpdateBotSession RPC "
        "will be preserved. If not provided, the previous RPC will be "
        "cancelled.",
        TypeInfo(DataType::COMMANDLINE_DT_BOOL), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITHOUT_ARG, DefaultValue(false));

    d_spec.emplace_back("buildbox-run", "Absolute path to runner executable",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_REQUIRED, ArgumentSpec::C_WITH_ARG);

    d_spec.emplace_back(
        "runner-arg",
        "Arguments to pass buildbox-run when the worker runs a job\n"
        "This can be useful if the buildbox-run implementation you're using "
        "supports non-standard options",
        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("platform",
                        "Add a key-value pair to the 'Platform' message the "
                        "worker sends to the server\n"
                        "(see the Remote Workers API specification for keys "
                        "and values you can use)"
                        "\nformat is --platform KEY=VALUE",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING_PAIR_ARRAY),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back(
        "metrics-mode",
        "format is --metrics-mode=MODE where 'MODE' is one of:\n"
        "udp://<hostname>:<port>\nfile:///path/to/file\nstderr",
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("metrics-publish-interval",
                        "Metrics publishing interval",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("metrics-statsd-format",
                        "if set to any value, the format used by statsd "
                        "publisher, when tagging is set. Supports "
                        "'influx', 'graphite' and 'dog', defaults to 'influx'",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue("influx"));
    d_spec.emplace_back("log-level", "Log verbosity level",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue("info"));
    d_spec.emplace_back("verbose", "Set log level to debug",
                        TypeInfo(DataType::COMMANDLINE_DT_BOOL),
                        ArgumentSpec::O_OPTIONAL);
    d_spec.emplace_back("log-directory",
                        "Write logs to this directory with filenames "
                        "'<program name>.<hostname>.<user "
                        "name>.log.<severity level>.<date>.<time>.<pid>'",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(""));
    d_spec.emplace_back("config-file", "Absolute path to config file",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(""));

#ifdef LOGSTREAM_DEBUG
    d_spec.emplace_back(
        "launch-logstream-command",
        "When streaming outputs, launch this command using system() and wait "
        "for it to return before invoking the runner. A \"{}\" placeholder "
        "will be replaced with the LogStream write resource name. (E.g.: "
        "\"/usr/bin/logstream-reader --stream={}\")",
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);
#endif

    d_spec.emplace_back("", "BOT Id", TypeInfo(&d_botId),
                        ArgumentSpec::O_OPTIONAL);
};

} // namespace buildboxworker
