/*
 * call-seq:
 *    conn.send_query_prepared( statement_name [, params, result_format ] )
 *      -> nil
 *
 * Execute prepared named statement specified by _statement_name_
 * asynchronously, and returns immediately.
 * On failure, it raises a PGError exception.
 *
 * +params+ is an array of the optional bind parameters for the 
 * SQL query. Each element of the +params+ array may be either:
 *   a hash of the form:
 *     {:value  => String (value of bind parameter)
 *      :format => Fixnum (0 for text, 1 for binary)
 *     }
 *   or, it may be a String. If it is a string, that is equivalent to the hash:
 *     { :value => <string value>, :format => 0 }
 * 
 * PostgreSQL bind parameters are represented as $1, $1, $2, etc.,
 * inside the SQL query. The 0th element of the +params+ array is bound
 * to $1, the 1st element is bound to $2, etc. +nil+ is treated as +NULL+.
 *
 * The optional +result_format+ should be 0 for text results, 1
 * for binary.
 */
static VALUE
pgconn_send_query_prepared(int argc, VALUE *argv, VALUE self)
{
    PGconn *conn = get_pgconn(self);
    int result;
    VALUE name, params, in_res_fmt;
    VALUE param, param_value, param_format;
    VALUE param_value_tmp;
    VALUE sym_value, sym_format;
    VALUE gc_array;
    VALUE error;
    int i = 0;
    int nParams;
    char ** paramValues;
    int *paramLengths;
    int *paramFormats;
    int resultFormat;

    rb_scan_args(argc, argv, "12", &name, &params, &in_res_fmt);
    Check_Type(name, T_STRING);

    if(NIL_P(params)) {
        params = rb_ary_new2(0);
        resultFormat = 0;
    }
    else {
        Check_Type(params, T_ARRAY);
    }

    if(NIL_P(in_res_fmt)) {
        resultFormat = 0;
    }
    else {
        resultFormat = NUM2INT(in_res_fmt);
    }

    gc_array = rb_ary_new();
    rb_gc_register_address(&gc_array);
    sym_value = ID2SYM(rb_intern("value"));
    sym_format = ID2SYM(rb_intern("format"));
    nParams = RARRAY_LEN(params);
    paramValues = ALLOC_N(char *, nParams);
    paramLengths = ALLOC_N(int, nParams);
    paramFormats = ALLOC_N(int, nParams);
    for(i = 0; i < nParams; i++) {
        param = rb_ary_entry(params, i);
        if (TYPE(param) == T_HASH) {
            param_value_tmp = rb_hash_aref(param, sym_value);
            if(param_value_tmp == Qnil)
                param_value = param_value_tmp;
            else
                param_value = rb_obj_as_string(param_value_tmp);
            param_format = rb_hash_aref(param, sym_format);
        }
        else {
            if(param == Qnil)
                param_value = param;
            else
                param_value = rb_obj_as_string(param);
            param_format = INT2NUM(0);
        }

        if(param_value == Qnil) {
            paramValues[i] = NULL;
            paramLengths[i] = 0;
        }
        else {
            Check_Type(param_value, T_STRING);
            /* make sure param_value doesn't get freed by the GC */
            rb_ary_push(gc_array, param_value);
            paramValues[i] = StringValuePtr(param_value);
            paramLengths[i] = RSTRING_LEN(param_value);
        }

        if(param_format == Qnil)
            paramFormats[i] = 0;
        else
            paramFormats[i] = NUM2INT(param_format);
    }

    result = PQsendQueryPrepared(conn, StringValuePtr(name), nParams, 
        (const char * const *)paramValues, paramLengths, paramFormats, 
        resultFormat);

    rb_gc_unregister_address(&gc_array);

    xfree(paramValues);
    xfree(paramLengths);
    xfree(paramFormats);

    if(result == 0) {
        error = rb_exc_new2(rb_ePGError, PQerrorMessage(conn));
        rb_iv_set(error, "@connection", self);
        rb_exc_raise(error);
    }
    return Qnil;
}