/*
 * Oracle module result related functions
 *
 * Copyright (C) 2007,2008 TRUNK MOBILE
 *
 * This file is part of opensips, a free SIP server.
 *
 * opensips is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * opensips is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 */


#include <string.h>
#include <time.h>
#include <oci.h>
#include "../../db/db_res.h"
#include "../../db/db_row.h"
#include "../../mem/mem.h"
#include "../../dprint.h"
#include "ora_con.h"
#include "dbase.h"
#include "asynch.h"
#include "res.h"


#define MAX_DEF_HANDLES 64

struct dmap {
    OCIDefine* defh[MAX_DEF_HANDLES];
    union {
	dvoid* v;
	double* f;
	int* i;
	char* c;
	OCIDate* o;
    }pv[MAX_DEF_HANDLES];
    dvoid* pval[MAX_DEF_HANDLES];
    ub2 ilen[MAX_DEF_HANDLES];
    sb2 ind[MAX_DEF_HANDLES];
    ub2 len[MAX_DEF_HANDLES];
};
typedef struct dmap dmap_t;


/*
 * Get and convert columns from a result. Define handlers and buffers
 */
static int get_columns(ora_con_t* con, db_res_t* _r, OCIStmt* _c, dmap_t* _d)
{
	OCIParam *param;
	size_t tsz;
	ub4 i, n;
	sword status;

	status = OCIAttrGet(_c, OCI_HTYPE_STMT, &n, NULL, OCI_ATTR_PARAM_COUNT,
		con->errhp);

	if (status != OCI_SUCCESS) {
		LM_ERR("driver: %s\n", db_oracle_error(con, status));
		return -1;
	}

	if (!n) {
		LM_ERR("no columns\n");
		return -2;
	}

	if (n >= MAX_DEF_HANDLES) {
		LM_ERR("too many res. Rebuild with MAX_DEF_HANDLES >= %u\n", n);
		return -3;
	}

	if (db_allocate_columns(_r, n) != 0) {
		LM_ERR("could not allocate columns\n");
		return -4;
	}
	for (i = 0; i < n; ++i)
		memset(RES_NAMES(_r)[i], 0, sizeof(db_key_t));

	RES_COL_N(_r) = n;

	tsz = 0;
	memset(_d->defh, 0, sizeof(_d->defh[0]) * n);
	for (i = 0; i < n; i++) {
		ub4 len;
		ub2 dtype;

		status = OCIParamGet(_c, OCI_HTYPE_STMT, con->errhp,
			(dvoid**)(dvoid*)&param, i+1);
		if (status != OCI_SUCCESS) goto ora_err;

		{
			text* name;
			status = OCIAttrGet(param, OCI_DTYPE_PARAM,
				(dvoid**)(dvoid*)&name,	&len, OCI_ATTR_NAME,
				con->errhp);
			if (status != OCI_SUCCESS) goto ora_err;
			RES_NAMES(_r)[i]->s = (char*)pkg_malloc(len+1);
			if (!RES_NAMES(_r)[i]->s) {
				db_free_columns(_r);
				LM_ERR("no private memory left\n");
				return -5;
			}
			RES_NAMES(_r)[i]->len = len;
			memcpy(RES_NAMES(_r)[i]->s, name, len);
			RES_NAMES(_r)[i]->s[len] = '\0';
		}

		status = OCIAttrGet(param, OCI_DTYPE_PARAM,
			(dvoid**)(dvoid*)&dtype, NULL, OCI_ATTR_DATA_TYPE,
			con->errhp);
		if (status != OCI_SUCCESS) goto ora_err;

		switch (dtype) {
		case SQLT_UIN:		/* unsigned integer */
set_bitmap:
			LM_DBG("use DB_BITMAP type\n");
			RES_TYPES(_r)[i] = DB_BITMAP;
			len = sizeof(VAL_BITMAP((db_val_t*)NULL));
			break;

		case SQLT_INT:		/* (ORANET TYPE) integer */
set_int:
			LM_DBG("use DB_INT result type\n");
			RES_TYPES(_r)[i] = DB_INT;
			len = sizeof(VAL_INT((db_val_t*)NULL));
			break;

		case SQLT_LNG:		/* long */
		case SQLT_VNU:		/* NUM with preceding length byte */
		case SQLT_NUM:		/* (ORANET TYPE) oracle numeric */
			len = 0; /* PRECISION is ub1 */
			status = OCIAttrGet(param, OCI_DTYPE_PARAM,
				(dvoid**)(dvoid*)&len, NULL, OCI_ATTR_PRECISION,
				con->errhp);
			if (status != OCI_SUCCESS) goto ora_err;
			if (len <= 11) {
				sb1 sc;
				status = OCIAttrGet(param, OCI_DTYPE_PARAM,
					(dvoid**)(dvoid*)&sc, NULL,
					OCI_ATTR_SCALE, con->errhp);
				if (status != OCI_SUCCESS) goto ora_err;
				if (!sc) {
					dtype = SQLT_INT;
					if (len != 11) goto set_int;
					dtype = SQLT_UIN;
					goto set_bitmap;
				}
			}
			LM_DBG("use DB_BIGINT result type\n");
			RES_TYPES(_r)[i] = DB_BIGINT;
			len = sizeof(VAL_BIGINT((db_val_t*)NULL));
			dtype = SQLT_NUM;
			break;

		case SQLT_FLT:		/* (ORANET TYPE) Floating point number */
		case SQLT_BFLOAT:       /* Native Binary float*/
		case SQLT_BDOUBLE:	/* NAtive binary double */
		case SQLT_IBFLOAT:	/* binary float canonical */
		case SQLT_IBDOUBLE:	/* binary double canonical */
		case SQLT_PDN:		/* (ORANET TYPE) Packed Decimal Numeric */
			LM_DBG("use DB_DOUBLE result type\n");
			RES_TYPES(_r)[i] = DB_DOUBLE;
			len = sizeof(VAL_DOUBLE((db_val_t*)NULL));
			dtype = SQLT_FLT;
			break;

//		case SQLT_TIME:		/* TIME */
//		case SQLT_TIME_TZ:	/* TIME WITH TIME ZONE */
		case SQLT_DATE:		/* ANSI Date */
		case SQLT_DAT:		/* date in oracle format */
		case SQLT_ODT:		/* OCIDate type */
		case SQLT_TIMESTAMP:	/* TIMESTAMP */
		case SQLT_TIMESTAMP_TZ:	/* TIMESTAMP WITH TIME ZONE */
		case SQLT_TIMESTAMP_LTZ:/* TIMESTAMP WITH LOCAL TZ */
//		case SQLT_INTERVAL_YM:	/* INTERVAL YEAR TO MONTH */
//		case SQLT_INTERVAL_DS:	/* INTERVAL DAY TO SECOND */
			LM_DBG("use DB_DATETIME result type\n");
			RES_TYPES(_r)[i] = DB_DATETIME;
			len = sizeof(OCIDate);
			dtype = SQLT_ODT;
			break;

		case SQLT_CLOB:		/* character lob */
		case SQLT_BLOB:		/* binary lob */
//		case SQLT_BFILEE:	/* binary file lob */
//		case SQLT_CFILEE:	/* character file lob */
//		case SQLT_BIN:		/* binary data(DTYBIN) */
//		case SQLT_LBI:		/* long binary */
			LM_DBG("use DB_BLOB result type\n");
			RES_TYPES(_r)[i] = DB_BLOB;
			goto dyn_str;

		case SQLT_CHR:		/* (ORANET TYPE) character string */
		case SQLT_STR:		/* zero terminated string */
		case SQLT_VST:		/* OCIString type */
		case SQLT_VCS:		/* Variable character string */
		case SQLT_AFC:		/* Ansi fixed char */
		case SQLT_AVC:		/* Ansi Var char */
//		case SQLT_RID:		/* rowid */
			LM_DBG("use DB_STR result type\n");
			RES_TYPES(_r)[i] = DB_STR;
dyn_str:
			dtype = SQLT_CHR;
			len = 0; /* DATA_SIZE is ub2 */
			status = OCIAttrGet(param, OCI_DTYPE_PARAM,
				(dvoid**)(dvoid*)&len, NULL, OCI_ATTR_DATA_SIZE,
				con->errhp);
			if (status != OCI_SUCCESS) goto ora_err;
			if (len >= 4000) {
				LM_DBG("use DB_BLOB result type\n");
				RES_TYPES(_r)[i] = DB_BLOB;
			}
			++len;
			break;

		default:
			LM_ERR("unsupported datatype %d\n", dtype);
			goto stop_load;
		}
		_d->ilen[i] = (ub2)len;
		_d->pv[i].v = st_buf + tsz;
		tsz += len;
		status = OCIDefineByPos(_c, &_d->defh[i], con->errhp, i+1,
			_d->pv[i].v, len, dtype, &_d->ind[i],
			&_d->len[i], NULL, OCI_DEFAULT);
		if (status != OCI_SUCCESS) goto ora_err;
	}

#if STATIC_BUF_LEN < 65536
#error
#endif
	if (tsz > 65536) {
		LM_ERR("Row size exceed 65K. IOB's are not supported\n");
		goto stop_load;
	}
	return 0;

ora_err:
	LM_ERR("driver: %s\n", db_oracle_error(con, status));
stop_load:
	db_free_columns(_r);
	return -6;
}


