#include <mongoc/mongoc-opts-helpers-private.h>
#include <mongoc/mongoc-read-concern-private.h>
#include <mongoc/mongoc-util-private.h>
#include <mongoc/mongoc-write-concern-private.h>

#include <mongoc/mongoc.h>

#include <bson/bson-types.h>

#include <json-test.h>
#include <test-libmongoc.h>


static void
compare_write_concern(const mongoc_write_concern_t *wc_correct, const mongoc_write_concern_t *wc)
{
   ASSERT_CMPINT32(wc_correct->w, ==, wc->w);
   ASSERT_CMPINT64(wc_correct->wtimeout, ==, wc->wtimeout);
   ASSERT_CMPINT(wc_correct->journal, ==, wc->journal);
}


static void
compare_read_concern(const mongoc_read_concern_t *rc_correct, const mongoc_read_concern_t *rc)
{
   ASSERT_CMPSTR(rc_correct->level, rc->level);
}

mongoc_write_concern_t *
convert_write_concern(const bson_t *wc_doc)
{
   mongoc_write_concern_t *wc;
   bson_iter_t iter;
   const char *key;

   wc = mongoc_write_concern_new();
   BSON_ASSERT(bson_iter_init(&iter, wc_doc));

   while (bson_iter_next(&iter)) {
      key = bson_iter_key(&iter);

      if (strcmp(key, "w") == 0) {
         if (BSON_ITER_HOLDS_UTF8(&iter)) {
            if (strcmp(bson_lookup_utf8(wc_doc, "w"), "majority") == 0) {
               mongoc_write_concern_set_w(wc, MONGOC_WRITE_CONCERN_W_MAJORITY);
            } else {
               mongoc_write_concern_set_wtag(wc, bson_lookup_utf8(wc_doc, "w"));
            }
         } else {
            if (bson_lookup_int32(wc_doc, "w") < 0) {
               goto invalid;
            }
            mongoc_write_concern_set_w(wc, bson_lookup_int32(wc_doc, "w"));
         }
      } else if (strcmp(key, "wtimeoutMS") == 0) {
         if (bson_lookup_int32(wc_doc, "wtimeoutMS") < 0) {
            goto invalid;
         }
         mongoc_write_concern_set_wtimeout_int64(wc, bson_lookup_int32(wc_doc, "wtimeoutMS"));
      } else if (strcmp(key, "journal") == 0) {
         mongoc_write_concern_set_journal(wc, bson_iter_value(&iter)->value.v_bool);
      }
   }

   if (wc->w == 0 && wc->journal == 1) {
      goto invalid;
   }

   return wc;

invalid:
   mongoc_write_concern_destroy(wc);
   return NULL;
}

mongoc_read_concern_t *
convert_read_concern(const bson_t *rc_doc)
{
   mongoc_read_concern_t *rc;
   bson_iter_t iter;
   const char *key;

   rc = mongoc_read_concern_new();
   BSON_ASSERT(bson_iter_init(&iter, rc_doc));

   while (bson_iter_next(&iter)) {
      key = bson_iter_key(&iter);

      if (strcmp(key, "level") == 0) {
         if (BSON_ITER_HOLDS_UTF8(&iter)) {
            mongoc_read_concern_set_level(rc, bson_lookup_utf8(rc_doc, "level"));
         } else {
            goto invalid;
         }
      }
   }

   return rc;

invalid:
   mongoc_read_concern_destroy(rc);
   return NULL;
}


