/*
  +----------------------------------------------------------------------+
  | PHP Version 5                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2013 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author: Wez Furlong <wez@php.net>                                    |
  |         Frank M. Kromann <frank@kromann.info>                        |
  +----------------------------------------------------------------------+
*/

/* $Id$ */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/php_string.h"
#include "ext/standard/info.h"
#include "pdo/php_pdo.h"
#include "pdo/php_pdo_driver.h"
#include "php_pdo_dblib.h"
#include "php_pdo_dblib_int.h"
#include "zend_exceptions.h"

static void free_rows(pdo_dblib_stmt *S TSRMLS_DC)
{
	int i, j;
	
	for (i = 0; i < S->nrows; i++) {
		for (j = 0; j < S->ncols; j++) {
			pdo_dblib_colval *val = &S->rows[i*S->ncols] + j;
			if (val->data) {
				efree(val->data);
				val->data = NULL;
			}
		}
	}
	efree(S->rows);
	S->rows = NULL;
	S->nrows = 0;
}

static int pdo_dblib_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC)
{
	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;

	if (S->rows) {
		free_rows(S TSRMLS_CC);
	}
	if (S->cols) {
		efree(S->cols);
	}
	efree(S);

	return 1;
}

static int pdo_dblib_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC)
{
	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
	pdo_dblib_db_handle *H = S->H;
	RETCODE resret, ret;
	int i, j;
	int arows;
	unsigned int size;
	
	dbsetuserdata(H->link, &S->err);

	if (S->rows) {
		/* clean them up */
		free_rows(S TSRMLS_CC);
	}

	if (FAIL == dbcmd(H->link, stmt->active_query_string)) {
		return 0;
	}
	if (FAIL == dbsqlexec(H->link)) {
		return 0;
	}
	
	resret = dbresults(H->link);
	if (resret == FAIL) {
		return 0;
	}

	ret = dbnextrow(H->link);

	stmt->row_count = DBCOUNT(H->link);

    if (ret == NO_MORE_ROWS) {
       return 1;
    }
    
	if (!S->cols) {
		S->ncols = dbnumcols(H->link);

		if (S->ncols <= 0) {
			return 1;
		}

		S->cols = ecalloc(S->ncols, sizeof(pdo_dblib_col));
		stmt->column_count = S->ncols;
	
		for (i = 0, j = 0; i < S->ncols; i++) {
			char *tmp = NULL;

			S->cols[i].coltype = dbcoltype(H->link, i+1);
			S->cols[i].name = (char*)dbcolname(H->link, i+1);

			if (!strlen(S->cols[i].name)) {
				if (j) {
					spprintf(&tmp, 0, "computed%d", j++);
					strlcpy(S->cols[i].name, tmp, strlen(tmp)+1);
					efree(tmp);
				} else {
					S->cols[i].name = "computed";
					j++;
				}
			}

			S->cols[i].source = (char*)dbcolsource(H->link, i+1);
			tmp = estrdup(S->cols[i].source ? S->cols[i].source : "");
			S->cols[i].source = tmp;
			efree(tmp);

			S->cols[i].maxlen = dbcollen(H->link, i+1);
		}
	}

	arows = 100;
	size = S->ncols * sizeof(pdo_dblib_colval);
	S->rows = safe_emalloc(arows, size, 0);

	/* let's fetch all the data */
	do {
		if (S->nrows >= arows) {
			arows *= 2;
			S->rows = erealloc(S->rows, arows * size);
		}
		for (i = 0; i < S->ncols; i++) {
			pdo_dblib_colval *val = &S->rows[S->nrows * S->ncols + i];

			if (dbdatlen(H->link, i+1) == 0 && dbdata(H->link, i+1) == NULL) {
				val->len = 0;
				val->data = NULL;
			} else {
				switch (S->cols[i].coltype) {
					case SQLCHAR:
					case SQLTEXT:
					case SQLVARBINARY:
					case SQLBINARY:
					case SQLIMAGE:
						val->len = dbdatlen(H->link, i+1);
						val->data = emalloc(val->len + 1);
						memcpy(val->data, dbdata(H->link, i+1), val->len);
						val->data[val->len] = '\0';
						break;
					case SQLMONEY:
					case SQLMONEY4:
					case SQLMONEYN: {
						DBFLT8 money_value;
						dbconvert(NULL, S->cols[i].coltype, dbdata(H->link, i+1), dbdatlen(H->link, i+1), SQLFLT8, (LPBYTE)&money_value, 8);
						val->len = spprintf(&val->data, 0, "%.4f", money_value);
						}
						break;
#ifdef SQLUNIQUE
					case SQLUNIQUE: {
#else
					case 36: { /* FreeTDS hack, also used by ext/mssql */
#endif
						val->len = 36+1;
						val->data = emalloc(val->len + 1);

						/* uniqueidentifier is a 16-byte binary number, convert to 32 char hex string */
#ifdef SQLUNIQUE
						val->len = dbconvert(NULL, SQLUNIQUE, dbdata(H->link, i+1), dbdatlen(H->link, i+1), SQLCHAR, val->data, val->len);
#else
						val->len = dbconvert(NULL, 36, dbdata(H->link, i+1), dbdatlen(H->link, i+1), SQLCHAR, val->data, val->len);
#endif
						php_strtoupper(val->data, val->len);
						break;
						}
					default:
						if (dbwillconvert(S->cols[i].coltype, SQLCHAR)) {
							val->len = 32 + (2 * dbdatlen(H->link, i+1));
							val->data = emalloc(val->len);

							val->len = dbconvert(NULL, S->cols[i].coltype, dbdata(H->link, i+1),
									dbdatlen(H->link, i+1), SQLCHAR, val->data, val->len);

							if (val->len >= 0) {
								val->data[val->len] = '\0';
							}
						} else {
							val->len = 0;
							val->data = NULL;
						}
				}
			}
		}

		S->nrows++;

		ret = dbnextrow(H->link);

		if (ret == BUF_FULL) {
			dbclrbuf(H->link, DBLASTROW(H->link)-1);
		}
	} while (ret != FAIL && ret != NO_MORE_ROWS);

	if (resret != NO_MORE_RESULTS) {
		/* there are additional result sets available */
		dbresults(H->link);
		/* cancel pending rows */
		dbcanquery(H->link);

		/* TODO: figure out a sane solution */
	}

	S->current = -1;
		
	return 1;	
}

static int pdo_dblib_stmt_fetch(pdo_stmt_t *stmt,
	enum pdo_fetch_orientation ori, long offset TSRMLS_DC)
{
	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;

	if (!S->rows) {
		return 0;
	}
	
	if (++S->current < S->nrows) {
		return 1;
	}

	return 0;
}

static int pdo_dblib_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC)
{
	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
	struct pdo_column_data *col = &stmt->columns[colno];

	if (!S->rows) {
		return 0;
	}

	col->maxlen = S->cols[colno].maxlen;	
	col->namelen = strlen(S->cols[colno].name);	
	col->name = estrdup(S->cols[colno].name);
	col->param_type = PDO_PARAM_STR;
		
	return 1;
}

static int pdo_dblib_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr,
	 unsigned long *len, int *caller_frees TSRMLS_DC)
{
	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;
	pdo_dblib_colval *val = &S->rows[S->current * S->ncols + colno];

	*ptr = val->data;
	*len = val->len;
	return 1;
}

static int pdo_dblib_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param,
		enum pdo_param_event event_type TSRMLS_DC)
{
	return 1;
}

static int dblib_dblib_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC)
{
	pdo_dblib_stmt *S = (pdo_dblib_stmt*)stmt->driver_data;

	if (S->rows) {
		free_rows(S TSRMLS_CC);
		S->rows = NULL;
	}

	return 1;
}

struct pdo_stmt_methods dblib_stmt_methods = {
	pdo_dblib_stmt_dtor,
	pdo_dblib_stmt_execute,
	pdo_dblib_stmt_fetch,
	pdo_dblib_stmt_describe,
	pdo_dblib_stmt_get_col,
	pdo_dblib_stmt_param_hook,
	NULL, /* set attr */
	NULL, /* get attr */
	NULL, /* meta */
	NULL, /* nextrow */
	dblib_dblib_stmt_cursor_closer
};

