/*
 * 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_LOCALCAS_H
#define INCLUDED_BUILDBOXCOMMON_LOCALCAS_H

#include <chrono>

#include <buildboxcommon_protos.h>
#include <buildboxcommon_temporarydirectory.h>
#include <buildboxcommon_temporaryfile.h>

namespace buildboxcommon {

using blob_time_type = std::chrono::system_clock::time_point;
using digest_time_callback_type =
    std::function<void(const Digest &, blob_time_type)>;

// Hold a file's name and it's directory's file descriptor
struct FdNamePair {
    FdNamePair(int fd, std::string name)
        : d_directory_fd(fd), d_file_name(name)
    {
    }

    int d_directory_fd;
    std::string d_file_name;
};

class LocalCas {
  public:
    virtual ~LocalCas() = default;

    // Returns whether the given Digest is stored in the local CAS.
    virtual bool hasBlob(const Digest &digest) = 0;

    /* Saves a blob in the storage and returns whether it had to actually
     * perform the write operation (i.e. if the file exists, it returns false.)
     *
     * In cases where the size reported in the Digest does not match the actual
     * size of the data, it raises an `std::invalid_argument` exception.
     *
     * If the write operation fails for another reason, it throws an
     * `std::runtime_error` exception.
     */
    virtual bool writeBlob(const Digest &digest, const std::string &data) = 0;
    virtual bool writeBlob(int fd, Digest *digest) = 0;

    /* Adds a blob to the CAS by moving a file. `path` must be inside
     * `d_temp_directory`.
     *
     * The caller must guarantee that the Digest is correct and that the file
     * is no longer modified.
     *
     * Returns `true` if the file was added to the CAS with this call, or
     * `false if it was already present.
     *
     * If the move operation fails, throws an `std::runtime_error` exception.
     */
    virtual bool
    moveBlobFromTemporaryFile(const Digest &digest,
                              buildboxcommon::TemporaryFile tempFile) = 0;
    virtual bool moveBlobFromTemporaryFile(
        const Digest &digest,
        const buildboxcommon::TemporaryDirectory &directory,
        const std::string &filename) = 0;

    /* Returns the corresponding data for a digest stored in the local CAS.
     *
     * If the digest is not present or the read fails, returns `nullptr`.
     *
     * If the optional parameter `limit` is set to
     * `buildboxcommon::LocalCas::npos`, it reads from `offset` until the end
     * of the data.
     *
     * Throws:
     *  - `std::out_of_range` if `offset` is larger than the data size
     */
    virtual std::unique_ptr<std::string> readBlob(const Digest &digest) = 0;

    virtual std::unique_ptr<std::string>
    readBlob(const Digest &digest, int64_t offset, size_t length) = 0;

    virtual bool copyBlob(const Digest &digest, int dirfd, const char *path,
                          mode_t mode = 0,
                          struct stat *destStat = nullptr) = 0;

    virtual bool deleteBlob(const Digest &digest) = 0;

    virtual bool deleteBlob(const Digest &digest,
                            const blob_time_type &if_unmodified_since) = 0;

    virtual void listBlobs(digest_time_callback_type callback) = 0;

    virtual int64_t getDiskUsage(bool force_scan = false) = 0;

    virtual std::optional<int64_t> getDiskQuota() = 0;

    virtual std::string path(const Digest &digest) = 0;

    virtual std::string pathUnchecked(const Digest &digest) = 0;

    virtual FdNamePair pathAt(const Digest &digest) = 0;

    virtual FdNamePair pathAtUnchecked(const Digest &digest) = 0;

    virtual TemporaryDirectory createStagingDirectory() = 0;

    virtual const std::string &path() = 0;

    virtual int64_t getAvailableDiskSpace() = 0;

    virtual TemporaryFile createTemporaryFile() = 0;

    virtual TemporaryDirectory createTemporaryDirectory() = 0;

    static const size_t npos = std::string::npos;

    virtual bool externalFileMovesAllowed() const = 0;

    virtual bool objectFilesWritable() const = 0;

    virtual bool moveBlobFromExternalFile(const Digest &digest, int dirfd,
                                          const std::string &path) = 0;
};

class BlobNotFoundException : public std::exception {
  public:
    explicit BlobNotFoundException(const char *message) : d_message(message) {}
    explicit BlobNotFoundException(const std::string &message)
        : d_message(message)
    {
    }

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

  private:
    std::string d_message;
};

class OutOfSpaceException : public std::runtime_error {
  public:
    explicit OutOfSpaceException(const char *message)
        : std::runtime_error(message)
    {
    }
    explicit OutOfSpaceException(const std::string &message)
        : std::runtime_error(message)
    {
    }
};

} // namespace buildboxcommon

#endif // INCLUDED_BUILDBOXCOMMON_LOCALCAS_H
