#include "iwkv.h"
#include "iwlog.h"
#include "iwutils.h"
#include "iwcfg.h"
#include "iwkv_tests.h"

#define KBUFSZ 128
#define VBUFSZ 128
char kbuf[KBUFSZ];
char vbuf[VBUFSZ];

extern int8_t iwkv_next_level;

static int logstage(FILE *f, const char *name, IWDB db) {
  int rci = fprintf(f, "\n#### Stage: %s\n", name);
  iwkvd_db(f, db, IWKVD_PRINT_NO_LEVEVELS | IWKVD_PRINT_VALS, 0);
  fflush(f);
  return rci < 0 ? rci : 0;
}

static int logstage2(FILE *f, const char *name, IWDB db) {
  int rci = fprintf(f, "\n#### Stage: %s\n", name);
  iwkvd_db(f, db, IWKVD_PRINT_NO_LEVEVELS | IWKVD_PRINT_VALS, 0);
  fflush(f);
  return rci < 0 ? rci : 0;
}

int init_suite() {
  iwrc rc = iwkv_init();
  return rc;
}

int clean_suite() {
  return 0;
}

// Test5 staff
struct Test5DUP1 {
  bool _mv;
  bool _1v;
  bool _10v;
};

static int64_t _test5dup5visitor(uint64_t dv, int64_t idx, void *op) {
  CU_ASSERT_PTR_NOT_NULL_FATAL(op);
  struct Test5DUP1 *s = op;
  switch (dv) {
    case -1ULL:
      s->_mv = true;
      break;
    case 1ULL:
      s->_1v = true;
      break;
    case 10ULL:
      s->_10v = true;
      break;
    default:
      CU_FAIL("Invalid dup value");
      break;
  }
  return 1;
}

// Test Slides
static void iwkv_test3_impl(int fmt_version) {
  FILE *f = fmt_version > 1 ? fopen("iwkv_test1_3_v2.log", "w+") : fopen("iwkv_test1_3.log", "w+");
  CU_ASSERT_PTR_NOT_NULL(f);

  iwrc rc;
  IWKV_val key = { 0 };
  IWKV_val val = { 0 };
  IWKV iwkv;
  IWDB db1;
  IWKV_OPTS opts = {
    .path        = "iwkv_test1_3.db",
    .oflags      = IWKV_TRUNC,
    .fmt_version = fmt_version
  };

  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  for (int i = -23, c = 0; i <= 23; ++i) {
    for (int j = 0; j < 63; ++j, ++c) {
      iwkv_next_level = i < 0 ? -i : i;
      snprintf(kbuf, KBUFSZ, "%05dkkk", c);
      snprintf(vbuf, VBUFSZ, "%05dval", c);
      key.data = kbuf;
      key.size = strlen(key.data);
      val.data = vbuf;
      val.size = strlen(val.data);
      rc = iwkv_put(db1, &key, &val, 0);
      CU_ASSERT_EQUAL_FATAL(rc, 0);
    }
  }
  //       middle
  // 23 \    |    / 23
  //     \   |   /
  //    0 \__|__/ 0
  iwkv_next_level = 23;
  // put: 01858aaa in order to split middle zero sblk
  snprintf(kbuf, KBUFSZ, "%05daaa", 1858);
  snprintf(vbuf, VBUFSZ, "%05dval", 1858);
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  logstage2(f, "iwkv_test3", db1);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  // Compare logs with referenced
#ifndef _WIN32
  FILE *r = fmt_version > 1 ? fopen("iwkv_test1_3_v2.ref", "r+") : fopen("iwkv_test1_3.ref", "r+");
#else
  FILE *r = fmt_version > 1 ? fopen("iwkv_test1_3w_v2.ref", "r+") : fopen("iwkv_test1_3w.ref", "r+");
#endif
  CU_ASSERT_PTR_NOT_NULL(r);
  int rci = cmp_files(r, f);
  CU_ASSERT_EQUAL_FATAL(rci, 0);
  fclose(f);
  fclose(r);
}

static void iwkv_test3_v1() {
  iwkv_test3_impl(1);
}