/*
 * Convert data fron db format to internal format
 */
static int convert_row(db_res_t* _res, db_row_t* _r, dmap_t* _d)
{
	unsigned i, n = RES_COL_N(_res);

	ROW_N(_r) = n;

	for (i = 0; i < n; i++) {
		static const str dummy_string = {"", 0};

		db_val_t* v = &ROW_VALUES(_r)[i];
		db_type_t t = RES_TYPES(_res)[i];

		if (_d->ind[i] == -1) {
			/* Initialize the string pointers to a dummy empty
			 * string so that we do not crash when the NULL flag
			 * is set but the module does not check it properly
			 */
			VAL_STRING(v) = dummy_string.s;
			VAL_STR(v) = dummy_string;
			VAL_BLOB(v) = dummy_string;
			VAL_TYPE(v) = t;
			VAL_NULL(v) = 1;
			continue;
		}

		if (_d->ind[i])
			LM_WARN("truncated value in DB\n");

		VAL_TYPE(v) = t;
		switch (t) {
		case DB_INT:
			VAL_INT(v) = *_d->pv[i].i;
			break;

		case DB_BIGINT:
			VAL_BIGINT(v) = *_d->pv[i].i;
			break;

		case DB_BITMAP:
			VAL_BITMAP(v) = *_d->pv[i].i;
			break;

		case DB_DOUBLE:
			VAL_DOUBLE(v) = *_d->pv[i].f;
			break;

		case DB_DATETIME:
			{
				struct tm tm;
				memset(&tm, 0, sizeof(tm));
				OCIDateGetTime(_d->pv[i].o, &tm.tm_hour,
					&tm.tm_min, &tm.tm_sec);
				OCIDateGetDate(_d->pv[i].o, &tm.tm_year,
					&tm.tm_mon, &tm.tm_mday);
				if (tm.tm_mon)
					--tm.tm_mon;
				if (tm.tm_year >= 1900)
					tm.tm_year -= 1900;
				VAL_TIME(v) = mktime(&tm);
			}
			break;

		case DB_STR:
		case DB_BLOB:
		case DB_STRING:
			{
				size_t len = _d->len[i];
				char *pstr = pkg_malloc(len+1);

				if (pstr == NULL)
					return -1;

				memcpy(pstr, _d->pv[i].c, len);
				pstr[len] = '\0';
				VAL_FREE(v) = 1;
				if (t == DB_STR) {
					VAL_STR(v).s = pstr;
					VAL_STR(v).len = len;
				} else if (t == DB_BLOB) {
					VAL_BLOB(v).s = pstr;
					VAL_BLOB(v).len = len;
				} else {
					VAL_STRING(v) = pstr;
				}
			}
			break;

		default:
			LM_ERR("unknown type mapping (%u)\n", t);
			return -2;
		}
	}

	return 0;
}


