/*
 * call-seq:
 *    conn.exec_prepared(statement_name [, params, result_format ] ) -> PGresult
 *
 * Execute prepared named statement specified by _statement_name_.
 * Returns a PGresult instance on success.
 * 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_exec_prepared(int argc, VALUE *argv, VALUE self)
{
        PGconn *conn = get_pgconn(self);
        PGresult *result = NULL;
        VALUE rb_pgresult;
        VALUE name, params, in_res_fmt;
        VALUE param, param_value, param_format;
        VALUE param_value_tmp;
        VALUE sym_value, sym_format;
        VALUE gc_array;
        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(params)->len;
        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 = PQexecPrepared(conn, StringValuePtr(name), nParams, 
                (const char * const *)paramValues, paramLengths, paramFormats, 
                resultFormat);

        rb_gc_unregister_address(&gc_array);

        free(paramValues);
        free(paramLengths);
        free(paramFormats);

        rb_pgresult = new_pgresult(result);
        pgresult_check(self, rb_pgresult);
        if (rb_block_given_p()) {
                return rb_ensure(yield_pgresult, rb_pgresult, 
                        pgresult_clear, rb_pgresult);
        }
        return rb_pgresult;
}