#include <bson/bson-bcon.h>

#include <TestSuite.h>

static void
test_utf8(void)
{
   const char *val;

   bson_t *bcon = BCON_NEW("hello", "world");

   BSON_ASSERT(BCON_EXTRACT(bcon, "hello", BCONE_UTF8(val)));

   BSON_ASSERT(strcmp(val, "world") == 0);

   bson_destroy(bcon);
}

static void
test_double(void)
{
   double val;

   bson_t *bcon = BCON_NEW("foo", BCON_DOUBLE(1.1));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_DOUBLE(val)));

   BSON_ASSERT(val == 1.1);

   bson_destroy(bcon);
}

static void
test_decimal128(void)
{
   bson_decimal128_t val = {0};
   bson_decimal128_t dec = {0};
   bson_t *bcon = NULL;

   bson_decimal128_from_string("12", &dec);
   bcon = BCON_NEW("foo", BCON_DECIMAL128(&dec));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_DECIMAL128(val)));

   BSON_ASSERT(val.low == 0xCULL);
   BSON_ASSERT(val.high == 0x3040000000000000ULL);

   bson_destroy(bcon);
}

static void
test_binary(void)
{
   bson_subtype_t subtype;
   uint32_t len;
   const uint8_t *binary;

   bson_t *bcon = BCON_NEW("foo", BCON_BIN(BSON_SUBTYPE_BINARY, (uint8_t *)"deadbeef", 8));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_BIN(subtype, binary, len)));

   BSON_ASSERT(subtype == BSON_SUBTYPE_BINARY);
   BSON_ASSERT(len == 8);
   BSON_ASSERT(memcmp(binary, "deadbeef", 8) == 0);

   bson_destroy(bcon);
}


static void
test_undefined(void)
{
   bson_t *bcon = BCON_NEW("foo", BCON_UNDEFINED);

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_UNDEFINED));

   bson_destroy(bcon);
}


static void
test_oid(void)
{
   bson_oid_t oid;
   bson_t *bcon;
   const bson_oid_t *ooid;

   bson_oid_init(&oid, NULL);

   bcon = BCON_NEW("foo", BCON_OID(&oid));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_OID(ooid)));

   BSON_ASSERT(bson_oid_equal(&oid, ooid));

   bson_destroy(bcon);
}

static void
test_bool(void)
{
   bool b;

   bson_t *bcon = BCON_NEW("foo", BCON_BOOL(true));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_BOOL(b)));

   BSON_ASSERT(b == true);

   bson_destroy(bcon);
}

static void
test_date_time(void)
{
   int64_t out;

   bson_t *bcon = BCON_NEW("foo", BCON_DATE_TIME(10000));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_DATE_TIME(out)));

   BSON_ASSERT(out == 10000);

   bson_destroy(bcon);
}


static void
test_null(void)
{
   bson_t *bcon = BCON_NEW("foo", BCON_NULL);

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_NULL));

   bson_destroy(bcon);
}


static void
test_regex(void)
{
   const char *regex;
   const char *flags;

   bson_t *bcon = BCON_NEW("foo", BCON_REGEX("^foo|bar$", "i"));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_REGEX(regex, flags)));

   BSON_ASSERT(strcmp(regex, "^foo|bar$") == 0);
   BSON_ASSERT(strcmp(flags, "i") == 0);

   bson_destroy(bcon);
}


static void
test_dbpointer(void)
{
   const char *collection;
   bson_oid_t oid;
   const bson_oid_t *ooid;
   bson_t *bcon;

   bson_oid_init(&oid, NULL);

   bcon = BCON_NEW("foo", BCON_DBPOINTER("collection", &oid));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_DBPOINTER(collection, ooid)));

   BSON_ASSERT(strcmp(collection, "collection") == 0);
   BSON_ASSERT(bson_oid_equal(ooid, &oid));

   bson_destroy(bcon);
}


static void
test_code(void)
{
   const char *val;

   bson_t *bcon = BCON_NEW("foo", BCON_CODE("var a = {};"));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_CODE(val)));

   BSON_ASSERT(strcmp(val, "var a = {};") == 0);

   bson_destroy(bcon);
}


static void
test_symbol(void)
{
   const char *val;

   bson_t *bcon = BCON_NEW("foo", BCON_SYMBOL("symbol"));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_SYMBOL(val)));

   BSON_ASSERT(strcmp(val, "symbol") == 0);

   bson_destroy(bcon);
}


