// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
// vim: ts=8 sw=2 sts=2 expandtab

#include "kvstore_tool.h"

#include <iostream>

#include "common/config_proxy.h" // for class ConfigProxy
#include "common/errno.h"
#include "common/url_escape.h"
#include "common/pretty_binary.h"
#include "global/global_context.h" // for g_conf()
#include "include/buffer.h"
#include "include/types.h" // for struct byte_u_t
#include "kv/KeyValueHistogram.h"

#ifdef WITH_BLUESTORE
#include "os/bluestore/BlueStore.h"
#endif

using namespace std;

StoreTool::StoreTool(const string& type,
		     const string& path,
                     bool read_only,
		     bool to_repair,
		     bool need_stats)
  : store_path(path)
{

  if (need_stats) {
    g_conf()->rocksdb_perf = true;
    g_conf()->rocksdb_collect_compaction_stats = true;
  }

  if (type == "bluestore-kv") {
    if (load_bluestore(path, read_only, to_repair) != 0)
      exit(1);
  } else {
    auto db_ptr = KeyValueDB::create(g_ceph_context, type, path);
    if (!to_repair) {
      int r = read_only ? db_ptr->open_read_only(std::cerr) : db_ptr->open(std::cerr);
      if (r < 0) {
        cerr << "failed to open type " << type << " path " << path << ": "
             << cpp_strerror(r) << std::endl;
        exit(1);
      }
    }
    db.reset(db_ptr);
  }
}


int StoreTool::load_bluestore(const string& path, bool read_only, bool to_repair)
{
#ifdef WITH_BLUESTORE
    auto bluestore = new BlueStore(g_ceph_context, path);
    KeyValueDB *db_ptr;
    int r = bluestore->open_db_environment(&db_ptr, read_only, to_repair);
    if (r < 0) {
     return -EINVAL;
    }
    db = decltype(db){db_ptr, Deleter((ObjectStore*)bluestore)};
    return 0;
#else
    cerr << "bluestore not compiled in" << std::endl;
    return -1;
#endif // WITH_BLUESTORE
}


uint32_t StoreTool::traverse(const string& prefix,
                             const bool do_crc,
                             const bool do_value_dump,
                             ostream *out)
{
  KeyValueDB::WholeSpaceIterator iter = db->get_wholespace_iterator();

  if (prefix.empty())
    iter->seek_to_first();
  else
    iter->seek_to_first(prefix);

  uint32_t crc = -1;

  while (iter->valid()) {
    pair<string,string> rk = iter->raw_key();
    if (!prefix.empty() && (rk.first != prefix))
      break;

    if (out)
      *out << url_escape(rk.first) << "\t" << url_escape(rk.second);
    if (do_crc) {
      bufferlist bl;
      bl.append(rk.first);
      bl.append(rk.second);
      bl.append(iter->value());

      crc = bl.crc32c(crc);
      if (out) {
        *out << "\t" << bl.crc32c(0);
      }
    }
    if (out)
      *out << std::endl;
    if (out && do_value_dump) {
      bufferptr bp = iter->value_as_ptr();
      bufferlist value;
      value.append(bp);
      ostringstream os;
      value.hexdump(os);
      std::cout << os.str() << std::endl;
    }
    iter->next();
  }

  return crc;
}

void StoreTool::list(const string& prefix, const bool do_crc,
                     const bool do_value_dump)
{
  traverse(prefix, do_crc, do_value_dump,& std::cout);
}

bool StoreTool::exists(const string& prefix)
{
  ceph_assert(!prefix.empty());
  KeyValueDB::WholeSpaceIterator iter = db->get_wholespace_iterator();
  iter->seek_to_first(prefix);
  return (iter->valid() && (iter->raw_key().first == prefix));
}

bool StoreTool::exists(const string& prefix, const string& key)
{
  ceph_assert(!prefix.empty());

  if (key.empty()) {
    return exists(prefix);
  }
  bool exists = false;
  get(prefix, key, exists);
  return exists;
}

bufferlist StoreTool::get(const string& prefix,
			  const string& key,
			  bool& exists)
{
  ceph_assert(!prefix.empty() && !key.empty());

  map<string,bufferlist> result;
  std::set<std::string> keys;
  keys.insert(key);
  db->get(prefix, keys, &result);

  if (result.count(key) > 0) {
    exists = true;
    return result[key];
  } else {
    exists = false;
    return bufferlist();
  }
}

uint64_t StoreTool::get_size()
{
  map<string,uint64_t> extras;
  uint64_t s = db->get_estimated_size(extras);
  for (auto& [name, size] : extras) {
    std::cout << name << " - " << size << std::endl;
  }
  std::cout << "total: " << s << std::endl;
  return s;
}

bool StoreTool::set(const string &prefix, const string &key, bufferlist &val)
{
  ceph_assert(!prefix.empty());
  ceph_assert(!key.empty());
  ceph_assert(val.length() > 0);

  KeyValueDB::Transaction tx = db->get_transaction();
  tx->set(prefix, key, val);
  int ret = db->submit_transaction_sync(tx);

  return (ret == 0);
}

bool StoreTool::rm(const string& prefix, const string& key)
{
  ceph_assert(!prefix.empty());
  ceph_assert(!key.empty());

  KeyValueDB::Transaction tx = db->get_transaction();
  tx->rmkey(prefix, key);
  int ret = db->submit_transaction_sync(tx);

  return (ret == 0);
}

bool StoreTool::rm_prefix(const string& prefix)
{
  ceph_assert(!prefix.empty());

  KeyValueDB::Transaction tx = db->get_transaction();
  tx->rmkeys_by_prefix(prefix);
  int ret = db->submit_transaction_sync(tx);

  return (ret == 0);
}