static void iwkv_test3_v2() {
  iwkv_test3_impl(2);
}

static void iwkv_test2_impl(int fmt_version) {
  FILE *f = fmt_version > 1 ? fopen("iwkv_test1_2_v2.log", "w+") : fopen("iwkv_test1_2.log", "w+");
  CU_ASSERT_PTR_NOT_NULL(f);

  iwrc rc;
  IWKV_val key = { 0 };
  IWKV_val val = { 0 };
  IWKV iwkv;
  IWDB db1;
  IWKV_OPTS opts = {
    .path        = "iwkv_test1_2.db",
    .oflags      = IWKV_TRUNC,
    .fmt_version = fmt_version
  };

  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  for (int i = 252 * 2; i >= 0; --i) { // 189
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    key.data = kbuf;
    key.size = strlen(key.data);
    val.data = vbuf;
    val.size = strlen(val.data);
    rc = iwkv_put(db1, &key, &val, 0);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
  }
  logstage(f, "desc sorted keys inserted", db1);

  for (int i = 0; i <= 252 * 2; ++i) {
    int cret;
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    int vsize = strlen(vbuf);
    key.data = kbuf;
    key.size = strlen(key.data);
    rc = iwkv_get(db1, &key, &val);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
    IW_CMP(cret, vbuf, vsize, val.data, val.size);
    CU_ASSERT_EQUAL_FATAL(cret, 0);
    iwkv_kv_dispose(0, &val);
  }

  //  snprintf(kbuf, KBUFSZ, "%03dkkk", 64);
  //  key.data = kbuf;
  //  key.size = strlen(key.data);
  //  rc = iwkv_del(db1, &key);
  //  CU_ASSERT_EQUAL_FATAL(rc, 0);
  //  logstage(f, "removed 064kkk", db1);
  //
  //
  //  snprintf(kbuf, KBUFSZ, "%03dkkk", 126);
  //  key.data = kbuf;
  //  key.size = strlen(key.data);
  //  rc = iwkv_del(db1, &key);
  //  CU_ASSERT_EQUAL_FATAL(rc, 0);
  //  logstage(f, "removed 126kkk", db1);
  //
  //
  //  // Now delete more than half of block records
  //  // 126
  //  for (int i = 64; i <= 99; ++i) {
  //    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
  //    key.data = kbuf;
  //    key.size = strlen(key.data);
  //    rc = iwkv_del(db1, &key);
  //    if (i == 64) {
  //      CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND);
  //      rc = 0;
  //      continue;
  //    }
  //    CU_ASSERT_EQUAL_FATAL(rc, 0);
  //  }
  //  logstage(f, "removed 065kkk - 099kkk", db1); // 125
  //
  //  for (int i = 100; i <= 125; ++i) {
  //    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
  //    key.data = kbuf;
  //    key.size = strlen(key.data);
  //    rc = iwkv_del(db1, &key);
  //    CU_ASSERT_EQUAL_FATAL(rc, 0);
  //  }
  //  logstage(f, "removed all keys in SBLK[54]", db1); // 125

  rc = iwkv_db_destroy(&db1);      // Destroy DB and remove all db blocks
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Reopen DB then check db1
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  logstage(f, "db1 destroyed", db1);
  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Compare logs with referenced
#ifndef _WIN32
  FILE *r = fmt_version > 1 ? fopen("iwkv_test1_2_v2.ref", "r+") : fopen("iwkv_test1_2.ref", "r+");
#else
  FILE *r = fmt_version > 1 ? fopen("iwkv_test1_2w_v2.ref", "r+") : fopen("iwkv_test1_2w.ref", "r+");
#endif
  CU_ASSERT_PTR_NOT_NULL(r);
  int rci = cmp_files(r, f);
  CU_ASSERT_EQUAL_FATAL(rci, 0);
  fclose(f);
  fclose(r);
}

static void iwkv_test2_v1() {
  iwkv_test2_impl(1);
}

static void iwkv_test2_v2() {
  iwkv_test2_impl(2);
}

