#include "processargs.h"

#include <buildboxcommon_commandlinetypes.h>
#include <buildboxcommon_connectionoptions_commandline.h>
#include <buildboxcommon_logging_commandline.h>

#include <regex>

namespace casdownload {

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

const std::string usageText =
    "Download the specified digest(root/tree/action/file) into the specified "
    "directory\n"
    "\n"
    "Action digest download expects that ActionCache is hosted on the same\n"
    "server as the CAS server.\n"
    "\n"
    "Example usage:\n"
    "  casdownload --remote=http://localhost:50051\n"
    "              --destination-dir=/path/to/output\n"
    "              --root-digest=deafbeef/123\n";

bool digestFromString(const std::string &s, buildboxcommon::Digest *digest)
{
    // "[hash in hex notation]/[size_bytes]"
    static const std::regex regex("^([0-9a-fA-F]+)/(\\d+)");
    std::smatch matches;
    if (std::regex_search(s, matches, regex) && matches.size() == 3) {
        const std::string hash = matches[1];
        const std::string size = matches[2];

        digest->set_hash(hash);
        digest->set_size_bytes(std::stoll(size));
        return true;
    }

    return false;
}

std::string stringFromDigestType(DigestType digestType)
{
    switch (digestType) {
        case DIGEST_ROOT:
            return "root";
        case DIGEST_TREE:
            return "tree";
        case DIGEST_ACTION:
            return "action";
        case DIGEST_FILE:
            return "file";
    }
    return "";
}

ProcessedArgs processArgs(int argc, char *argv[])
{
    // If this tool gets to use another API endpoint, this has to be changed
    // to be a common configuration, and separate CAS configuration added.
    //
    // NB: Third argument is not supplied because there is an obsolete
    // --cas-server argument that can be used instead of --remote.
    // Once --cas-server is removed, make --remote required by using
    // a three-argument constructor here.
    auto connectionOptionsCommandLine =
        buildboxcommon::ConnectionOptionsCommandLine("CAS", "");

    std::vector<buildboxcommon::CommandLineTypes::ArgumentSpec> spec;

    const auto &connectionOptionsSpec = connectionOptionsCommandLine.spec();

    spec.insert(spec.end(), connectionOptionsSpec.cbegin(),
                connectionOptionsSpec.cend());

    spec.emplace_back("destination-dir", "Directory to save downloaded data",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_REQUIRED, ArgumentSpec::C_WITH_ARG);

    spec.emplace_back("root-digest", "Download a tree by its root ID",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    spec.emplace_back(
        "tree-digest",
        "Download a tree from a single blob containing a tree message",
        TypeInfo(DataType::COMMANDLINE_DT_STRING), ArgumentSpec::O_OPTIONAL,
        ArgumentSpec::C_WITH_ARG);

    spec.emplace_back("action-digest",
                      "Download the stderr, stdout, output files and "
                      "directories of this digest via the ActionCache",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    spec.emplace_back("file-digest", "Download a blob by its content",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    spec.emplace_back("cas-server", "[Deprecated] Use --remote",
                      TypeInfo(DataType::COMMANDLINE_DT_STRING),
                      ArgumentSpec::O_OPTIONAL, ArgumentSpec::C_WITH_ARG);

    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);

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

    ProcessedArgs args = {};

    auto cl = buildboxcommon::CommandLine(spec, usageText);

    if (!cl.parse(argc, argv)) {
        cl.usage();
        return args;
    }

    if (cl.exists("help") || cl.exists("version")) {
        args.d_processed = true;
        return args;
    }

    if (!connectionOptionsCommandLine.configureChannel(
            cl, "", &args.d_casConnectionOptions)) {
        return args;
    }

    if (cl.exists("cas-server")) {
        std::cerr << "WARNING\n"
                  << "WARNING --cas-server option is deprecated. Use --remote "
                     "instead.\n"
                  << "WARNING\n";

        if (args.d_casConnectionOptions.d_url != "") {
            std::cerr << "WARNING\n"
                      << "WARNING --cas-server and --remote options are "
                         "redundant, --cas-server value is ignored.\n"
                      << "WARNING\n";
        }
        else {
            args.d_casConnectionOptions.d_url = cl.getString("cas-server");
        }
    }

    args.d_digestFunctionValue =
        cl.exists("digest-function")
            ? buildboxcommon::DigestGenerator::stringToDigestFunction(
                  cl.getString("digest-function"))
            : static_cast<buildboxcommon::DigestFunction_Value>(
                  BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE);

    if (args.d_casConnectionOptions.d_url == "") {
        std::cerr << "One of --cas-server and --remote is required\n";
        return args;
    }

    if (!buildboxcommon::parseLoggingOptions(cl, args.d_logLevel)) {
        return args;
    }

    std::string digest;
    std::vector<DigestType> inputedDigestTypes;

    if (cl.exists("root-digest")) {
        args.d_digestType = DIGEST_ROOT;
        digest = cl.getString("root-digest");
        inputedDigestTypes.push_back(args.d_digestType);
    }
    else if (cl.exists("tree-digest")) {
        args.d_digestType = DIGEST_TREE;
        digest = cl.getString("tree-digest");
        inputedDigestTypes.push_back(args.d_digestType);
    }
    else if (cl.exists("action-digest")) {
        args.d_digestType = DIGEST_ACTION;
        digest = cl.getString("action-digest");
        inputedDigestTypes.push_back(args.d_digestType);
    }
    else if (cl.exists("file-digest")) {
        args.d_digestType = DIGEST_FILE;
        digest = cl.getString("file-digest");
        inputedDigestTypes.push_back(args.d_digestType);
    }
    else {
        std::cerr << "Error: no digests were specified on the command line\n";
        cl.usage();
        return args;
    }

    if (!digestFromString(digest, &args.d_digest)) {
        std::cerr << "Malformed --root-digest, expected <hash>/<size>, got "
                  << digest << '\n';
        return args;
    }

    if (inputedDigestTypes.size() > 1) {
        std::stringstream ss;
        for (const auto digestType : inputedDigestTypes) {
            ss << stringFromDigestType(digestType) << " ";
        }
        std::cerr << ss.str() << "must be used individually, only "
                  << stringFromDigestType(args.d_digestType)
                  << "-digest will be used and all others ignored\n";
    }

    if (cl.getBool("verbose", false)) {
        args.d_logLevel = buildboxcommon::LogLevel::DEBUG;
    }

    args.d_destinationDir = cl.getString("destination-dir");
    args.d_valid = true;

    return args;
}

} // namespace casdownload
