/*
 * 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 <buildboxcasd_cmdlinespec.h>
#include <buildboxcommon_digestgenerator.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_logging_commandline.h>

namespace buildboxcasd {

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

CmdLineSpec::CmdLineSpec(
    const buildboxcommon::ConnectionOptionsCommandLine &commonClientSpec,
    const buildboxcommon::ConnectionOptionsCommandLine &casClientSpec,
    const buildboxcommon::ConnectionOptionsCommandLine &raClientSpec,
    const buildboxcommon::ConnectionOptionsCommandLine &acClientSpec,
    const buildboxcommon::ConnectionOptionsCommandLine &execClientSpec)
{
    // consume the buildboxcommon::ConnectionOptions specs

    // Common:
    d_spec.insert(d_spec.end(), commonClientSpec.spec().begin(),
                  commonClientSpec.spec().end());

    // CAS:
    d_spec.insert(d_spec.end(), casClientSpec.spec().begin(),
                  casClientSpec.spec().end());
    d_spec.emplace_back("server-instance",
                        "[Deprecated] Use --local-server-instance.",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("local-server-instance",
                        "Create an additional local CAS server instance "
                        "(useful when --cas-remote is specified).",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back(
        "digest-function",
        "Set a custom digest function. Default: " +
            buildboxcommon::DigestFunction_Value_Name(
                BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE) +
            "\nSupported functions: " +
            buildboxcommon::DigestGenerator::supportedDigestFunctionsList(),
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);

    // Action Cache:
    d_spec.insert(d_spec.end(), acClientSpec.spec().begin(),
                  acClientSpec.spec().end());

    // Remote Asset:
    d_spec.insert(d_spec.end(), raClientSpec.spec().begin(),
                  raClientSpec.spec().end());

    // Execution:
    d_spec.insert(d_spec.end(), execClientSpec.spec().begin(),
                  execClientSpec.spec().end());

    d_spec.emplace_back(
        "proxy-instance",
        "Name of proxy instance. Multiple values of "
        "PROXY_INSTANCE[:REMOTE_INSTANCE] can be specified. "
        "If REMOTE_INSTANCE exists, the proxy instance uses it "
        "for remote connections, overriding --*instance options.",
        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("bind",
                        "Bind to address:port or UNIX socket in unix:path. "
                        "Multiple addresses can be specified.",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back(
        "unix-socket-group",
        "If binding to a UNIX socket, update its group ownership",
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("unix-socket-mode",
                        "If binding to a UNIX socket, update its mode in "
                        "octal format e.g. 0775",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("permit-keepalive-time",
                        "The minimum allowed time in seconds between "
                        "keepalive pings from clients",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    d_spec.emplace_back("quota-high",
                        "Maximum local cache size (e.g., 50G or 2T)",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("quota-low",
                        "Local cache size to retain on LRU expiry, either as "
                        "absolute size or as percentage of `quota-high`",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue("50%"));
    d_spec.emplace_back("reserved",
                        "Reserved disk space (headroom left when "
                        "`quota-high` gets automatically reduced)",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue("2G"));
    d_spec.emplace_back("protect-session-blobs",
                        "Do not expire blobs created or "
                        "used in the current session",
                        TypeInfo(DataType::COMMANDLINE_DT_BOOL),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITHOUT_ARG,
                        DefaultValue(false));
    d_spec.emplace_back("fail-fast-during-cleanup",
                        "Immediately reject write requests with "
                        "RESOURCE_EXHAUSTED when LRU cleanup is in progress",
                        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 to enable the "
                        "local execution service",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    d_spec.emplace_back(
        "runner-arg",
        "Arguments to pass buildbox-run when buildbox-casd runs a job",
        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back(
        "jobs",
        "Maximum number of jobs to run concurrently for local execution",
        TypeInfo(DataType::COMMANDLINE_DT_INT), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG, DefaultValue(4));
    d_spec.emplace_back(
        "exec-hybrid-queue-limit",
        "Maximum queue length for local execution in proxy instance",
        TypeInfo(DataType::COMMANDLINE_DT_INT), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back(
        "exec-fallback-to-local",
        "Fallback to local execution if the remote server is unable to "
        "schedule the clients request (e.g. due to service throttling). "
        "Fallback executions are excluded from the --exec-hybrid-queue-limit "
        "value. Requires that --buildbox-run is set.",
        TypeInfo(DataType::COMMANDLINE_DT_BOOL), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITHOUT_ARG, DefaultValue(false));
    d_spec.emplace_back(
        "cache-failures", "Cache local actions with non-zero exit code",
        TypeInfo(DataType::COMMANDLINE_DT_BOOL), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG, DefaultValue(true));
    d_spec.emplace_back("findmissingblobs-cache-ttl",
                        "Number of seconds to cache the results of\n"
                        "FindMissingBlobs() calls when in proxy mode",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(0));
    d_spec.emplace_back(
        "capture-allow-file-moves",
        "Attempt to move files into the CAS when a LocalCAS capture request "
        "has its `move_files` flag set. This will avoid a copy only when a "
        "file is in the same filesystem as the LOCAL_CACHE directory and "
        "owned by the user that is running casd. NOTE: Only enable if clients "
        "can be trusted to not break the consistency of the storage.",
        TypeInfo(DataType::COMMANDLINE_DT_BOOL), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITHOUT_ARG, DefaultValue(false));
    d_spec.emplace_back(
        "metrics-mode",
        "Metrics Mode: --metrics-mode=MODE - options for MODE are\n"
        "udp://<hostname>:<port>\nfile:///path/to/file\nstderr",
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);
    const int DEFAULT_METRICS_PUBLISH_INTERVAL = 15;
    d_spec.emplace_back("metrics-publish-interval",
                        "Metrics publishing interval",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(DEFAULT_METRICS_PUBLISH_INTERVAL));
    d_spec.emplace_back("metrics-prefix", "Prefix of metric names",
                        TypeInfo(DataType::COMMANDLINE_DT_STRING),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue("buildboxcasd"));
    d_spec.emplace_back("read-only-remote",
                        "Specifies if casd should update the remote "
                        "CAS with local build artifacts",
                        TypeInfo(DataType::COMMANDLINE_DT_BOOL),
                        ArgumentSpec::O_OPTIONAL);
    d_spec.emplace_back("store-writable-objects",
                        "Store object files with writable mode (644)",
                        TypeInfo(DataType::COMMANDLINE_DT_BOOL),
                        ArgumentSpec::O_OPTIONAL);
    d_spec.emplace_back("num-digest-threads",
                        "The number of threads to hash files to digests. "
                        "Default=min(hardware-concurrency,16)",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("num-io-threads",
                        "The number of threads for proxy CAS instances to "
                        "upload or download blobs from/to remote CAS. For "
                        "now, only upload is parallelized."
                        "Default=min(hardware-concurrency*2,32)",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back(
        "shutdown-deadline",
        "Forcefully terminate pending requests the specified number of "
        "seconds after requesting shutdown (SIGINT/SIGTERM)",
        TypeInfo(DataType::COMMANDLINE_DT_INT), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);

    d_spec.emplace_back(
        "allow-pre-unstage-command",
        "A list of _absolute paths_ to executables that are "
        "allowed to run before staged directories are unstaged.",
        TypeInfo(DataType::COMMANDLINE_DT_STRING_ARRAY),
        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);
    d_spec.emplace_back("ac-validation-cache-ttl",
                        "Number of seconds to cache the results of "
                        "GetActionResult() validations when in proxy mode",
                        TypeInfo(DataType::COMMANDLINE_DT_INT),
                        ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG,
                        DefaultValue(0));

    auto loggingSpec = buildboxcommon::loggingCommandLineSpec();
    d_spec.insert(d_spec.end(), loggingSpec.cbegin(), loggingSpec.cend());

    d_spec.emplace_back("", "Cache path", TypeInfo(&d_cachePath),
                        ArgumentSpec::O_REQUIRED);
}

} // namespace buildboxcasd