void StoreTool::print_summary(const uint64_t total_keys, const uint64_t total_size,
                              const uint64_t total_txs, const string& store_path,
                              const string& other_path, const int duration) const
{
  std::cout << "summary:" << std::endl;
  std::cout << "  copied " << total_keys << " keys" << std::endl;
  std::cout << "  used " << total_txs << " transactions" << std::endl;
  std::cout << "  total size " << byte_u_t(total_size) << std::endl;
  std::cout << "  from '" << store_path << "' to '" << other_path << "'"
            << std::endl;
  std::cout << "  duration " << duration << " seconds" << std::endl;
}

int StoreTool::print_stats() const
{
  ostringstream ostr;
  Formatter* f = Formatter::create("json-pretty", "json-pretty", "json-pretty");
  int ret = -1;
  if (g_conf()->rocksdb_perf) {
    db->get_statistics(f);
    ostr << "db_statistics ";
    f->flush(ostr);
    ret = 0;
  } else {
    ostr << "db_statistics not enabled";
    f->flush(ostr);
  }
  std::cout <<  ostr.str() << std::endl;
  delete f;
  return ret;
}

//Itrerates through the db and collects the stats
int StoreTool::build_size_histogram(const string& prefix0) const
{
  ostringstream ostr;
  Formatter* f = Formatter::create("json-pretty", "json-pretty", "json-pretty");

  const size_t MAX_PREFIX = 256;
  uint64_t num[MAX_PREFIX] = {0};

  size_t max_key_size = 0, max_value_size = 0;
  uint64_t total_key_size = 0, total_value_size = 0;
  size_t key_size = 0, value_size = 0;
  KeyValueHistogram hist;

  auto start = coarse_mono_clock::now();

  auto iter = db->get_iterator(prefix0, KeyValueDB::ITERATOR_NOCACHE);
  iter->seek_to_first();
  while (iter->valid()) {
    pair<string, string> key(iter->raw_key());
    key_size = key.first.size() + key.second.size();
    value_size = iter->value().length();
    hist.value_hist[hist.get_value_slab(value_size)]++;
    max_key_size = std::max(max_key_size, key_size);
    max_value_size = std::max(max_value_size, value_size);
    total_key_size += key_size;
    total_value_size += value_size;


    unsigned prefix = key.first[0];
    ceph_assert(prefix < MAX_PREFIX);
    num[prefix]++;
    hist.update_hist_entry(hist.key_hist, key.first, key_size, value_size);
    iter->next();
  }

  ceph::timespan duration = coarse_mono_clock::now() - start;
  f->open_object_section("rocksdb_key_value_stats");
  for (size_t i = 0; i < MAX_PREFIX; ++i) {
    if (num[i]) {
      string key = "Records for prefix: ";
      key += pretty_binary_string(string(1, char(i)));
      f->dump_unsigned(key, num[i]);
    }
  }
  f->dump_unsigned("max_key_size", max_key_size);
  f->dump_unsigned("max_value_size", max_value_size);
  f->dump_unsigned("total_key_size", total_key_size);
  f->dump_unsigned("total_value_size", total_value_size);
  hist.dump(f);
  f->close_section();

  f->flush(ostr);
  delete f;

  std::cout << ostr.str() << std::endl;
  std::cout << __func__ << " finished in " << duration << " seconds" << std::endl;
  return 0;
}

int StoreTool::copy_store_to(const string& type, const string& other_path,
                             const int num_keys_per_tx,
                             const string& other_type)
{
  if (num_keys_per_tx <= 0) {
    std::cerr << "must specify a number of keys/tx > 0" << std::endl;
    return -EINVAL;
  }

  // open or create a RocksDB store at @p other_path
  boost::scoped_ptr<KeyValueDB> other;
  KeyValueDB *other_ptr = KeyValueDB::create(g_ceph_context,
					     other_type,
					     other_path);
  if (int err = other_ptr->create_and_open(std::cerr); err < 0) {
    return err;
  }
  other.reset(other_ptr);

  KeyValueDB::WholeSpaceIterator it = db->get_wholespace_iterator();
  it->seek_to_first();
  uint64_t total_keys = 0;
  uint64_t total_size = 0;
  uint64_t total_txs = 0;

  auto duration = [start=coarse_mono_clock::now()] {
    const auto now = coarse_mono_clock::now();
    auto seconds = std::chrono::duration<double>(now - start);
    return seconds.count();
  };

  do {
    int num_keys = 0;

    KeyValueDB::Transaction tx = other->get_transaction();

    while (it->valid() && num_keys < num_keys_per_tx) {
      auto [prefix, key] = it->raw_key();
      bufferlist v = it->value();
      tx->set(prefix, key, v);

      num_keys++;
      total_size += v.length();

      it->next();
    }

    total_txs++;
    total_keys += num_keys;

    if (num_keys > 0)
      other->submit_transaction_sync(tx);

    std::cout << "ts = " << duration() << "s, copied " << total_keys
              << " keys so far (" << byte_u_t(total_size) << ")"
              << std::endl;

  } while (it->valid());

  print_summary(total_keys, total_size, total_txs, store_path, other_path,
                duration());

  return 0;
}

void StoreTool::compact()
{
  db->compact();
}

void StoreTool::compact_prefix(const string& prefix)
{
  db->compact_prefix(prefix);
}

void StoreTool::compact_range(const string& prefix,
                              const string& start,
                              const string& end)
{
  db->compact_range(prefix, start, end);
}

int StoreTool::destructive_repair()
{
  return db->repair(std::cout);
}
