/*
 * Copyright 2018 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_FALLBACKSTAGEDDIRECTORY
#define INCLUDED_BUILDBOXCOMMON_FALLBACKSTAGEDDIRECTORY

#include <buildboxcommon_casclient.h>
#include <buildboxcommon_stageddirectory.h>
#include <buildboxcommon_temporarydirectory.h>

#include <dirent.h>
#include <functional>
#include <memory>
#include <vector>

namespace buildboxcommon {
/**
 * An implementation of StagedDirectory that uses only the base CAS protocol
 * from the Remote Execution API specification.
 */
class FallbackStagedDirectory : public StagedDirectory {
  public:
    /**
     * Download the directory with the given digest from CAS, to location on
     * disk specified by path.
     */
    FallbackStagedDirectory(
        const Digest &digest, const std::string &path,
        std::shared_ptr<CASClient> cas_client,
        const std::vector<std::string> &preUnstageCommands);

    ~FallbackStagedDirectory() override;

    // Rule of Five: Delete copy constructor and copy assignment operator
    FallbackStagedDirectory(const FallbackStagedDirectory &) = delete;
    FallbackStagedDirectory &
    operator=(const FallbackStagedDirectory &) = delete;

    // Rule of Five: Delete move constructor and move assignment operator
    FallbackStagedDirectory(FallbackStagedDirectory &&other) = delete;
    FallbackStagedDirectory &
    operator=(FallbackStagedDirectory &&other) = delete;

    OutputFile captureFile(const char *relative_path,
                           const Command &command) const override;
    OutputDirectory
    captureDirectory(const char *relative_path, const Command &command,
                     int64_t *directoryTotalSize) const override;

    /* Helpers marked as protected to unit test */

    OutputFile
    captureFile(const char *relative_path,
                const std::function<void(const int fd, const Digest &digest)>
                    &upload_file_function,
                const bool capture_mtime = false) const;

    OutputDirectory captureDirectory(
        const char *relative_path, const Command &command,
        const std::function<std::pair<Digest, Digest>(const std::string &path)>
            &upload_directory_function) const;

  protected:
    FallbackStagedDirectory();

    OutputFile captureFile(const char *relative_path,
                           const char *workingDirectory) const;

    // Upload a directory, returning the pair of (tree digest, root directory
    // digest). The tree digest is empty is outputDirFormat is DIRECTORY_ONLY
    // as it's not uploaded to CAS.
    std::pair<Digest, Digest>
    uploadDirectory(const std::string &path,
                    const Command_OutputDirectoryFormat outputDirFormat,
                    int64_t *directoryTotalSize) const;

  private:
    std::shared_ptr<CASClient> d_casClient;
    TemporaryDirectory d_stage_directory;

    std::vector<std::string> d_preUnstageCommands;

    int d_stage_directory_fd = 0;

    // Run pre-unstage commands with best efforts
    void runPreUnstageCommands() const noexcept;
};
} // namespace buildboxcommon

#endif
