/* gcc example-session.c -o example-session \
 *     $(pkg-config --cflags --libs libmongoc-1.0) */

/* ./example-session [CONNECTION_STRING] */

#include <mongoc/mongoc.h>

#include <stdio.h>


int
main(int argc, char *argv[])
{
   int exit_code = EXIT_FAILURE;

   mongoc_client_t *client = NULL;
   const char *uri_string = "mongodb://127.0.0.1/?appname=session-example";
   mongoc_uri_t *uri = NULL;
   mongoc_client_session_t *client_session = NULL;
   mongoc_collection_t *collection = NULL;
   bson_error_t error;
   bson_t *selector = NULL;
   bson_t *update = NULL;
   bson_t *update_opts = NULL;
   bson_t *find_opts = NULL;
   mongoc_read_prefs_t *secondary = NULL;
   mongoc_cursor_t *cursor = NULL;
   const bson_t *doc;
   char *str;
   bool r;

   mongoc_init();

   if (argc > 1) {
      uri_string = argv[1];
   }

   uri = mongoc_uri_new_with_error(uri_string, &error);
   if (!uri) {
      fprintf(stderr,
              "failed to parse URI:\n"
              "error message: %s\n",
              error.message);
      goto done;
   }

   client = mongoc_client_new_from_uri(uri);
   if (!client) {
      goto done;
   }

   mongoc_client_set_error_api(client, 2);

   /* pass NULL for options - by default the session is causally consistent */
   client_session = mongoc_client_start_session(client, NULL, &error);
   if (!client_session) {
      fprintf(stderr, "Failed to start session: %s\n", error.message);
      goto done;
   }

   collection = mongoc_client_get_collection(client, "test", "collection");
   selector = BCON_NEW("_id", BCON_INT32(1));
   update = BCON_NEW("$inc", "{", "x", BCON_INT32(1), "}");
   update_opts = bson_new();
   if (!mongoc_client_session_append(client_session, update_opts, &error)) {
      fprintf(stderr, "Could not add session to opts: %s\n", error.message);
      goto done;
   }

   r = mongoc_collection_update_one(collection, selector, update, update_opts, NULL /* reply */, &error);

   if (!r) {
      fprintf(stderr, "Update failed: %s\n", error.message);
      goto done;
   }

   bson_destroy(selector);
   selector = BCON_NEW("_id", BCON_INT32(1));
   secondary = mongoc_read_prefs_new(MONGOC_READ_SECONDARY);

   find_opts = BCON_NEW("maxTimeMS", BCON_INT32(2000));
   if (!mongoc_client_session_append(client_session, find_opts, &error)) {
      fprintf(stderr, "Could not add session to opts: %s\n", error.message);
      goto done;
   }

   /* read from secondary. since we're in a causally consistent session, the
    * data is guaranteed to reflect the update we did on the primary. the query
    * blocks waiting for the secondary to catch up, if necessary, or times out
    * and fails after 2000 ms.
    */
   cursor = mongoc_collection_find_with_opts(collection, selector, find_opts, secondary);

   while (mongoc_cursor_next(cursor, &doc)) {
      str = bson_as_relaxed_extended_json(doc, NULL);
      fprintf(stdout, "%s\n", str);
      bson_free(str);
   }

   if (mongoc_cursor_error(cursor, &error)) {
      fprintf(stderr, "Cursor Failure: %s\n", error.message);
      goto done;
   }

   exit_code = EXIT_SUCCESS;

done:
   if (find_opts) {
      bson_destroy(find_opts);
   }
   if (update) {
      bson_destroy(update);
   }
   if (selector) {
      bson_destroy(selector);
   }
   if (update_opts) {
      bson_destroy(update_opts);
   }
   if (secondary) {
      mongoc_read_prefs_destroy(secondary);
   }
   /* destroy cursor, collection, session before the client they came from */
   if (cursor) {
      mongoc_cursor_destroy(cursor);
   }
   if (collection) {
      mongoc_collection_destroy(collection);
   }
   if (client_session) {
      mongoc_client_session_destroy(client_session);
   }
   if (uri) {
      mongoc_uri_destroy(uri);
   }
   if (client) {
      mongoc_client_destroy(client);
   }

   mongoc_cleanup();

   return exit_code;
}