static void
test_codewscope(void)
{
   const char *code;
   bson_t oscope;

   bson_t *scope = BCON_NEW("b", BCON_INT32(10));
   bson_t *bcon = BCON_NEW("foo", BCON_CODEWSCOPE("var a = b;", scope));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_CODEWSCOPE(code, oscope)));

   BSON_ASSERT(strcmp(code, "var a = b;") == 0);
   bson_eq_bson(&oscope, scope);

   bson_destroy(&oscope);
   bson_destroy(scope);
   bson_destroy(bcon);
}


static void
test_int32(void)
{
   int32_t i32;

   bson_t *bcon = BCON_NEW("foo", BCON_INT32(10));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_INT32(i32)));

   BSON_ASSERT(i32 == 10);

   bson_destroy(bcon);
}


static void
test_timestamp(void)
{
   int32_t timestamp;
   int32_t increment;

   bson_t *bcon = BCON_NEW("foo", BCON_TIMESTAMP(100, 1000));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_TIMESTAMP(timestamp, increment)));

   BSON_ASSERT(timestamp == 100);
   BSON_ASSERT(increment == 1000);

   bson_destroy(bcon);
}


static void
test_int64(void)
{
   int64_t i64;

   bson_t *bcon = BCON_NEW("foo", BCON_INT64(10));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_INT64(i64)));

   BSON_ASSERT(i64 == 10);

   bson_destroy(bcon);
}


static void
test_maxkey(void)
{
   bson_t *bcon = BCON_NEW("foo", BCON_MAXKEY);

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_MAXKEY));

   bson_destroy(bcon);
}


static void
test_minkey(void)
{
   bson_t *bcon = BCON_NEW("foo", BCON_MINKEY);

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_MINKEY));

   bson_destroy(bcon);
}


static void
test_bson_document(void)
{
   bson_t ochild;
   bson_t *child = BCON_NEW("bar", "baz");
   bson_t *bcon = BCON_NEW("foo", BCON_DOCUMENT(child));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_DOCUMENT(ochild)));

   bson_eq_bson(&ochild, child);

   bson_destroy(&ochild);
   bson_destroy(child);
   bson_destroy(bcon);
}


static void
test_bson_array(void)
{
   bson_t ochild;
   bson_t *child = BCON_NEW("0", "baz");
   bson_t *bcon = BCON_NEW("foo", BCON_ARRAY(child));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_ARRAY(ochild)));

   bson_eq_bson(&ochild, child);

   bson_destroy(&ochild);
   bson_destroy(child);
   bson_destroy(bcon);
}


static void
test_inline_array(void)
{
   int32_t a, b;

   bson_t *bcon = BCON_NEW("foo", "[", BCON_INT32(1), BCON_INT32(2), "]");

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", "[", BCONE_INT32(a), BCONE_INT32(b), "]"));

   BSON_ASSERT(a == 1);
   BSON_ASSERT(b == 2);

   bson_destroy(bcon);
}

static void
test_inline_doc(void)
{
   int32_t a, b;

   bson_t *bcon = BCON_NEW("foo", "{", "b", BCON_INT32(2), "a", BCON_INT32(1), "}");

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", "{", "a", BCONE_INT32(a), "b", BCONE_INT32(b), "}"));

   BSON_ASSERT(a == 1);
   BSON_ASSERT(b == 2);

   bson_destroy(bcon);
}


static void
test_extract_ctx_helper(bson_t *bson, ...)
{
   va_list ap;
   bcon_extract_ctx_t ctx;
   int i;
   int n;

   bcon_extract_ctx_init(&ctx);

   va_start(ap, bson);

   n = va_arg(ap, int);

   for (i = 0; i < n; i++) {
      BSON_ASSERT(bcon_extract_ctx_va(bson, &ctx, &ap));
   }

   va_end(ap);
}

static void
test_extract_ctx(void)
{
   int32_t a, b, c;

   bson_t *bson = BCON_NEW("a", BCON_INT32(1), "b", BCON_INT32(2), "c", BCON_INT32(3));

   test_extract_ctx_helper(bson, 3, "a", BCONE_INT32(a), NULL, "b", BCONE_INT32(b), NULL, "c", BCONE_INT32(c), NULL);

   BSON_ASSERT(a == 1);
   BSON_ASSERT(b == 2);
   BSON_ASSERT(c == 3);

   bson_destroy(bson);
}


