#include <aggregator.h>
#include <database.h>

/* wraps a factory "handler" class. The "-aggregators" instance variable of
 * the SQLite3::Database holds an array of all AggrogatorWrappers.
 *
 * An AggregatorWrapper holds the following instance variables:
 * -handler_klass: the handler that creates the instances.
 * -instances:     array of all the cAggregatorInstance objects currently
 *                 in-flight for this aggregator. */
static VALUE cAggregatorWrapper;

/* wraps a instance of the "handler" class. Loses its reference at the end of
 * the xFinal callback.
 *
 * An AggregatorInstance holds the following instance variables:
 * -handler_instance: the instance to call `step` and `finalize` on.
 * -exc_status:       status returned by rb_protect.
 *                    != 0 if an exception occurred. If an exception occurred
 *                    `step` and `finalize` won't be called any more. */
static VALUE cAggregatorInstance;

typedef struct rb_sqlite3_protected_funcall_args {
    VALUE self;
    ID method;
    int argc;
    VALUE *params;
} protected_funcall_args_t;

/* why isn't there something like this in the ruby API? */
static VALUE
rb_sqlite3_protected_funcall_body(VALUE protected_funcall_args_ptr)
{
  protected_funcall_args_t *args =
    (protected_funcall_args_t*)protected_funcall_args_ptr;

  return rb_funcall2(args->self, args->method, args->argc, args->params);
}

static VALUE
rb_sqlite3_protected_funcall(VALUE self, ID method, int argc, VALUE *params,
                             int* exc_status)
{
  protected_funcall_args_t args = {
    .self = self, .method = method, .argc = argc, .params = params
  };
  return rb_protect(rb_sqlite3_protected_funcall_body, (VALUE)(&args), exc_status);
}

/* called in rb_sqlite3_aggregator_step and rb_sqlite3_aggregator_final. It
 * checks if the execution context already has an associated instance. If it
 * has one, it returns it. If there is no instance yet, it creates one and
 * associates it with the context. */
static VALUE
rb_sqlite3_aggregate_instance(sqlite3_context *ctx)
{
  VALUE aw = (VALUE) sqlite3_user_data(ctx);
  VALUE handler_klass = rb_iv_get(aw, "-handler_klass");
  VALUE inst;
  VALUE *inst_ptr = sqlite3_aggregate_context(ctx, (int)sizeof(VALUE));

  if (!inst_ptr) {
    rb_fatal("SQLite is out-of-merory");
  }

  inst = *inst_ptr;

  if (inst == Qfalse) { /* Qfalse == 0 */
    VALUE instances = rb_iv_get(aw, "-instances");
    int exc_status;

    inst = rb_class_new_instance(0, NULL, cAggregatorInstance);
    rb_iv_set(inst, "-handler_instance", rb_sqlite3_protected_funcall(
      handler_klass, rb_intern("new"), 0, NULL, &exc_status));
    rb_iv_set(inst, "-exc_status", INT2NUM(exc_status));

    rb_ary_push(instances, inst);

    *inst_ptr = inst;
  }

  if (inst == Qnil) {
    rb_fatal("SQLite called us back on an already destroyed aggregate instance");
  }

  return inst;
}

/* called by rb_sqlite3_aggregator_final. Unlinks and frees the
 * aggregator_instance_t, so the handler_instance won't be marked any more
 * and Ruby's GC may free it. */
static void
rb_sqlite3_aggregate_instance_destroy(sqlite3_context *ctx)
{
  VALUE aw = (VALUE) sqlite3_user_data(ctx);
  VALUE instances = rb_iv_get(aw, "-instances");
  VALUE *inst_ptr = sqlite3_aggregate_context(ctx, 0);
  VALUE inst;

  if (!inst_ptr || (inst = *inst_ptr)) {
    return;
  }

  if (inst == Qnil) {
    rb_fatal("attempt to destroy aggregate instance twice");
  }

  rb_iv_set(inst, "-handler_instance", Qnil); // may catch use-after-free
  if (rb_ary_delete(instances, inst) == Qnil) {
    rb_fatal("must be in instances at that point");
  }

  *inst_ptr = Qnil;
}

static void
rb_sqlite3_aggregator_step(sqlite3_context * ctx, int argc, sqlite3_value **argv)
{
  VALUE inst = rb_sqlite3_aggregate_instance(ctx);
  VALUE handler_instance = rb_iv_get(inst, "-handler_instance");
  VALUE * params = NULL;
  VALUE one_param;
  int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status"));
  int i;

  if (exc_status) {
    return;
  }

  if (argc == 1) {
    one_param = sqlite3val2rb(argv[0]);
    params = &one_param;
  }
  if (argc > 1) {
    params = xcalloc((size_t)argc, sizeof(VALUE));
    for(i = 0; i < argc; i++) {
      params[i] = sqlite3val2rb(argv[i]);
    }
  }
  rb_sqlite3_protected_funcall(
    handler_instance, rb_intern("step"), argc, params, &exc_status);
  if (argc > 1) {
    xfree(params);
  }

  rb_iv_set(inst, "-exc_status", INT2NUM(exc_status));
}