static void iwkv_test1_impl(int fmt_version) {
  FILE *f = fmt_version > 1 ? fopen("iwkv_test1_1_v2.log", "w+") : fopen("iwkv_test1_1.log", "w+");
  CU_ASSERT_PTR_NOT_NULL(f);
  char buf[128];
  size_t vsize;

  IWKV_OPTS opts = {
    .path        = "iwkv_test1.db",
    .oflags      = IWKV_TRUNC,
    .fmt_version = fmt_version
  };
  // Test open/close
  IWKV iwkv;
  IWDB db1, db2, db3;
  iwrc rc;
  IWKV_val key = { .data = "foo" };
  key.size = strlen(key.data);
  IWKV_val val = { .data = "bar" };
  val.size = strlen(val.data);

  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Test open/close existing db
  opts.oflags = 0;
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Test create/destroy db
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 2, 0, &db2); // destroyed
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 3, 0, &db3);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db_destroy(&db2);     // destroyed
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Test one in read-only mode
  opts.oflags = IWKV_RDONLY;
  opts.path = "not-existing.db";
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_TRUE_FATAL(rc);
  iwrc_strip_errno(&rc);
#ifdef _WIN32
  iwrc_strip_werror(&rc);
#endif
  CU_ASSERT_EQUAL(rc, IW_ERROR_NOT_EXISTS);

  // Open in read-only mode and acquire not existing db
  opts.path = "iwkv_test1.db";
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_db(iwkv, 2, 0, &db2);
  CU_ASSERT_EQUAL(rc, IW_ERROR_READONLY);

  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_db(iwkv, 3, 0, &db3);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  logstage(f, "empty db", db1);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Open in write mode, then put a simple kv
  opts.oflags = 0;
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  // iwkv_next_level = 3;
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  logstage(f, "put foo:bar", db1);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Open db and get out single record
  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  val.size = 0;
  val.data = 0;

  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_NSTRING_EQUAL(key.data, "foo", key.size);
  CU_ASSERT_NSTRING_EQUAL(val.data, "bar", val.size);
  iwkv_kv_dispose(0, &val);

  rc = iwkv_get_copy(db1, &key, buf, sizeof(buf), &vsize);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL(3, vsize);
  CU_ASSERT_NSTRING_EQUAL(buf, "bar", vsize);

  rc = iwkv_get_copy(db1, &key, buf, 1, &vsize);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL(3, vsize);
  CU_ASSERT_NSTRING_EQUAL(buf, "b", 1);

  // put foo->bazzz
  key.data = "foo";
  key.size = strlen(key.data);
  val.data = "bazzz";
  val.size = strlen(val.data);
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // put foo->zzz with IWKV_NO_OVERWRITE
  val.data = "zzz";
  val.size = strlen(val.data);
  rc = iwkv_put(db1, &key, &val, IWKV_NO_OVERWRITE);

  CU_ASSERT_EQUAL_FATAL(rc, IWKV_ERROR_KEY_EXISTS);
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_NSTRING_EQUAL(key.data, "foo", key.size);
  CU_ASSERT_NSTRING_EQUAL(val.data, "bazzz", val.size);
  iwkv_kv_dispose(0, &val);

  logstage(f, "put foo:bazz", db1);

  // put foo->''
  val.data = "";
  val.size = strlen(val.data);
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_NSTRING_EQUAL(key.data, "foo", key.size);
  CU_ASSERT_NSTRING_EQUAL(val.data, "", val.size);
  iwkv_kv_dispose(0, &val);

  logstage(f, "put foo:", db1);

  val.data = "bar";
  val.size = strlen(val.data);
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_NSTRING_EQUAL(key.data, "foo", key.size);
  CU_ASSERT_NSTRING_EQUAL(val.data, "bar", val.size);
  iwkv_kv_dispose(0, &val);

  logstage(f, "put foo:bar", db1);

  // remove key/value
  rc = iwkv_del(db1, &key, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, IWKV_ERROR_NOTFOUND);

  logstage(f, "remove foo:bar", db1);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, IWKV_ERROR_NOTFOUND);

  // iwkv_next_level = 0;

  for (int i = 0; i < 127 * 2; i += 2) {
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    key.data = kbuf;
    key.size = strlen(key.data);
    val.data = vbuf;
    val.size = strlen(val.data);
    rc = iwkv_put(db1, &key, &val, 0);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
  }

  logstage(f, "fill up first block", db1);

  // iwkv_next_level = 0;
  for (int i = 0; i < 127 * 2; i += 2) {
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    key.data = kbuf;
    key.size = strlen(key.data);
    rc = iwkv_get(db1, &key, &val);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
    CU_ASSERT_EQUAL_FATAL(strncmp(val.data, vbuf, val.size), 0);
    iwkv_kv_dispose(0, &val);
  }

  for (int i = 1; i < 127 * 2; i += 2) {
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    key.data = kbuf;
    key.size = strlen(key.data);
    val.data = vbuf;
    val.size = strlen(val.data);
    //logstage(stderr, "!!!!!!", db1);
    rc = iwkv_put(db1, &key, &val, 0);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
    //logstage(stderr, "??????", db1);
  }

  logstage(f, "fill up second block", db1);

  // Check basic cursor operations
  IWKV_cursor cur1;

  rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_BEFORE_FIRST, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_cursor_to(cur1, IWKV_CURSOR_PREV);
  CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND);
  rc = iwkv_cursor_close(&cur1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_AFTER_LAST, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_cursor_get(cur1, &key, &val);
  CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND);
  rc = iwkv_cursor_to(cur1, IWKV_CURSOR_NEXT);
  CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND);
  rc = iwkv_cursor_close(&cur1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_AFTER_LAST, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_cursor_to(cur1, IWKV_CURSOR_PREV);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  int i = 0;
  do {
    IWKV_val key;
    IWKV_val val;
    iwrc rc2 = iwkv_cursor_get(cur1, &key, &val);
    CU_ASSERT_EQUAL_FATAL(rc2, 0);
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
    CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
    iwkv_kv_dispose(&key, &val);
    if (i == 2) {
      rc2 = iwkv_cursor_to(cur1, IWKV_CURSOR_PREV);
      CU_ASSERT_EQUAL_FATAL(rc2, 0);
      rc2 = iwkv_cursor_get(cur1, &key, &val);
      CU_ASSERT_EQUAL_FATAL(rc2, 0);
      snprintf(kbuf, KBUFSZ, "%03dkkk", i + 1);
      snprintf(vbuf, VBUFSZ, "%03dval", i + 1);
      CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
      CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
      rc2 = iwkv_cursor_to(cur1, IWKV_CURSOR_NEXT);
      CU_ASSERT_EQUAL_FATAL(rc2, 0);
      iwkv_kv_dispose(&key, &val);
    }
    ++i;
  } while (!(rc = iwkv_cursor_to(cur1, IWKV_CURSOR_PREV)));
  CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND);
  rc = iwkv_cursor_close(&cur1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  --i;
  rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_BEFORE_FIRST, 0);
  while (!(rc = iwkv_cursor_to(cur1, IWKV_CURSOR_NEXT))) {
    IWKV_val key;
    IWKV_val val;
    iwrc rc2 = iwkv_cursor_get(cur1, &key, &val);
    CU_ASSERT_EQUAL_FATAL(rc2, 0);
    snprintf(kbuf, KBUFSZ, "%03dkkk", i);
    snprintf(vbuf, VBUFSZ, "%03dval", i);
    --i;
    CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
    CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
    iwkv_kv_dispose(&key, &val);
  }
  CU_ASSERT_EQUAL(rc, IWKV_ERROR_NOTFOUND);
  rc = iwkv_cursor_close(&cur1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Set cursor to key
  do {
    IWKV_val key;
    IWKV_val val;
    snprintf(kbuf, KBUFSZ, "%03dkkk", 30);
    snprintf(vbuf, VBUFSZ, "%03dval", 30);
    key.data = kbuf;
    key.size = strlen(kbuf);
    rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_EQ, &key);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
    iwrc rc2 = iwkv_cursor_get(cur1, &key, &val);
    CU_ASSERT_EQUAL_FATAL(rc2, 0);
    CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
    CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
    iwkv_kv_dispose(&key, &val);

    // Move to GE 000
    snprintf(kbuf, KBUFSZ, "%03d", 0);
    snprintf(vbuf, VBUFSZ, "%03dval", 0);
    key.data = kbuf;
    key.size = strlen(kbuf);
    rc2 = iwkv_cursor_to_key(cur1, IWKV_CURSOR_GE, &key);
    if (rc2) {
      iwlog_ecode_error3(rc2);
    }
    CU_ASSERT_EQUAL_FATAL(rc2, 0);
    rc2 = iwkv_cursor_get(cur1, &key, &val);
    CU_ASSERT_EQUAL_FATAL(rc2, 0);
    snprintf(kbuf, KBUFSZ, "%03dkkk", 0);
    snprintf(vbuf, VBUFSZ, "%03dval", 0);
    CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
    CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
    iwkv_kv_dispose(&key, &val);

    // Move to EQ 000
    snprintf(kbuf, KBUFSZ, "%03d", 0);
    snprintf(vbuf, VBUFSZ, "%03dval", 0);
    key.data = kbuf;
    key.size = strlen(kbuf);
    rc2 = iwkv_cursor_to_key(cur1, IWKV_CURSOR_EQ, &key);
    CU_ASSERT_EQUAL(rc2, IWKV_ERROR_NOTFOUND);

    rc = iwkv_cursor_close(&cur1);
    CU_ASSERT_EQUAL_FATAL(rc, 0);
  } while (0);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  logstage(f, "state after reopen", db1);

  // Put a big key
  snprintf(kbuf, KBUFSZ, "abracadabrabracadabrabracadabrabracadabrabracadabrabracadabrabracadabr1");
  key.size = strlen(kbuf);
  snprintf(vbuf, VBUFSZ, "vabracadabrabracadabrabracadabrabracadabrabracadabrabracadabrabracadabr");
  val.size = strlen(vbuf);
  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL_FATAL(strncmp(val.data, vbuf, val.size), 0);
  iwkv_kv_dispose(0, &val);

  logstage(f, "a big key", db1);

  // 061kkk:061val
  // Set value with cursor
  snprintf(kbuf, KBUFSZ, "%03dkkk", 61);
  snprintf(vbuf, VBUFSZ, "%03dval2", 61);
  key.data = kbuf;
  key.size = strlen(kbuf);
  val.data = vbuf;
  val.size = strlen(vbuf);

  // Cursor set
  rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_EQ, &key);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_cursor_set(cur1, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  key.data = 0;
  val.data = 0;
  rc = iwkv_cursor_get(cur1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
  CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
  iwkv_kv_dispose(&key, &val);
  rc = iwkv_cursor_close(&cur1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Set in write mode
  snprintf(kbuf, KBUFSZ, "%03dkkk", 61);
  snprintf(vbuf, VBUFSZ, "%03dval3", 61);
  key.data = kbuf;
  key.size = strlen(kbuf);
  val.data = vbuf;
  val.size = strlen(vbuf);
  rc = iwkv_cursor_open(db1, &cur1, IWKV_CURSOR_EQ, &key);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  rc = iwkv_cursor_set(cur1, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  key.data = 0;
  val.data = 0;
  rc = iwkv_cursor_get(cur1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL(strncmp(key.data, kbuf, key.size), 0);
  CU_ASSERT_EQUAL(strncmp(val.data, vbuf, val.size), 0);
  iwkv_kv_dispose(&key, &val);
  rc = iwkv_cursor_close(&cur1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  // Compare logs with referenced
#ifndef _WIN32
  FILE *r = fmt_version > 1 ? fopen("iwkv_test1_1_v2.ref", "r+") : fopen("iwkv_test1_1.ref", "r+");
#else
  FILE *r = fmt_version > 1 ? fopen("iwkv_test1_1w_v2.ref", "r+") : fopen("iwkv_test1_1w.ref", "r+");
#endif
  CU_ASSERT_PTR_NOT_NULL_FATAL(r);
  int rci = cmp_files(r, f);
  CU_ASSERT_EQUAL_FATAL(rci, 0);
  fclose(r);
  fclose(f);
}

static void iwkv_test1_v1() {
  iwkv_test1_impl(1);
}

static void iwkv_test1_v2() {
  iwkv_test1_impl(2);
}

static void iwkv_test8_impl(int fmt_version) {
  IWKV_OPTS opts = {
    .path        = "iwkv_test1_8.db",
    .oflags      = IWKV_TRUNC,
    .fmt_version = fmt_version
  };
  IWKV iwkv;
  IWDB db1;
  IWKV_cursor cur;
  IWKV_val key;
  IWKV_val val = {
    .data = "foo",
    .size = sizeof("foo") - 1
  };
  IWKV_val oval;
  size_t ksz;
  uint64_t llv = 1;
  uint64_t llv2;

  iwrc rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_db(iwkv, 1, IWDB_VNUM64_KEYS, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  key.data = &llv;
  key.size = sizeof(llv);

  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_get(db1, &key, &oval);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  iwkv_val_dispose(&oval);

  llv = 0xffffffffffffffe;
  key.data = &llv;

  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_get(db1, &key, &oval);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  iwkv_val_dispose(&oval);

  rc = iwkv_get_copy(db1, &key, &llv2, sizeof(llv2), &ksz);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_cursor_open(db1, &cur, IWKV_CURSOR_EQ, &key);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  llv = 0xffffffffffffffffULL;
  rc = iwkv_put(db1, &key, &val, 0);
  CU_ASSERT_EQUAL(rc, IW_ERROR_OVERFLOW);


  llv = 0;
  int64_t compound;
  rc = iwkv_cursor_copy_key(cur, &llv, sizeof(llv), &ksz, &compound);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL(llv, 0xffffffffffffffe);

  llv = 0;
  rc = iwkv_cursor_get(cur, &oval, 0);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL_FATAL(oval.size, sizeof(llv));
  memcpy(&llv, oval.data, sizeof(llv));
  CU_ASSERT_EQUAL(llv, 0xffffffffffffffe);
  iwkv_val_dispose(&oval);

  rc = iwkv_cursor_to(cur, IWKV_CURSOR_NEXT);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  llv = 0;
  rc = iwkv_cursor_copy_key(cur, &llv, sizeof(llv), &ksz, &compound);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL(llv, 1);

  iwkv_cursor_close(&cur);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
}

static void iwkv_test8_v1() {
  iwkv_test1_impl(1);
}

static void iwkv_test8_v2() {
  iwkv_test1_impl(2);
}

static void iwkv_test7_impl(int fmt_version) {
  IWKV_OPTS opts = {
    .path        = "iwkv_test1_7.db",
    .oflags      = IWKV_TRUNC,
    .fmt_version = fmt_version
  };
  IWKV iwkv;
  IWDB db1;
  IWKV_val key, val;
  int64_t llv;
  key.data = "foo";
  key.size = strlen(key.data);

  iwrc rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  llv = 1;
  val.data = &llv;
  val.size = sizeof(llv);

  rc = iwkv_put(db1, &key, &val, IWKV_VAL_INCREMENT);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_put(db1, &key, &val, IWKV_VAL_INCREMENT);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_put(db1, &key, &val, IWKV_VAL_INCREMENT);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  llv = 0;
  val.data = 0;
  val.size = 0;
  rc = iwkv_get(db1, &key, &val);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  CU_ASSERT_EQUAL_FATAL(val.size, sizeof(llv));
  memcpy(&llv, val.data, sizeof(llv));
  llv = IW_ITOHLL(llv);
  CU_ASSERT_EQUAL(llv, 3);
  iwkv_val_dispose(&val);

  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
}

static void iwkv_test7_v1() {
  iwkv_test7_impl(1);
}

static void iwkv_test7_v2() {
  iwkv_test7_impl(2);
}

static void iwkv_test6_impl(int fmt_version) {
  IWKV_OPTS opts = {
    .path        = "iwkv_test1_6.db",
    .oflags      = IWKV_TRUNC,
    .fmt_version = fmt_version
  };
  const int vbsiz = 1000 * 1000;
  // Test open/close
  char kbuf[100];
  char *vbuf = malloc(vbsiz);
  IWKV iwkv;
  IWDB db1;
  IWKV_val key, val;

  iwrc rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);

  rc = iwkv_db(iwkv, 1, 0, &db1);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  key.data = kbuf;
  val.data = vbuf;
  val.size = vbsiz;
  for (int i = 0; i < 20; ++i) {
    memset(vbuf, ' ' + i + 1, vbsiz);
    snprintf(kbuf, sizeof(kbuf), "%016d", i);
    key.size = strlen(kbuf);
    rc = iwkv_put(db1, &key, &val, 0);
    if (rc) {
      iwlog_ecode_error3(rc);
    }
    CU_ASSERT_EQUAL_FATAL(rc, 0);
  }
  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL_FATAL(rc, 0);
  free(vbuf);
}

static void iwkv_test6_v1() {
  iwkv_test6_impl(1);
}

static void iwkv_test6_v2() {
  iwkv_test6_impl(2);
}

static void iwkv_test9(void) {
  IWKV_OPTS opts = {
    .path = "garbage.data",
  };
  // Test open/close
  IWKV iwkv;
  iwrc rc = iwkv_open(&opts, &iwkv);
  CU_ASSERT_EQUAL(rc, IWFS_ERROR_INVALID_FILEMETA);
  rc = iwkv_close(&iwkv);
  CU_ASSERT_EQUAL(rc, IW_ERROR_INVALID_STATE);
}

int main() {
  CU_pSuite pSuite = NULL;

  /* Initialize the CUnit test registry */
  if (CUE_SUCCESS != CU_initialize_registry()) {
    return CU_get_error();
  }

  /* Add a suite to the registry */
  pSuite = CU_add_suite("iwkv_test1", init_suite, clean_suite);

  if (NULL == pSuite) {
    CU_cleanup_registry();
    return CU_get_error();
  }

  /* Add the tests to the suite */
  if (  (NULL == CU_add_test(pSuite, "iwkv_test1_v1", iwkv_test1_v1))
     || (NULL == CU_add_test(pSuite, "iwkv_test1_v2", iwkv_test1_v2))
     || (NULL == CU_add_test(pSuite, "iwkv_test2_v1", iwkv_test2_v1))
     || (NULL == CU_add_test(pSuite, "iwkv_test2_v2", iwkv_test2_v2))
     || (NULL == CU_add_test(pSuite, "iwkv_test3_v1", iwkv_test3_v1))
     || (NULL == CU_add_test(pSuite, "iwkv_test3_v2", iwkv_test3_v2)) ||

        //-    (NULL == CU_add_test(pSuite, "iwkv_test4", iwkv_test4)) ||
        //-    (NULL == CU_add_test(pSuite, "iwkv_test5", iwkv_test5)) ||

        (NULL == CU_add_test(pSuite, "iwkv_test6_v1", iwkv_test6_v1))
     || (NULL == CU_add_test(pSuite, "iwkv_test6_v2", iwkv_test6_v2))
     || (NULL == CU_add_test(pSuite, "iwkv_test7_v1", iwkv_test7_v1))
     || (NULL == CU_add_test(pSuite, "iwkv_test7_v2", iwkv_test7_v2))
     || (NULL == CU_add_test(pSuite, "iwkv_test8_v1", iwkv_test8_v1))
     || (NULL == CU_add_test(pSuite, "iwkv_test8_v2", iwkv_test8_v2))
     || (NULL == CU_add_test(pSuite, "iwkv_test9", iwkv_test9))) {
    CU_cleanup_registry();
    return CU_get_error();
  }

  /* Run all tests using the CUnit Basic interface */
  CU_basic_set_mode(CU_BRM_VERBOSE);
  CU_basic_run_tests();
  int ret = CU_get_error() || CU_get_number_of_failures();
  CU_cleanup_registry();
  return ret;
}
