//  Copyright (c) 2013, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).
#pragma once

#include <functional>
#include <string>
#include <unordered_map>

#include "rocksdb/slice.h"
#include "utilities/persistent_cache/block_cache_tier_file.h"
#include "utilities/persistent_cache/hash_table.h"
#include "utilities/persistent_cache/hash_table_evictable.h"
#include "utilities/persistent_cache/lrulist.h"

namespace ROCKSDB_NAMESPACE {

//
// Block Cache Tier Metadata
//
// The BlockCacheTierMetadata holds all the metadata associated with block
// cache. It
// fundamentally contains 2 indexes and an LRU.
//
// Block Cache Index
//
// This is a forward index that maps a given key to a LBA (Logical Block
// Address). LBA is a disk pointer that points to a record on the cache.
//
// LBA = { cache-id, offset, size }
//
// Cache File Index
//
// This is a forward index that maps a given cache-id to a cache file object.
// Typically you would lookup using LBA and use the object to read or write
struct BlockInfo {
  explicit BlockInfo(const Slice& key, const LBA& lba = LBA())
      : key_(key.ToString()), lba_(lba) {}

  std::string key_;
  LBA lba_;
};

class BlockCacheTierMetadata {
 public:
  explicit BlockCacheTierMetadata(const uint32_t blocks_capacity = 1024 * 1024,
                                  const uint32_t cachefile_capacity = 10 * 1024)
      : cache_file_index_(cachefile_capacity), block_index_(blocks_capacity) {}

  virtual ~BlockCacheTierMetadata() {}

  // Insert a given cache file
  bool Insert(BlockCacheFile* file);

  // Lookup cache file based on cache_id
  BlockCacheFile* Lookup(const uint32_t cache_id);

  // Insert block information to block index
  BlockInfo* Insert(const Slice& key, const LBA& lba);
  // bool Insert(BlockInfo* binfo);

  // Lookup block information from block index
  bool Lookup(const Slice& key, LBA* lba);

  // Remove a given from the block index
  BlockInfo* Remove(const Slice& key);

  // Find and evict a cache file using LRU policy
  BlockCacheFile* Evict();

  // Clear the metadata contents
  virtual void Clear();

 protected:
  // Remove all block information from a given file
  virtual void RemoveAllKeys(BlockCacheFile* file);

 private:
  // Cache file index definition
  //
  // cache-id => BlockCacheFile
  struct BlockCacheFileHash {
    uint64_t operator()(const BlockCacheFile* rec) {
      return std::hash<uint32_t>()(rec->cacheid());
    }
  };

  struct BlockCacheFileEqual {
    uint64_t operator()(const BlockCacheFile* lhs, const BlockCacheFile* rhs) {
      return lhs->cacheid() == rhs->cacheid();
    }
  };

  using CacheFileIndexType =
      EvictableHashTable<BlockCacheFile, BlockCacheFileHash,
                         BlockCacheFileEqual>;

  // Block Lookup Index
  //
  // key => LBA
  struct Hash {
    size_t operator()(BlockInfo* node) const {
      return std::hash<std::string>()(node->key_);
    }
  };

  struct Equal {
    size_t operator()(BlockInfo* lhs, BlockInfo* rhs) const {
      return lhs->key_ == rhs->key_;
    }
  };

  using BlockIndexType = HashTable<BlockInfo*, Hash, Equal>;

  CacheFileIndexType cache_file_index_;
  BlockIndexType block_index_;
};

}  // namespace ROCKSDB_NAMESPACE
