/*
 * Copyright 2022 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.
 */

#ifndef INCLUDED_TREXE_EXECUTIONOPTIONS
#define INCLUDED_TREXE_EXECUTIONOPTIONS

#include <cstddef>
#include <memory>
#include <optional>
#include <ostream>
#include <set>
#include <string>
#include <unordered_map>

#include <buildboxcommon_commandline.h>
#include <buildboxcommon_connectionoptions.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_merklize.h>
#include <buildboxcommon_protos.h>
#include <buildboxcommon_version.h>
#include <toml++/toml.h>

#include <trexe_cmdlinespec.h>
#include <vector>

namespace trexe {

const std::string TREXE_METADATA_TOOL_NAME = "trexe";
constexpr unsigned int DEFAULT_MAX_NUM_DIGEST_THREADS = 16;
constexpr unsigned int DEFAULT_MAX_NUM_IO_THREADS = 32;

/**
 * What will trexe do, determined by the combination of options
 */
enum class TrexeMode {
    // Cancel the async operation
    CANCEL,
    // Wait for the async operation to finish and download the results
    WAIT_DOWNLOAD,
    // Download the results from the async operation
    // If the execution is not finished, exit immediately
    DOWNLOAD,
    // Run execution locally with caching
    CACHE_ONLY_EXECUTION,
    // Submit a remote execution and wait until it's finished
    REMOTE_EXECUTION_BLOCKING,
    // Submit a remote execution and print the operation-id to stdout
    REMOTE_EXECUTION_ASYNC,
};

struct InputPathOption {
    // Mask file permissions inside of the input tree.
    // Directory permissions are not affected.
    enum class FilePermission {
        NONE,       // Don't capture Unix mode
        CAPTURE,    // Capture mode, without further masking
        READ_ONLY,  // Mask with readonly bits
        READ_WRITE, // Mask with read and write bits
    };

    InputPathOption(const std::string &local,
                    const std::optional<std::string> &remote = {},
                    FilePermission permission = FilePermission::NONE,
                    bool captureMtime = false,
                    std::map<std::string, std::string> nodeProperties = {},
                    std::shared_ptr<std::vector<buildboxcommon::IgnorePattern>>
                        ignorePatterns = nullptr)
        : d_local(local), d_remote(remote), d_permission(permission),
          d_captureMtime(captureMtime), d_nodeProperties(nodeProperties)
    {
        if (ignorePatterns != nullptr) {
            d_ignoreMatcher = std::make_shared<buildboxcommon::IgnoreMatcher>(
                d_local, ignorePatterns);
        }
    }
    std::string d_local;
    std::optional<std::string> d_remote = {};
    FilePermission d_permission = FilePermission::NONE;
    bool d_captureMtime;
    std::map<std::string, std::string> d_nodeProperties;
    std::shared_ptr<buildboxcommon::IgnoreMatcher> d_ignoreMatcher;

    static InputPathOption fromCLI(const std::string &s);
    static FilePermission filePermissionFromString(const std::string &s);

    bool operator==(const InputPathOption &other) const;
};

std::ostream &operator<<(std::ostream &os,
                         const InputPathOption &inputPathOption);

struct ExecutionOptions {

    ExecutionOptions()
        : d_numDigestThreads(std::min(std::thread::hardware_concurrency(),
                                      DEFAULT_MAX_NUM_DIGEST_THREADS)),
          d_numIOThreads(std::min(2 * std::thread::hardware_concurrency(),
                                  DEFAULT_MAX_NUM_IO_THREADS))
    {
        d_priority = std::make_shared<int>(0);
        d_metadata["tool-name"] = TREXE_METADATA_TOOL_NAME;
        d_metadata["tool-version"] = buildboxcommon::VERSION;
        // TODO: refactor so empty strings don't have to be prepopulated
        d_metadata["tool-invocation-id"] = "";
        d_metadata["correlated-invocations-id"] = "";
    }

    /**
     * Check that the combination of options are valid.
     *
     * Throws an `std::runtime` exception if options are invalid.
     */
    void validate() const;

    /**
     * Get the trexe mode
     */
    TrexeMode mode() const;

    /*
     * Return whether trexe waits a running execution
     */
    bool isBlocking() const;

    bool d_cancelMode = false;
    bool d_cacheOnly = false;
    bool d_skipCacheLookup = false;
    bool d_doNotCache = false;
    bool d_followSymlinks = true; // default to true for backward compatibility
    buildboxcommon::LogLevel d_logLevel = buildboxcommon::LogLevel::ERROR;
    std::string d_downloadResultsPath; // do not download when empty
    std::vector<std::string> d_argv;
    std::string d_workingDir = ".";
    std::vector<InputPathOption> d_inputPaths;
    std::string d_inputRootDigest;
    std::set<std::string> d_outputPaths;
    std::set<std::string> d_outputNodeProperties;
    std::string d_operation;
    std::set<std::pair<std::string, std::string>> d_platform;
    std::map<std::string, std::string> d_environment;
    int d_execTimeout = 0;
    std::string d_salt;
    // TODO: why is this a pointer?
    std::shared_ptr<int> d_priority;
    std::unordered_map<std::string, std::string> d_metadata;
    // Scheduling metadata, containing locality hint
    build::buildgrid::SchedulingMetadata d_schedulingMetadata;
    std::string d_resultMetadataFile;
    std::string d_actionResultJsonFile;
    std::string d_runnerCommand = "buildbox-run";
    std::string d_stdoutFile;
    std::string d_stderrFile;
    std::vector<std::string> d_extraRunArgs;
    bool d_streamLogs = false;
    bool d_logProgress = false;
    bool d_useLocalCas = false;
    bool d_localCasSkipUpload = false;
    size_t d_numDigestThreads;
    size_t d_numIOThreads;

    const int DEFAULT_METRICS_PUBLISH_INTERVAL = 15;
    std::string d_metricMode = "";
    int d_metricPublishInterval = DEFAULT_METRICS_PUBLISH_INTERVAL;
    std::string d_metricPrefix = "trexe";

    // REAPI connections
    buildboxcommon::ConnectionOptions d_execConn;
    buildboxcommon::ConnectionOptions d_casConn;
    buildboxcommon::ConnectionOptions d_acConn;
    buildboxcommon::ConnectionOptions d_lsConn;

    buildboxcommon::DigestFunction_Value d_digestFunction =
        static_cast<buildboxcommon::DigestFunction_Value>(
            BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE);

    static ExecutionOptions
    fromCommandLine(const std::vector<std::string> &,
                    const buildboxcommon::CommandLine &);

    static ExecutionOptions newFromTOML(const toml::table &table);
    static void updateFromTOML(const toml::table &table,
                               ExecutionOptions &options);

    friend std::ostream &operator<<(std::ostream &, const ExecutionOptions &);

  private:
    // access by getter
    std::optional<bool> d_blockingExecute = std::nullopt;
};

} // namespace trexe

#endif
