// Copyright (C) 2020-2022 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

#pragma once

#include <cstdint>
#include <functional>
#include <string>

class Config;
class Context;
class Digest;

class InodeCache
{
public:
  // Specifies in which mode a file was hashed since the hash result does not
  // only depend on the actual content but also on operations that were
  // performed that affect the return value. For example, source code files are
  // normally scanned for macros while binary files are not.
  enum class ContentType {
    // The file was not scanned for temporal macros.
    raw = 0,
    // The file was checked for temporal macros (see check_for_temporal_macros
    // in hashutil).
    checked_for_temporal_macros = 1,
  };

  InodeCache(const Config& config);
  ~InodeCache();

  // Return whether it's possible to use the inode cache on the filesystem
  // associated with `fd`.
  static bool available(int fd);

  // Get saved hash digest and return value from a previous call to
  // do_hash_file() in hashutil.cpp.
  //
  // Returns true if saved values could be retrieved from the cache, false
  // otherwise.
  bool get(const std::string& path,
           ContentType type,
           Digest& file_digest,
           int* return_value = nullptr);

  // Put hash digest and return value from a successful call to do_hash_file()
  // in hashutil.cpp.
  //
  // Returns true if values could be stored in the cache, false otherwise.
  bool put(const std::string& path,
           ContentType type,
           const Digest& file_digest,
           int return_value = 0);

  // Unmaps the current cache and removes the mapped file from disk.
  //
  // Returns true on success, false otherwise.
  bool drop();

  // Returns name of the persistent file.
  std::string get_file();

  // Returns total number of cache hits.
  //
  // Counters are incremented in debug mode only.
  int64_t get_hits();

  // Returns total number of cache misses.
  //
  // Counters are incremented in debug mode only.
  int64_t get_misses();

  // Returns total number of errors.
  //
  // Currently only lock errors will be counted, since the counter is not
  // accessible before the file has been successfully mapped into memory.
  //
  // Counters are incremented in debug mode only.
  int64_t get_errors();

private:
  struct Bucket;
  struct Entry;
  struct Key;
  struct SharedRegion;
  using BucketHandler = std::function<void(Bucket* bucket)>;

  bool mmap_file(const std::string& inode_cache_file);
  static bool
  hash_inode(const std::string& path, ContentType type, Digest& digest);
  bool with_bucket(const Digest& key_digest,
                   const BucketHandler& bucket_handler);
  static bool create_new_file(const std::string& filename);
  bool initialize();

  const Config& m_config;
  struct SharedRegion* m_sr = nullptr;
  bool m_failed = false;
};
