// Copyright 2021 Bloomberg Finance L.P
//
// 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_EXECUTIONCONTEXT
#define INCLUDED_EXECUTIONCONTEXT

#include <atomic>
#include <grpcchannels.h>
#include <parsedcommand.h>
#include <protos.h>

#include <buildboxcommon_executionclient.h>
#include <buildboxcommon_grpcclient.h>
#include <buildboxcommon_protos.h>
#include <buildboxcommonmetrics_durationmetricvalue.h>

namespace recc {

/**
 * The ExecutionContext class holds the state for command execution.
 */
class ExecutionContext {
  public:
    ExecutionContext();
    virtual ~ExecutionContext();

    enum ParseConfigOption {
        PARSE_CONFIG,
        SKIP_PARSING,
    };

    ParseConfigOption getParseConfigOption();
    void disableConfigParsing();

    /**
     * Set the stop token, which cancels execution at the next possible point
     * when it becomes true.
     */
    void setStopToken(const std::atomic_bool &stop_requested);

    /**
     * Execute the specified command. Depending on the configuration, this may
     * use remote execution or local execution with caching.
     */
    int execute(int argc, char *argv[]);

    const std::map<
        std::string,
        buildboxcommon::buildboxcommonmetrics::DurationMetricValue> *
    getDurationMetrics() const;

    const std::map<std::string, int64_t> *getCounterMetrics() const;

    const buildboxcommon::Digest &getActionDigest() const;

    const buildboxcommon::ActionResult &getActionResult() const;

    buildboxcommon::CASClient *getCasClient() const;

  private:
    ParseConfigOption parseConfigOption = PARSE_CONFIG;
    const std::atomic_bool *d_stopRequested{};
    std::map<std::string,
             buildboxcommon::buildboxcommonmetrics::DurationMetricValue>
        d_durationMetrics;
    std::function<void(
        const std::string &,
        buildboxcommon::buildboxcommonmetrics::DurationMetricValue)>
        d_addDurationMetricCallback;
    std::map<std::string, int64_t> d_counterMetrics;
    std::function<void(const std::string &, int64_t)>
        d_recordCounterMetricCallback;
    buildboxcommon::Digest d_actionDigest;
    buildboxcommon::ActionResult d_actionResult;

    std::shared_ptr<buildboxcommon::CASClient> d_casClient;
    bool d_casdSameUserHint = false;

    int executeConfigured(int argc, char *argv[]);

    int execLocally(int argc, char *argv[]);

    buildboxcommon::ActionResult execLocallyWithActionResult(
        int argc, char *argv[], buildboxcommon::digest_string_map *blobs,
        buildboxcommon::digest_string_map *digest_to_filepaths,
        const std::set<std::string> &products,
        const std::set<std::string> &makeDependencyProducts,
        const std::string &modifiedUploadPath);

    template <typename Response>
    void populateHashesFromResponses(
        Response &responses, proto::ActionResult &actionResult,
        buildboxcommon::digest_string_map *digest_to_filepaths,
        std::unordered_map<std::string, std::string>
            &contentsPathToOutputPathMap);

    void uploadResources(
        const buildboxcommon::digest_string_map &blobs,
        const buildboxcommon::digest_string_map &digest_to_filepaths,
        const std::string &uploadBlobsCounterMetricName);

    int64_t calculateTotalSize(
        const buildboxcommon::digest_string_map &blobs,
        const buildboxcommon::digest_string_map &digest_to_filepaths);

    void addDurationMetric(
        const std::string &name,
        buildboxcommon::buildboxcommonmetrics::DurationMetricValue value);

    void recordCounterMetric(const std::string &name, int64_t value);

    std::shared_ptr<proto::Action>
    tryCreateAction(buildboxcommon::digest_string_map *blobs,
                    buildboxcommon::digest_string_map *digest_to_filepaths,
                    std::set<std::string> *products,
                    const ParsedCommand &command, const std::string &cwd);

    void
    setupCASClient(std::shared_ptr<GrpcChannels> *returnChannels,
                   std::shared_ptr<buildboxcommon::GrpcClient> *casGrpcClient);

    void setupExecClient(
        const GrpcChannels &returnChannels,
        std::shared_ptr<buildboxcommon::GrpcClient> *actionCacheGrpcClient,
        std::shared_ptr<buildboxcommon::ExecutionClient> *execClient,
        const proto::Digest &actionDigest, bool local_runner);

    void queryActionCache(
        bool *action_in_cache, buildboxcommon::ActionResult *result,
        const std::shared_ptr<buildboxcommon::ExecutionClient> &execClient,
        const buildboxcommon::Digest &actionDigest,
        const ParsedCommand &command);

    int executeLocallyAndUploadActionResult(
        int argc, char *argv[],
        const std::shared_ptr<buildboxcommon::ExecutionClient> &execClient,
        const std::set<std::string> &products,
        const std::set<std::string> &makeDependencyFiles,
        buildboxcommon::digest_string_map *blobs,
        buildboxcommon::digest_string_map *digest_to_filepaths);

    void serveOutputFromActionResult(
        std::shared_ptr<buildboxcommon::ExecutionClient> &execClient,
        proto::ActionResult &result, const std::set<std::string> &products,
        const std::set<std::string> &makeDependencyFiles);
};

} // namespace recc

#endif