static void
test_rw_concern_uri(void *scenario_vp)
{
   bson_iter_t scenario_iter;
   bson_iter_t test_iter;
   bson_t test;
   const char *description;
   const char *uri_str;
   bool valid;
   mongoc_uri_t *uri;
   bson_t rc_doc;
   bson_t wc_doc;
   const mongoc_read_concern_t *rc;
   const mongoc_write_concern_t *wc;
   mongoc_write_concern_t *wc_correct;
   mongoc_read_concern_t *rc_correct;

   BSON_ASSERT_PARAM(scenario_vp);
   const bson_t *const scenario = scenario_vp;

   /* initialize tests with the scenario */
   BSON_ASSERT(bson_iter_init_find(&scenario_iter, scenario, "tests"));
   BSON_ASSERT(bson_iter_recurse(&scenario_iter, &test_iter));

   while (bson_iter_next(&test_iter)) {
      bson_iter_bson(&test_iter, &test);

      description = bson_lookup_utf8(&test, "description");
      uri_str = bson_lookup_utf8(&test, "uri");
      valid = _mongoc_lookup_bool(&test, "valid", true);

      if (_mongoc_lookup_bool(&test, "warning", false)) {
         test_error("update the \"%s\" test to handle warning: true", description);
      }

      uri = mongoc_uri_new_with_error(uri_str, NULL);
      if (!valid) {
         BSON_ASSERT(!uri);
         continue;
      }

      BSON_ASSERT(uri);

      if (bson_has_field(&test, "readConcern")) {
         rc = mongoc_uri_get_read_concern(uri);
         bson_lookup_doc(&test, "readConcern", &rc_doc);
         rc_correct = convert_read_concern(&rc_doc);
         compare_read_concern(rc_correct, rc);
         mongoc_read_concern_destroy(rc_correct);
      }

      if (bson_has_field(&test, "writeConcern")) {
         wc = mongoc_uri_get_write_concern(uri);
         bson_lookup_doc(&test, "writeConcern", &wc_doc);
         wc_correct = convert_write_concern(&wc_doc);
         compare_write_concern(wc_correct, wc);
         mongoc_write_concern_destroy(wc_correct);
      }

      mongoc_uri_destroy(uri);
   }
}

static void
test_rw_concern_document(void *scenario_vp)
{
   bson_iter_t scenario_iter;
   bson_iter_t test_iter;
   bson_t test;
   bool valid;
   bson_t rc_doc;
   bson_t wc_doc;
   mongoc_write_concern_t *wc;
   mongoc_read_concern_t *rc;
   const bson_t *wc_doc_result;
   const bson_t *rc_doc_result;
   bson_t rc_doc_correct;
   bson_t wc_doc_correct;

   BSON_ASSERT_PARAM(scenario_vp);
   const bson_t *const scenario = scenario_vp;

   BSON_ASSERT(bson_iter_init_find(&scenario_iter, scenario, "tests"));
   BSON_ASSERT(bson_iter_recurse(&scenario_iter, &test_iter));

   while (bson_iter_next(&test_iter)) {
      bson_iter_bson(&test_iter, &test);
      valid = _mongoc_lookup_bool(&test, "valid", true);

      if (bson_has_field(&test, "readConcern")) {
         bson_lookup_doc(&test, "readConcern", &rc_doc);
         rc = convert_read_concern(&rc_doc);
      } else {
         rc = mongoc_read_concern_new();
      }

      if (bson_has_field(&test, "writeConcern")) {
         bson_lookup_doc(&test, "writeConcern", &wc_doc);
         wc = convert_write_concern(&wc_doc);
      } else {
         wc = mongoc_write_concern_new();
      }

      if (!valid) {
         BSON_ASSERT(rc == NULL || wc == NULL);
         mongoc_write_concern_destroy(wc);
         mongoc_read_concern_destroy(rc);
         continue;
      }

      if (bson_has_field(&test, "readConcernDocument")) {
         bson_lookup_doc(&test, "readConcernDocument", &rc_doc_correct);
         rc_doc_result = _mongoc_read_concern_get_bson(rc);
         match_bson(&rc_doc_correct, rc_doc_result, false /* is_command */);
      }

      if (bson_has_field(&test, "writeConcernDocument")) {
         bson_lookup_doc(&test, "writeConcernDocument", &wc_doc_correct);
         wc_doc_result = _mongoc_write_concern_get_bson(wc);
         match_bson(&wc_doc_correct, wc_doc_result, false);
      }

      mongoc_write_concern_destroy(wc);
      mongoc_read_concern_destroy(rc);
   }
}

void
test_read_write_concern_install(TestSuite *suite)
{
   install_json_test_suite(suite, JSON_DIR, "read_write_concern/connection-string", &test_rw_concern_uri);
   install_json_test_suite(suite, JSON_DIR, "read_write_concern/document", &test_rw_concern_document);
}