/* we assume that this function is only called once per execution context */
static void
rb_sqlite3_aggregator_final(sqlite3_context * ctx)
{
  VALUE inst = rb_sqlite3_aggregate_instance(ctx);
  VALUE handler_instance = rb_iv_get(inst, "-handler_instance");
  int exc_status = NUM2INT(rb_iv_get(inst, "-exc_status"));

  if (!exc_status) {
    VALUE result = rb_sqlite3_protected_funcall(
      handler_instance, rb_intern("finalize"), 0, NULL, &exc_status);
    if (!exc_status) {
      set_sqlite3_func_result(ctx, result);
    }
  }

  if (exc_status) {
    /* the user should never see this, as Statement.step() will pick up the
     * outstanding exception and raise it instead of generating a new one
     * for SQLITE_ERROR with message "Ruby Exception occurred" */
    sqlite3_result_error(ctx, "Ruby Exception occurred", -1);
  }

  rb_sqlite3_aggregate_instance_destroy(ctx);
}

/* call-seq: define_aggregator2(aggregator)
 *
 * Define an aggregrate function according to a factory object (the "handler")
 * that knows how to obtain to all the information. The handler must provide
 * the following class methods:
 *
 * +arity+:: corresponds to the +arity+ parameter of #create_aggregate. This
 *           message is optional, and if the handler does not respond to it,
 *           the function will have an arity of -1.
 * +name+:: this is the name of the function. The handler _must_ implement
 *          this message.
 * +new+:: this must be implemented by the handler. It should return a new
 *         instance of the object that will handle a specific invocation of
 *         the function.
 *
 * The handler instance (the object returned by the +new+ message, described
 * above), must respond to the following messages:
 *
 * +step+:: this is the method that will be called for each step of the
 *          aggregate function's evaluation. It should take parameters according
 *          to the *arity* definition.
 * +finalize+:: this is the method that will be called to finalize the
 *              aggregate function's evaluation. It should not take arguments.
 *
 * Note the difference between this function and #create_aggregate_handler
 * is that no FunctionProxy ("ctx") object is involved. This manifests in two
 * ways: The return value of the aggregate function is the return value of
 * +finalize+ and neither +step+ nor +finalize+ take an additional "ctx"
 * parameter.
 */
VALUE
rb_sqlite3_define_aggregator2(VALUE self, VALUE aggregator, VALUE ruby_name)
{
  /* define_aggregator is added as a method to SQLite3::Database in database.c */
  sqlite3RubyPtr ctx = sqlite3_database_unwrap(self);
  int arity, status;
  VALUE aw;
  VALUE aggregators;

  if (!ctx->db) {
    rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed database");
  }

  if (rb_respond_to(aggregator, rb_intern("arity"))) {
    VALUE ruby_arity = rb_funcall(aggregator, rb_intern("arity"), 0);
    arity = NUM2INT(ruby_arity);
  } else {
    arity = -1;
  }

  if (arity < -1 || arity > 127) {
#ifdef PRIsVALUE
    rb_raise(rb_eArgError, "%"PRIsVALUE" arity=%d out of range -1..127",
            self, arity);
#else
    rb_raise(rb_eArgError, "Aggregator arity=%d out of range -1..127", arity);
#endif
  }

  if (!rb_ivar_defined(self, rb_intern("-aggregators"))) {
    rb_iv_set(self, "-aggregators", rb_ary_new());
  }
  aggregators = rb_iv_get(self, "-aggregators");

  aw = rb_class_new_instance(0, NULL, cAggregatorWrapper);
  rb_iv_set(aw, "-handler_klass", aggregator);
  rb_iv_set(aw, "-instances", rb_ary_new());

  status = sqlite3_create_function(
    ctx->db,
    StringValueCStr(ruby_name),
    arity,
    SQLITE_UTF8,
    (void*)aw,
    NULL,
    rb_sqlite3_aggregator_step,
    rb_sqlite3_aggregator_final
  );

  if (status != SQLITE_OK) {
    rb_sqlite3_raise(ctx->db, status);
    return self; // just in case rb_sqlite3_raise returns.
  }

  rb_ary_push(aggregators, aw);

  return self;
}

void
rb_sqlite3_aggregator_init(void)
{
  /* rb_class_new generatos class with undefined allocator in ruby 1.9 */
  cAggregatorWrapper = rb_funcall(rb_cClass, rb_intern("new"), 0);
  rb_gc_register_mark_object(cAggregatorWrapper);

  cAggregatorInstance = rb_funcall(rb_cClass, rb_intern("new"), 0);
  rb_gc_register_mark_object(cAggregatorInstance);
}
