/*
 * Execute prepared SQL statement. Supports input and output parameters.
 */
/* {{{ proto int odbc_execute(int result_id [, array parameters_array])
   Execute a prepared statement */
PHP_FUNCTION(odbc_execute)
{ 

	/* XXX: Move this out of here */
#define STORED_PROCEDURE_RETURNSIZE 2048

    pval **pv_res, **pv_param_arr, **tmp;
    typedef struct params_t {
		SDWORD vallen;
		int fp;
	} params_t;
	params_t *params = NULL;
	char *filename;
   	SWORD sqltype, ctype, scale;
	SQLSMALLINT nullable;
	UDWORD precision;
   	odbc_result   *result;
	int numArgs, i, ne;
	RETCODE rc;
	UCHAR *t_output_parameters;
	SDWORD *t_output_param_size;

	int t_current_parameter = 0;
	int t_num_params;

	char t_tracer[STORED_PROCEDURE_RETURNSIZE+256];

	numArgs = ARG_COUNT(ht);
	if (numArgs == 1) {
		if (getParametersEx(1, &pv_res) == FAILURE)
			WRONG_PARAM_COUNT;
	} else {
		if (getParametersEx(2, &pv_res, &pv_param_arr) == FAILURE)
			WRONG_PARAM_COUNT;

        if ((*pv_param_arr)->type != IS_ARRAY) {
            php_error(E_WARNING, "No array passed to odbc_execute()");
            return;
        }
    }

	ZEND_FETCH_RESOURCE(result, odbc_result *, pv_res, -1, "ODBC result", le_result);
	
	/* XXX check for already bound parameters*/
	if (result->numparams > 0 && numArgs == 1) {
		php_error(E_WARNING, "No parameters to SQL statement given");
		RETURN_FALSE;
	}

    if (result->numparams > 0) {
		if ((ne = zend_hash_num_elements((*pv_param_arr)->value.ht)) < result->numparams) {
			php_error(E_WARNING,"Not enough parameters (%d should be %d) given",
					   ne, result->numparams);
			RETURN_FALSE;
		}

		zend_hash_internal_pointer_reset((*pv_param_arr)->value.ht);
        params = (params_t *)emalloc(sizeof(params_t) * result->numparams);
		
		/* Allocate storage space for return from stored procedure */

		t_output_parameters = emalloc(result->numparams * STORED_PROCEDURE_RETURNSIZE);
		t_output_param_size = emalloc(result->numparams * (sizeof (SDWORD)));

		memset(t_output_parameters, 0, (result->numparams * (sizeof(UCHAR)))); /* Empty params */
		memset(t_output_param_size, 0, (result->numparams * (sizeof (SDWORD)))); /* Empty size information */

		for(i = 1; i <= result->numparams; i++) {
            if (zend_hash_get_current_data((*pv_param_arr)->value.ht, (void **) &tmp) == FAILURE) {
                php_error(E_WARNING,"Error getting parameter");
                SQLFreeStmt(result->stmt,SQL_RESET_PARAMS);
				efree(params);
				efree(t_output_parameters);
				efree(t_output_param_size);
                RETURN_FALSE;
            }
            convert_to_string(*tmp);
			if ((*tmp)->type != IS_STRING) {
				php_error(E_WARNING,"Error converting parameter");
				SQLFreeStmt(result->stmt, SQL_RESET_PARAMS);
				efree(params);
				efree(t_output_parameters);
				efree(t_output_param_size);
				RETURN_FALSE;
			}
			
            SQLDescribeParam(result->stmt, (UWORD)i, &sqltype, &precision,
							 &scale, &nullable);
			params[i-1].vallen = (*tmp)->value.str.len;
			params[i-1].fp = -1;

			if (IS_SQL_BINARY(sqltype))
				ctype = SQL_C_BINARY;
			else
				ctype = SQL_C_CHAR;

			if ((*tmp)->value.str.val[0] == '\'' && 
				(*tmp)->value.str.val[(*tmp)->value.str.len - 1] == '\'') {
				filename = &(*tmp)->value.str.val[1];
				filename[(*tmp)->value.str.len - 2] = '\0';

                if ((params[i-1].fp = open(filename,O_RDONLY)) == -1) {
					php_error(E_WARNING,"Can't open file %s", filename);
					SQLFreeStmt(result->stmt, SQL_RESET_PARAMS);
					for(i = 0; i < result->numparams; i++) {
						if (params[i].fp != -1) {
							close(params[i].fp);
						}
					}
					efree(t_output_parameters);
					efree(params);
					RETURN_FALSE;
				}

				params[i-1].vallen = SQL_LEN_DATA_AT_EXEC(0);

				rc = SQLBindParameter(result->stmt, (UWORD)i, SQL_PARAM_INPUT,
									  ctype, sqltype, precision, scale,
									  (void *)params[i-1].fp, 0,
									  &params[i-1].vallen);
				
			} else {
				/* Check for whether this is INPUT or INPUT/OUTPUT variable */
				if ((*tmp)->value.str.val[0] == '&') { /* This is the way to tell us that we expect a return value. */ 
					if((*tmp)->value.str.len > 1) { /* This means that it's an INPUT and OUTPUT variable */
						t_output_param_size[t_current_parameter] = params[i-1].vallen-1;
						strncpy(t_output_parameters + (STORED_PROCEDURE_RETURNSIZE * t_current_parameter), (*tmp)->value.str.val+1, STORED_PROCEDURE_RETURNSIZE);				
						rc = SQLBindParameter(result->stmt, (UWORD)i, SQL_PARAM_INPUT_OUTPUT,
							ctype, sqltype, precision, scale,
							t_output_parameters + (STORED_PROCEDURE_RETURNSIZE * t_current_parameter), 0,
							&t_output_param_size[t_current_parameter]);
					} else { /* This means that it's an OUTPUT variable */
						rc = SQLBindParameter(result->stmt, (UWORD)i, SQL_PARAM_OUTPUT,
							ctype, sqltype, precision, scale,
							t_output_parameters + (STORED_PROCEDURE_RETURNSIZE * t_current_parameter), 0,
							&t_output_param_size[t_current_parameter]);
					}
					t_current_parameter++;
				} else {
					rc = SQLBindParameter(result->stmt, (UWORD)i, SQL_PARAM_INPUT,
									  ctype, sqltype, precision, scale,
									  (*tmp)->value.str.val, 0,
									  &params[i-1].vallen);
				}
			}
			zend_hash_move_forward((*pv_param_arr)->value.ht);
		}
	}
	/* Close cursor, needed for doing multiple selects */
	rc = SQLFreeStmt(result->stmt, SQL_CLOSE);
	
	t_num_params = t_current_parameter;
	t_current_parameter = 0;

	if (rc == SQL_ERROR) {
		ODBC_SQL_ERROR(result->conn_ptr->henv, result->conn_ptr->hdbc, result->stmt, "SQLFreeStmt");	
	}

	rc = SQLExecute(result->stmt);

	/* Pass data back to array */

	zend_hash_internal_pointer_reset((*pv_param_arr)->value.ht);
	for(i = 1; i <= result->numparams; i++) {
		char *t_data;
		if (zend_hash_get_current_data((*pv_param_arr)->value.ht, (void **) &tmp) == FAILURE) {
			php_error(E_WARNING,"Error getting parameter");
			SQLFreeStmt(result->stmt,SQL_RESET_PARAMS);
			efree(t_output_parameters);
			efree(t_output_param_size);
			efree(params);
			RETURN_FALSE;
		}
		convert_to_string(*tmp);
		t_data = estrdup(t_output_parameters + (STORED_PROCEDURE_RETURNSIZE * t_current_parameter));
		if((*tmp)->value.str.val[0] == '&') {

			(*tmp)->value.str.val = (t_data); /* Should this data be "free"'d somewhere? */
			(*tmp)->value.str.len = strlen(t_data);
			(*tmp)->type = IS_STRING;

			t_current_parameter++;
			if(t_current_parameter > t_num_params) {
				php_error(E_WARNING, "Internal error when populating original array with return values from stored procedure");
				RETURN_FALSE;
			}
		}
		zend_hash_move_forward((*pv_param_arr)->value.ht);
	}


	result->fetched = 0;
	if (rc == SQL_NEED_DATA) {
		char buf[4096];
		int fp, nbytes;
		while(rc == SQL_NEED_DATA) {
			rc = SQLParamData(result->stmt, (PTR FAR *)&fp);
			if (rc == SQL_NEED_DATA) {
				while((nbytes = read(fp, &buf, 4096)) > 0)
					SQLPutData(result->stmt,(UCHAR FAR*) &buf, nbytes);
			}
		}
	} else {
		if (rc != SQL_SUCCESS) {
			ODBC_SQL_ERROR(result->conn_ptr->henv, result->conn_ptr->hdbc, result->stmt, "SQLExecute");
			RETVAL_FALSE;
		}
	}	
	
	if (result->numparams > 0) {
		SQLFreeStmt(result->stmt, SQL_RESET_PARAMS);
		for(i = 0; i < result->numparams; i++) {
			if (params[i].fp != -1)
				close(params[i].fp);
		}
		efree(t_output_parameters);
		efree(t_output_param_size);
		efree(params);
	}

	
	if (rc == SQL_SUCCESS) {
		RETVAL_TRUE;
	}

	if (result->numcols == 0) {
		SQLNumResultCols(result->stmt, &(result->numcols));

		if (result->numcols > 0) {
			if (!odbc_bindcols(result)) {
				efree(result);
				RETVAL_FALSE;
			}
		} else {
			result->values = NULL;
		}
	}
}
/* }}} */