static void
test_nested(void)
{
   const char *utf8;
   int i32;

   bson_t *bcon = BCON_NEW("hello", "world", "foo", "{", "bar", BCON_INT32(10), "}");

   BSON_ASSERT(BCON_EXTRACT(bcon, "hello", BCONE_UTF8(utf8), "foo", "{", "bar", BCONE_INT32(i32), "}"));

   BSON_ASSERT(strcmp("world", utf8) == 0);
   BSON_ASSERT(i32 == 10);

   bson_destroy(bcon);
}


static void
test_skip(void)
{
   bson_t *bcon = BCON_NEW("hello", "world", "foo", "{", "bar", BCON_INT32(10), "}");

   BSON_ASSERT(
      BCON_EXTRACT(bcon, "hello", BCONE_SKIP(BSON_TYPE_UTF8), "foo", "{", "bar", BCONE_SKIP(BSON_TYPE_INT32), "}"));

   BSON_ASSERT(
      !BCON_EXTRACT(bcon, "hello", BCONE_SKIP(BSON_TYPE_UTF8), "foo", "{", "bar", BCONE_SKIP(BSON_TYPE_INT64), "}"));

   bson_destroy(bcon);
}


static void
test_iter(void)
{
   bson_iter_t iter;
   bson_t *other;

   bson_t *bcon = BCON_NEW("foo", BCON_INT32(10));

   BSON_ASSERT(BCON_EXTRACT(bcon, "foo", BCONE_ITER(iter)));

   BSON_ASSERT(bson_iter_type(&iter) == BSON_TYPE_INT32);
   BSON_ASSERT(bson_iter_int32(&iter) == 10);

   other = BCON_NEW("foo", BCON_ITER(&iter));

   bson_eq_bson(other, bcon);

   bson_destroy(bcon);
   bson_destroy(other);
}


void
test_bcon_extract_install(TestSuite *suite)
{
   TestSuite_Add(suite, "/bson/bcon/extract/test_utf8", test_utf8);
   TestSuite_Add(suite, "/bson/bcon/extract/test_double", test_double);
   TestSuite_Add(suite, "/bson/bcon/extract/test_decimal128", test_decimal128);
   TestSuite_Add(suite, "/bson/bcon/extract/test_binary", test_binary);
   TestSuite_Add(suite, "/bson/bcon/extract/test_undefined", test_undefined);
   TestSuite_Add(suite, "/bson/bcon/extract/test_oid", test_oid);
   TestSuite_Add(suite, "/bson/bcon/extract/test_bool", test_bool);
   TestSuite_Add(suite, "/bson/bcon/extract/test_date_time", test_date_time);
   TestSuite_Add(suite, "/bson/bcon/extract/test_null", test_null);
   TestSuite_Add(suite, "/bson/bcon/extract/test_regex", test_regex);
   TestSuite_Add(suite, "/bson/bcon/extract/test_dbpointer", test_dbpointer);
   TestSuite_Add(suite, "/bson/bcon/extract/test_code", test_code);
   TestSuite_Add(suite, "/bson/bcon/extract/test_symbol", test_symbol);
   TestSuite_Add(suite, "/bson/bcon/extract/test_codewscope", test_codewscope);
   TestSuite_Add(suite, "/bson/bcon/extract/test_int32", test_int32);
   TestSuite_Add(suite, "/bson/bcon/extract/test_timestamp", test_timestamp);
   TestSuite_Add(suite, "/bson/bcon/extract/test_int64", test_int64);
   TestSuite_Add(suite, "/bson/bcon/extract/test_maxkey", test_maxkey);
   TestSuite_Add(suite, "/bson/bcon/extract/test_minkey", test_minkey);
   TestSuite_Add(suite, "/bson/bcon/extract/test_bson_document", test_bson_document);
   TestSuite_Add(suite, "/bson/bcon/extract/test_bson_array", test_bson_array);
   TestSuite_Add(suite, "/bson/bcon/extract/test_inline_array", test_inline_array);
   TestSuite_Add(suite, "/bson/bcon/extract/test_inline_doc", test_inline_doc);
   TestSuite_Add(suite, "/bson/bcon/extract/test_extract_ctx", test_extract_ctx);
   TestSuite_Add(suite, "/bson/bcon/extract/test_nested", test_nested);
   TestSuite_Add(suite, "/bson/bcon/extract/test_skip", test_skip);
   TestSuite_Add(suite, "/bson/bcon/extract/test_iter", test_iter);
}
