/*
 * Copyright 2019 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_BUILDBOXCOMMON_FUSESTAGER_H
#define INCLUDED_BUILDBOXCOMMON_FUSESTAGER_H

#include <buildboxcommon_filestager.h>
#include <buildboxcommon_localcas.h>
#include <buildboxcommon_temporaryfile.h>

#include <mutex>
#include <sys/stat.h>
#include <unordered_set>

namespace buildboxcommon {

class FuseStager final : public FileStager {
  private:
    static const std::string s_buildboxFuseBinaryName;

  public:
    explicit FuseStager(LocalCas *cas_storage);

    ~FuseStager() override = default;

    class FuseStagedDirectory : public FileStager::StagedDirectory {
      public:
        explicit FuseStagedDirectory(FuseStager *stager,
                                     TemporaryFile *digest_file,
                                     const std::string &path,
                                     const pid_t stager_pid,
                                     const dev_t fuse_dev);

        ~FuseStagedDirectory() override;
        // disallow copy and move
        FuseStagedDirectory(const FuseStagedDirectory &) = delete;
        FuseStagedDirectory &operator=(const FuseStagedDirectory &) = delete;
        FuseStagedDirectory(FuseStagedDirectory &&) = delete;
        FuseStagedDirectory &operator=(FuseStagedDirectory &&) = delete;

      private:
        FuseStager *d_stager;
        const std::string d_staged_path;
        const std::unique_ptr<TemporaryFile> d_digest_file;
        const pid_t d_stager_pid;
        const dev_t d_fuse_dev;
    };

    std::unique_ptr<StagedDirectory>
    stage(const Digest &root_digest, const std::string &path,
          const std::optional<ProcessCredentials> &accessCredentials =
              std::nullopt,
          const FileStagedCallback &callback = std::nullopt) override;

    void addActiveDevice(dev_t dev);
    void removeActiveDevice(dev_t dev);
    std::string getHashXattrName(int dirfd) override;

    class StagerBinaryNotAvailable : public std::exception {
      public:
        StagerBinaryNotAvailable(const std::string message =
                                     "Could not find FUSE stager binary \"" +
                                     s_buildboxFuseBinaryName + "\"")
            : d_message(message)
        {
        }

        const char *what() const noexcept override
        {
            return d_message.c_str();
        }

      private:
        const std::string
            d_message; // NOLINT
                       // (cppcoreguidelines-avoid-const-or-ref-data-members)
    };

  private:
    std::string d_buildboxFusePath;

    std::unordered_set<dev_t> d_activeDevices;
    std::mutex d_activeDevicesMutex;

    pid_t launchStager(const std::string &digest_file_path,
                       const std::string &stage_path, struct stat *st);

    std::vector<std::string>
    prepareCommand(const std::string &digest_file_path,
                   const std::string &stage_path) const;

    std::unique_ptr<buildboxcommon::TemporaryFile>
    writeDigestToTemporaryFile(const Digest &digest) const;
};
} // namespace buildboxcommon

#endif // INCLUDED_BUILDBOXCOMMON_FUSESTAGER_H
