text: // Copyright 2021 LabradorWithShades
  // 70892548+LabradorWithShades@users.noreply.github.com
  
  #include <database_manager.hpp>
  
  #include <logger.hpp>
  
  #include <string>
  #include <thread>
  #include <utility>
  
  std::vector<std::string> DataBaseManager::getColumnFamilyNames(
      const std::string& filename, bool create) {
    std::vector<std::string> ans;
    rocksdb::Status s =
        rocksdb::DB::ListColumnFamilies(rocksdb::Options(), filename, &ans);
    if (!s.ok() && !(s.IsIOError() && create)) {
      COOL_LOG_CRITICAL("DB Manager",
                        "Failed retrieve column from database \"" + filename +
                        "\" with error: " + s.ToString());
      throw std::runtime_error("Failed to retrieve columns from database");
    } else if (!s.ok()) {
      ans.push_back(rocksdb::kDefaultColumnFamilyName);
    }
  
    return ans;
  }
  
  DataBaseManager::DataBaseManager(std::string filename, bool readOnly,
                                   bool create)
      : m_filename(std::move(filename)), m_readOnly(readOnly) {
    static const rocksdb::CompressionType preferredCompression =
        rocksdb::kNoCompression;
  
    rocksdb::Status s{};
    rocksdb::Options options{};
    options.IncreaseParallelism(
        static_cast<int>(std::thread::hardware_concurrency()));
    options.OptimizeLevelStyleCompaction();
    options.OptimizeForSmallDb();
    options.compression = preferredCompression;
    options.compression_per_level = std::vector<rocksdb::CompressionType>();
    options.create_if_missing = create;
  
    m_columnFamilyNames = getColumnFamilyNames(m_filename, create);
    for (auto& columnFamilyName : m_columnFamilyNames) {
      rocksdb::ColumnFamilyDescriptor descriptor{};
      descriptor.name = columnFamilyName;
      descriptor.options.OptimizeLevelStyleCompaction();
      descriptor.options.OptimizeForSmallDb();
      descriptor.options.compression = preferredCompression;
      descriptor.options.compression_per_level =
          std::vector<rocksdb::CompressionType>();
      m_columnFamilyDescriptors.push_back(std::move(descriptor));
    }
  
    if (readOnly) {
      s = rocksdb::DB::OpenForReadOnly(options, m_filename,
                                       m_columnFamilyDescriptors,
                                       &m_columnFamilyHandles, &mp_db);
    } else {
      s = rocksdb::DB::Open(options, m_filename,
                            m_columnFamilyDescriptors,
                            &m_columnFamilyHandles, &mp_db);
    }
    if (!s.ok()) {
      COOL_LOG_CRITICAL("DB Manager",
                        "Failed to open database \"" + m_filename +
                        "\" with error: " + s.ToString());
      throw std::runtime_error("Failed to open database");
    }
    COOL_LOG_INFO("DB Manager", "Database \"" + m_filename + "\" opened");
  }
  
  DataBaseManager::DataBaseManager(DataBaseManager&& other) noexcept
    : mp_db(other.mp_db)
    , m_filename(std::move(other.m_filename))
    , m_columnFamilyNames(std::move(other.m_columnFamilyNames))
    , m_columnFamilyDescriptors(std::move(other.m_columnFamilyDescriptors))
    , m_columnFamilyHandles(std::move(other.m_columnFamilyHandles))
    , m_readOnly(other.m_readOnly) {}
  
  DataBaseManager::~DataBaseManager() {
    for (auto* pHandle : m_columnFamilyHandles) {
      mp_db->DestroyColumnFamilyHandle(pHandle);
    }
    rocksdb::Status s = mp_db->Close();
    if (!s.ok()) {
      COOL_LOG_CRITICAL("DB Manager",
                        "Failed to close database \"" + m_filename +
                        "\" with error code: " + s.ToString());
    }
    COOL_LOG_INFO("DB Manager", "Closed database \"" + m_filename + "\"");
    delete mp_db;
  }
  
  const std::vector<std::string>& DataBaseManager::getColumnFamilyNames() const {
    return m_columnFamilyNames;
  }
  
  void DataBaseManager::addEntry(const std::string& key,
                                 const std::string& value) {
    if (m_readOnly) {
      COOL_LOG_ERROR("DB Manager", "Tried to modify read-only database");
      throw std::runtime_error("Tried to modify read-only database");
    }
  
    rocksdb::Status s = mp_db->Put(rocksdb::WriteOptions(), key, value);
    if (!s.ok()) {
      COOL_LOG_ERROR("DB Manager",
                     "Failed to add entry with error: " + s.ToString());
    }
    COOL_LOG_TRACE("DB Manager", "Added entry " + key + ":" + value +
                   " to database \"" + m_filename + "\", default column");
  }
  
  void DataBaseManager::addEntry(const std::string& column,
                                 const std::string& key,
                                 const std::string& value) {
    if (m_readOnly) {
      COOL_LOG_ERROR("DB Manager", "Tried to modify read-only database");
      return;
    }
  
    rocksdb::ColumnFamilyHandle* handle = findHandle(column);
    if (handle == nullptr) {
      rocksdb::ColumnFamilyDescriptor descriptor{};
      descriptor.name = column;
      descriptor.options.OptimizeLevelStyleCompaction();
      descriptor.options.OptimizeForSmallDb();
      descriptor.options.compression = rocksdb::kNoCompression;
      descriptor.options.compression_per_level =
          std::vector<rocksdb::CompressionType>();
  
      mp_db->CreateColumnFamily(descriptor.options, descriptor.name, &handle);
      m_columnFamilyNames.push_back(column);
      m_columnFamilyDescriptors.push_back(std::move(descriptor));
      m_columnFamilyHandles.push_back(handle);
    }
  
    rocksdb::Status s = mp_db->Put(rocksdb::WriteOptions(), handle, key, value);
    if (!s.ok()) {
      COOL_LOG_ERROR("DB Manager",
                     "Failed to add entry with error: " + s.ToString());
    }
    COOL_LOG_TRACE("DB Manager", "Added entry " + key + ":" + value +
                   " to database \"" + m_filename +
                   "\", column \"" + column + "\"");
  }
  
  void DataBaseManager::addEntry(const Entry& entry) {
    addEntry(entry.column, entry.key, entry.value);
  }
  
  std::string DataBaseManager::getEntry(const std::string& key) {
    std::string value{};
    rocksdb::Status s = mp_db->Get(rocksdb::ReadOptions(), key, &value);
    if (!s.ok()) {
      COOL_LOG_ERROR("DB Manager",
                     "Failed to get entry with error: " + s.ToString());
    }
    COOL_LOG_TRACE("DB Manager",
                   "Got entry " + key + ":" + value +
                   " from database, default column");
    return value;
  }
  
  std::string DataBaseManager::getEntry(const std::string& column,
                                        const std::string& key) {
    std::string value{};
  
    rocksdb::ColumnFamilyHandle* handle = findHandle(column);
    if (handle == nullptr) {
      COOL_LOG_ERROR("DB Manager", "Column \"" + column +
                     "\" not found in database \"" + m_filename + "\"");
      throw std::runtime_error("Column not found");
    }
  
    rocksdb::Status s = mp_db->Get(rocksdb::ReadOptions(), handle, key, &value);
    if (!s.ok()) {
      COOL_LOG_ERROR("DB Manager",
                     "Failed to get entry with error: " + s.ToString());
    }
    COOL_LOG_TRACE("DB Manager",
                   "Got entry " + key + ":" + value +
                   " from database, column \"" + column + "\"");
    return value;
  }
  
  void DataBaseManager::removeEntry(const std::string& key) {
    if (m_readOnly) {
      COOL_LOG_ERROR("DB Manager", "Tried to modify read-only database");
      return;
    }
    COOL_LOG_TRACE("DB Manager", "Removed entry from database");
  
    rocksdb::Status s = mp_db->Delete(rocksdb::WriteOptions(), key);
    if (!s.ok()) {
      COOL_LOG_ERROR("DB Manager",
                     "Failed to add entry with error: " + s.ToString());
    }
  }
  
  void DataBaseManager::removeEntry(const std::string& column,
                                    const std::string& key) {
    if (m_readOnly) {
      COOL_LOG_ERROR("DB Manager", "Tried to modify read-only database");
      return;
    }
    COOL_LOG_TRACE("DB Manager", "Removed entry from database");
  
    rocksdb::ColumnFamilyHandle* handle = findHandle(column);
    if (handle == nullptr) {
      COOL_LOG_ERROR("DB Manager",
                     "Column with name \"" + column + "\" not found");
      return;
    }
  
    rocksdb::Status s = mp_db->Delete(rocksdb::WriteOptions(), handle, key);
    if (!s.ok()) {
      COOL_LOG_ERROR("DB Manager",
                     "Failed to add entry with error: " + s.ToString());
    }
  }
  
  rocksdb::Iterator* DataBaseManager::createIterator(const std::string& column) {
    rocksdb::ColumnFamilyHandle* handle = findHandle(column);
    rocksdb::Iterator* it = mp_db->NewIterator(rocksdb::ReadOptions(), handle);
    rocksdb::Status s = it->status();
    if (!s.ok()) {
      COOL_LOG_CRITICAL("DB Manager",
                        "Failed to get iterator with error: " + s.ToString());
      throw std::runtime_error("Failed to get iterator for database \"" +
                               m_filename + "\"");
    }
    return it;
  }
  
  rocksdb::ColumnFamilyHandle* DataBaseManager::findHandle(
      const std::string& columnName) {
    for (size_t i = 0; i < m_columnFamilyDescriptors.size(); ++i) {
      if (m_columnFamilyDescriptors[i].name == columnName)
        return m_columnFamilyHandles[i];
    }
    return nullptr;
  }
  