#include <stdio.h>

#include "mongoc/mongoc-array-private.h"
#include "mongoc/mongoc-thread-private.h"
#include "future.h"
#include "../test-libmongoc.h"

{{ header_comment }}

#define FUTURE_TIMEOUT_ABORT                         \
   if (1) {                                          \
      fflush (stdout);                               \
      fprintf (stderr, "%s timed out\n", BSON_FUNC); \
      fflush (stderr);                               \
      abort ();                                      \
   } else                                            \
      ((void) 0)

void
future_get_void (future_t *future)
{
   if (future_wait (future)) {
      return;
   }

   FUTURE_TIMEOUT_ABORT;
}

{% for T in type_list %}
{{ T }}
future_get_{{ T }} (future_t *future)
{
   if (future_wait (future)) {
      return future_value_get_{{ T }} (&future->return_value);
   }

   FUTURE_TIMEOUT_ABORT;
}
{% endfor %}

future_t *
future_new (future_value_type_t return_type, int argc)
{
   future_t *future;

   future = (future_t *)bson_malloc0 (sizeof *future);
   future->return_value.type = return_type;
   future->argc = argc;
   future->argv = (future_value_t *)bson_malloc0 ((size_t) argc * sizeof(future_value_t));
   mongoc_cond_init (&future->cond);
   bson_mutex_init (&future->mutex);

   return future;
}

future_value_t *
future_get_param (future_t *future, int i)
{
   return &future->argv[i];
}

void
future_start (future_t *future,
              BSON_THREAD_FUN_TYPE (start_routine))
{
   int r = mcommon_thread_create (&future->thread,
                                  start_routine,
                                  (void *) future);

   BSON_ASSERT (!r);
}


void
future_resolve (future_t *future, future_value_t return_value)
{
   bson_mutex_lock (&future->mutex);
   BSON_ASSERT (!future->resolved);
   BSON_ASSERT (future->return_value.type == return_value.type);
   future->return_value = return_value;
   future->resolved = true;
   mongoc_cond_signal (&future->cond);
   bson_mutex_unlock (&future->mutex);
}


bool
future_wait_max (future_t *future, int64_t timeout_ms)
{
   int64_t remaining_usec = timeout_ms * 1000;
   int64_t deadline = bson_get_monotonic_time () + timeout_ms * 1000;
   bool resolved;

   bson_mutex_lock (&future->mutex);
   while (!future->resolved && remaining_usec > 0) {
      mongoc_cond_timedwait (&future->cond, &future->mutex,
                             remaining_usec / 1000);
      remaining_usec = deadline - bson_get_monotonic_time ();
   }
   resolved = future->resolved;
   bson_mutex_unlock (&future->mutex);

   if (resolved) {
      future->awaited = true;

      /* free memory */
      mcommon_thread_join (future->thread);
   }

   return resolved;
}


bool
future_wait (future_t *future)
{
   return future_wait_max (future, get_future_timeout_ms ());
}


void
future_destroy (future_t *future)
{
   BSON_ASSERT (future->awaited);
   bson_free (future->argv);
   mongoc_cond_destroy (&future->cond);
   bson_mutex_destroy (&future->mutex);
   bson_free (future);
}
