/*
 * IBM_DB::bind_param --  Binds a Ruby variable to an SQL statement parameter
 *
 * ===Description
 * bool IBM_DB::bind_param ( resource stmt, int parameter-number, string variable-name [, int parameter-type
 *                                         [, int data-type [, int precision [, int scale [, int size[]]]]]] )
 *
 * Binds a Ruby variable to an SQL statement parameter in a statement resource returned by IBM_DB::prepare().
 * This function gives you more control over the parameter type, data type, precision, and scale for
 * the parameter than simply passing the variable as part of the optional input array to IBM_DB::execute().
 *
 * ===Parameters
 *
 * stmt
 *   A prepared statement returned from IBM_DB::prepare(). 
 *
 * parameter-number
 *   Specifies the 1-indexed position of the parameter in the prepared statement. 
 *
 * variable-name
 *   A string specifying the name of the Ruby variable to bind to the parameter specified by parameter-number. 
 *
 * parameter-type
 *   A constant specifying whether the Ruby variable should be bound to the SQL parameter as an input parameter
 *   (SQL_PARAM_INPUT), an output parameter (SQL_PARAM_OUTPUT), or as a parameter that accepts input and returns output
 *   (SQL_PARAM_INPUT_OUTPUT). To avoid memory overhead, you can also specify PARAM_FILE to bind the Ruby variable
 *   to the name of a file that contains large object (BLOB, CLOB, or DBCLOB) data. 
 *
 * data-type
 *   A constant specifying the SQL data type that the Ruby variable should be bound as: one of SQL_BINARY,
 *   DB2_CHAR, DB2_DOUBLE, or DB2_LONG . 
 *
 * precision
 *   Specifies the precision that the variable should be bound to the database. 
 *
 * scale
 *    Specifies the scale that the variable should be bound to the database. 
 *
 * size
 *    Specifies the size that should be retreived from an INOUT/OUT parameter.
 *
 * ===Return Values
 *
 * Returns TRUE on success or FALSE on failure. 
 */
VALUE ibm_db_bind_param(int argc, VALUE *argv, VALUE self)
{
  char *varname = NULL;
  char error[DB2_MAX_ERR_MSG_LEN];
  long varname_len;
  long param_type = SQL_PARAM_INPUT;
  /* LONG types used for data being passed in */
  SQLUSMALLINT param_no = 0;
  long data_type = 0;
  long precision = 0;
  long scale = 0;
  long size = 0;
  SQLSMALLINT sql_data_type = 0;
  SQLUINTEGER sql_precision = 0;
  SQLSMALLINT sql_scale = 0;
  SQLSMALLINT sql_nullable = SQL_NO_NULLS;

  VALUE stmt = Qnil;
  stmt_handle *stmt_res;
  int rc = 0;

  VALUE r_param_no, r_varname, r_param_type=Qnil, r_size=Qnil;
  VALUE r_data_type=Qnil, r_precision=Qnil, r_scale=Qnil;
  rb_scan_args(argc, argv, "35", &stmt, &r_param_no, &r_varname, 
    &r_param_type, &r_data_type, &r_precision, &r_scale, &r_size);
  param_no = NUM2INT(r_param_no);
  varname = rb_str2cstr(r_varname, &varname_len);
  if (!NIL_P(r_param_type)) param_type = NUM2LONG(r_param_type);
  if (!NIL_P(r_data_type)) data_type = NUM2LONG(r_data_type);
  if (!NIL_P(r_precision)) precision = NUM2LONG(r_precision);
  if (!NIL_P(r_scale)) scale = NUM2LONG(r_scale);
  if (!NIL_P(r_size)) size = NUM2LONG(r_size);

  if (!NIL_P(stmt)) {
    Data_Get_Struct(stmt, stmt_handle, stmt_res);

    /* Check for Param options */
    switch (argc) {
      /* if argc == 3, then the default value for param_type will be used */
      case 3:
        param_type = SQL_PARAM_INPUT;
        rc = SQLDescribeParam((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)param_no, &sql_data_type, &sql_precision, &sql_scale, &sql_nullable);
        if ( rc == SQL_ERROR ) {
          _ruby_ibm_db_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
          sprintf(error, "Describe Param Failed: %s", IBM_DB_G(__ruby_stmt_err_msg));
          rb_throw(error, Qnil);
          return Qfalse;
        }
        /* Add to cache */
        _ruby_ibm_db_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, size, sql_data_type, sql_precision, sql_scale, sql_nullable );
        break;

      case 4:
        rc = SQLDescribeParam((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)param_no, &sql_data_type, &sql_precision, &sql_scale, &sql_nullable);
        if ( rc == SQL_ERROR ) {
          _ruby_ibm_db_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
          sprintf(error, "Describe Param Failed: %s", IBM_DB_G(__ruby_stmt_err_msg));
          rb_throw(error, Qnil);
          return Qfalse;
        }
        /* Add to cache */
        _ruby_ibm_db_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, size, sql_data_type, sql_precision, sql_scale, sql_nullable );
        break;

      case 5:
        rc = SQLDescribeParam((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)param_no, &sql_data_type, &sql_precision, &sql_scale, &sql_nullable);
        if ( rc == SQL_ERROR ) {
          _ruby_ibm_db_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
          sprintf(error, "Describe Param Failed: %s", IBM_DB_G(__ruby_stmt_err_msg));
          rb_throw(error, Qnil);
          return Qfalse;
        }
        sql_data_type = (SQLSMALLINT)data_type;
        /* Add to cache */
        _ruby_ibm_db_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, size, sql_data_type, sql_precision, sql_scale, sql_nullable );
        break;

      case 6:
        rc = SQLDescribeParam((SQLHSTMT)stmt_res->hstmt, (SQLUSMALLINT)param_no, &sql_data_type, &sql_precision, &sql_scale, &sql_nullable);
        if ( rc == SQL_ERROR ) {
          _ruby_ibm_db_check_sql_errors(stmt_res->hstmt, SQL_HANDLE_STMT, rc, 1, NULL, -1, 1);
          sprintf(error, "Describe Param Failed: %s", IBM_DB_G(__ruby_stmt_err_msg));
          rb_throw(error, Qnil);
          return Qfalse;
        }
        sql_data_type = (SQLSMALLINT)data_type;
        sql_precision = (SQLUINTEGER)precision;
        /* Add to cache */
        _ruby_ibm_db_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, size, sql_data_type, sql_precision, sql_scale, sql_nullable );
        break;

      case 7:
      case 8:
        /* Cache param data passed */
        /* I am using a linked list of nodes here because I dont know before hand how many params are being passed in/bound. */
        /* To determine this, a call to SQLNumParams is necessary. This is take away any advantages an array would have over linked list access */
        /* Data is being copied over to the correct types for subsequent CLI call because this might cause problems on other platforms such as AIX */
        sql_data_type = (SQLSMALLINT)data_type;
        sql_precision = (SQLUINTEGER)precision;
        sql_scale = (SQLSMALLINT)scale;
        _ruby_ibm_db_add_param_cache( stmt_res, param_no, varname, varname_len, param_type, size, sql_data_type, sql_precision, sql_scale, sql_nullable );
        break;

      default:
        /* WRONG_PARAM_COUNT; */
        return Qfalse;
    }
    /* end Switch */

    /* We bind data with DB2 CLI in IBM_DB::execute() */
    /* This will save network flow if we need to override params in it */

    return Qtrue;
  } else {
    rb_throw("Supplied parameter is invalid", Qnil);
    return Qfalse;
  }
}