/*
 * 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_LRULOCALCAS_H
#define INCLUDED_BUILDBOXCOMMON_LRULOCALCAS_H

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>

#include <buildboxcommon_localcas.h>
#include <buildboxcommon_protos.h>

namespace buildboxcommon {

class LruLocalCas final : public LocalCas {
    /* A LocalCas wrapper implementing LRU expiry of blobs. */
  public:
    LruLocalCas(std::unique_ptr<LocalCas> baseCas, double quotaLowRatio,
                std::optional<int64_t> quotaHigh, int64_t reservedSpace);

    ~LruLocalCas() override;

    // disallow copy and move
    LruLocalCas(const LruLocalCas &) = delete;
    LruLocalCas &operator=(const LruLocalCas &) = delete;
    LruLocalCas(LruLocalCas &&) = delete;
    LruLocalCas &operator=(LruLocalCas &&) = delete;

    void protectActiveBlobs(blob_time_type start_time);

    bool hasBlob(const Digest &digest) override;

    bool writeBlob(const Digest &digest, const std::string &data) override;
    bool writeBlob(int fd, Digest *digest) override;

    bool
    moveBlobFromTemporaryFile(const Digest &digest,
                              buildboxcommon::TemporaryFile tempFile) override;
    bool moveBlobFromTemporaryFile(
        const Digest &digest,
        const buildboxcommon::TemporaryDirectory &directory,
        const std::string &filename) override;

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

    std::unique_ptr<std::string> readBlob(const Digest &digest) override;

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

    bool deleteBlob(const Digest &digest) override;

    bool deleteBlob(const Digest &digest,
                    const blob_time_type &if_unmodified_since) override;

    void listBlobs(digest_time_callback_type callback) override;

    std::string path(const Digest &digest) override;

    std::string pathUnchecked(const Digest &digest) override;

    FdNamePair pathAt(const Digest &digest) override;

    FdNamePair pathAtUnchecked(const Digest &digest) override;

    const std::string &path() override;

    buildboxcommon::TemporaryDirectory createStagingDirectory() override;

    buildboxcommon::TemporaryFile createTemporaryFile() override;

    buildboxcommon::TemporaryDirectory createTemporaryDirectory() override;

    int64_t getDiskUsage(bool force_scan = false) override;

    std::optional<int64_t> getDiskQuota() override;

    int64_t getAvailableDiskSpace() override;

    void verifyDiskUsage();

    bool externalFileMovesAllowed() const override;

    bool objectFilesWritable() const override;

    bool moveBlobFromExternalFile(const Digest &digest, int dirfd,
                                  const std::string &path) override;

    LocalCas *getBase();

  private:
    std::unique_ptr<LocalCas> d_base_cas;
    std::atomic<int64_t> d_effective_quota_high{};
    const double d_quotaLowRatio;
    const std::optional<int64_t> d_configuredQuotaHigh;
    const int64_t d_reserved_space;
    std::atomic<int64_t> d_pending_write_space;
    bool d_protect_active_blobs = false;
    blob_time_type d_protect_active_time;
    std::mutex d_cleanup_mutex;
    std::atomic<bool> d_cancel_cleanup;

    std::thread d_disk_monitor_thread;

    // Condition variable to stop disk monitor thread
    std::condition_variable d_stop_cv;
    std::mutex d_stop_mutex;
    bool d_stop = false;

    int64_t calculateDiskUsage();

    void updateDiskQuota();

    void diskMonitorThread();

    void cleanup();

    /* This method takes the size of a blob and a `write_function` that
     * has the purpose of adding it to the CAS and must return `true`
     * if the contents of the CAS changed. (I.e. returning `false` when trying
     * to add a blob that is already stored.)
     *
     * If there is space available, `write_function` will be invoked and,
     * according to the truth value returned from it, the disk usage will be
     * updated accordingly.
     *
     */
    bool tryToAddBlob(google::protobuf::int64 blob_size,
                      const std::function<bool()> &add_blob_function);

    bool deleteBlobAndUpdateDiskUsage(
        const Digest &digest,
        const std::function<bool()> &delete_blob_callback);
};

} // namespace buildboxcommon

#endif // INCLUDED_BUILDBOXCOMMON_LRULOCALCAS_H