/*
 * Get rows and convert it from oracle to db API representation
 */
static int get_rows(ora_con_t* con, db_res_t* _r, OCIStmt* _c, dmap_t* _d)
{
	ub4 rcnt;
	sword status;
	unsigned n = RES_COL_N(_r);

	memcpy(_d->len, _d->ilen, sizeof(_d->len[0]) * n);

	// timelimited operation
	status = begin_timelimit(con, 0);
	if (status != OCI_SUCCESS) goto ora_err;
	do status = OCIStmtFetch2(_c, con->errhp, 1, OCI_FETCH_NEXT, 0,
		OCI_DEFAULT);
	while (wait_timelimit(con, status));
	if (done_timelimit(con, status)) goto stop_load;
	if (status != OCI_SUCCESS) {
		if (status != OCI_NO_DATA)
			goto ora_err;

		RES_ROW_N(_r) = 0;
		RES_ROWS(_r) = NULL;
		return 0;
	}

	status = OCIAttrGet(_c, OCI_HTYPE_STMT, &rcnt, NULL,
		OCI_ATTR_CURRENT_POSITION, con->errhp);
	if (status != OCI_SUCCESS) goto ora_err;
	if (!rcnt) {
		LM_ERR("lastpos==0\n");
		goto stop_load;
	}

	RES_ROW_N(_r) = rcnt;
	if (db_allocate_rows( _r, rcnt)!=0) {
		LM_ERR("no private memory left\n");
		return -1;
	}

	while ( 1 ) {
		if (convert_row(_r, &RES_ROWS(_r)[--rcnt], _d) < 0) {
			LM_ERR("error convert row\n");
			goto stop_load;
		}

		if (!rcnt)
			return 0;

		memcpy(_d->len, _d->ilen, sizeof(_d->len[0]) * n);
		// timelimited operation
		status = begin_timelimit(con, 0);
		if (status != OCI_SUCCESS) goto ora_err;
		do status = OCIStmtFetch2(_c, con->errhp, 1, OCI_FETCH_PRIOR, 0,
			OCI_DEFAULT);
		while (wait_timelimit(con, status));
		if (done_timelimit(con, status)) goto stop_load;
		if (status != OCI_SUCCESS) break;
	}
ora_err:
	LM_ERR("driver: %s\n", db_oracle_error(con, status));
stop_load:
	db_free_rows(_r);
	RES_ROW_N(_r) = 0;	/* TODO: skipped in db_res.c :) */
	return -3;
}


/*
 * Read database answer and fill the structure
 */
int db_oracle_store_result(const db_con_t* _h, db_res_t** _r)
{
	dmap_t dmap;
	int rc;
	db_res_t* r;
	ora_con_t* con;
	OCIStmt* hs;

	if (!_h || !_r) {
badparam:
		LM_ERR("invalid parameter\n");
		return -1;
	}

	con = CON_ORA(_h);
	{
	    query_data_t *pcb = con->pqdata;


	    if (!pcb || !pcb->_rs)
		    goto badparam;

	    hs = *pcb->_rs;
	    pcb->_rs = NULL; /* paranoid for next call */
	}

	rc = -1;
	if (_r)	*_r = NULL;	/* unification for all errors */

	r = db_new_result();
	if (!r) {
		LM_ERR("no memory left\n");
		goto done;
	}

	if (get_columns(con, r, hs, &dmap) < 0) {
		LM_ERR("error while getting column names\n");
		goto done;
	}

	if (get_rows(con, r, hs, &dmap) < 0) {
		LM_ERR("error while converting rows\n");
		db_free_columns(r);
		goto done;
	}

	rc = 0;
	*_r = r;
done:
	OCIHandleFree(hs, OCI_HTYPE_STMT);
	return rc;
}
