/*-------
 * Module:			environ.c
 *
 * Description:		This module contains routines related to
 *					the environment, such as storing connection handles,
 *					and returning errors.
 *
 * Classes:			EnvironmentClass (Functions prefix: "EN_")
 *
 * API functions:	SQLAllocEnv, SQLFreeEnv, SQLError
 *
 * Comments:		See "notice.txt" for copyright and license information.
 *-------
 */

#include "environ.h"

#include "connection.h"
#include "dlg_specific.h"
#include "statement.h"
#include <stdlib.h>
#include <string.h>
#include "pgapifunc.h"

extern GLOBAL_VALUES globals;

/* The one instance of the handles */
ConnectionClass *conns[MAX_CONNECTIONS];


RETCODE		SQL_API
PGAPI_AllocEnv(HENV FAR * phenv)
{
	static char *func = "PGAPI_AllocEnv";

	mylog("**** in PGAPI_AllocEnv ** \n");

	/*
	 * Hack for systems on which none of the constructor-making techniques
	 * in psqlodbc.c work: if globals appears not to have been
	 * initialized, then cause it to be initialized.  Since this should be
	 * the first function called in this shared library, doing it here
	 * should work.
	 */
	if (globals.socket_buffersize <= 0)
		getCommonDefaults(DBMS_NAME, ODBCINST_INI, NULL);

	*phenv = (HENV) EN_Constructor();
	if (!*phenv)
	{
		*phenv = SQL_NULL_HENV;
		EN_log_error(func, "Error allocating environment", NULL);
		return SQL_ERROR;
	}

	mylog("** exit PGAPI_AllocEnv: phenv = %u **\n", *phenv);
	return SQL_SUCCESS;
}


RETCODE		SQL_API
PGAPI_FreeEnv(HENV henv)
{
	static char *func = "PGAPI_FreeEnv";
	EnvironmentClass *env = (EnvironmentClass *) henv;

	mylog("**** in PGAPI_FreeEnv: env = %u ** \n", env);

	if (env && EN_Destructor(env))
	{
		mylog("   ok\n");
		return SQL_SUCCESS;
	}

	mylog("    error\n");
	EN_log_error(func, "Error freeing environment", env);
	return SQL_ERROR;
}


#define	DRVMNGRDIV	511
/*		Returns the next SQL error information. */
RETCODE		SQL_API
PGAPI_StmtError(	HSTMT hstmt,
			SWORD	RecNumber,
			UCHAR FAR * szSqlState,
			SDWORD FAR * pfNativeError,
			UCHAR FAR * szErrorMsg,
			SWORD cbErrorMsgMax,
			SWORD FAR * pcbErrorMsg,
			UWORD flag)
{
	/* CC: return an error of a hstmt  */
	StatementClass *stmt = (StatementClass *) hstmt;
	char		*msg;
	int		status;
	BOOL		partial_ok = ((flag & PODBC_ALLOW_PARTIAL_EXTRACT) != 0),
			clear_str = ((flag & PODBC_ERROR_CLEAR) != 0);
	SWORD		msglen, stapos, wrtlen, pcblen;

	mylog("**** PGAPI_StmtError: hstmt=%u <%d>\n", hstmt, cbErrorMsgMax);

	if (cbErrorMsgMax < 0)
		return SQL_ERROR;

	if (!SC_get_error(stmt, &status, &msg) || NULL == msg || !msg[0])
	{
		mylog("SC_Get_error returned nothing.\n");
		if (NULL != szSqlState)
			strcpy(szSqlState, "00000");
		if (NULL != pcbErrorMsg)
			*pcbErrorMsg = 0;
		if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
			szErrorMsg[0] = '\0';

		return SQL_NO_DATA_FOUND;
	}
	mylog("SC_get_error: status = %d, msg = #%s#\n", status, msg);
	msglen = (SWORD) strlen(msg);
	/*
	 *	Even though an application specifies a larger error message
	 *	buffer, the driver manager changes it silently.
	 *	Therefore we divide the error message into ... 
	 */
	if (stmt->error_recsize < 0)
	{
		if (cbErrorMsgMax > 0)
			stmt->error_recsize = cbErrorMsgMax - 1; /* apply the first request */
		else
			stmt->error_recsize = DRVMNGRDIV;
	}
	if (RecNumber < 0)
	{
		if (0 == stmt->errorpos)
			RecNumber = 1;
		else
			RecNumber = 2 + (stmt->errorpos - 1) / stmt->error_recsize;
	}
	stapos = (RecNumber - 1) * stmt->error_recsize;
	if (stapos > msglen)
		return SQL_NO_DATA_FOUND; 
	pcblen = wrtlen = msglen - stapos;
	if (pcblen > stmt->error_recsize)
		pcblen = stmt->error_recsize;
	if (0 == cbErrorMsgMax)
		wrtlen = 0; 
	else if (wrtlen >= cbErrorMsgMax)
	{
		if (partial_ok)
			wrtlen = cbErrorMsgMax - 1;
		else if (cbErrorMsgMax <= stmt->error_recsize)
			wrtlen = 0;
		else 
			wrtlen = stmt->error_recsize;
	}
	if (wrtlen > pcblen)
		wrtlen = pcblen;
	if (NULL != pcbErrorMsg)
		*pcbErrorMsg = pcblen;

	if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
	{
		memcpy(szErrorMsg, msg + stapos, wrtlen);
		szErrorMsg[wrtlen] = '\0';
	}

	if (NULL != pfNativeError)
		*pfNativeError = status;

	if (NULL != szSqlState)

		switch (status)
		{
				/* now determine the SQLSTATE to be returned */
			case STMT_ROW_VERSION_CHANGED:
				strcpy(szSqlState, "01001");
				/* data truncated */
				break;
			case STMT_TRUNCATED:
				strcpy(szSqlState, "01004");
				/* data truncated */
				break;
			case STMT_INFO_ONLY:
				strcpy(szSqlState, "00000");
				/* just information that is returned, no error */
				break;
			case STMT_BAD_ERROR:
				strcpy(szSqlState, "08S01");
				/* communication link failure */
				break;
			case STMT_CREATE_TABLE_ERROR:
				strcpy(szSqlState, "S0001");
				/* table already exists */
				break;
			case STMT_STATUS_ERROR:
			case STMT_SEQUENCE_ERROR:
				strcpy(szSqlState, "S1010");
				/* Function sequence error */
				break;
			case STMT_NO_MEMORY_ERROR:
				strcpy(szSqlState, "S1001");
				/* memory allocation failure */
				break;
			case STMT_COLNUM_ERROR:
				strcpy(szSqlState, "S1002");
				/* invalid column number */
				break;
			case STMT_NO_STMTSTRING:
				strcpy(szSqlState, "S1001");
				/* having no stmtstring is also a malloc problem */
				break;
			case STMT_ERROR_TAKEN_FROM_BACKEND:
				strcpy(szSqlState, "S1000");
				/* general error */
				break;
			case STMT_INTERNAL_ERROR:
				strcpy(szSqlState, "S1000");
				/* general error */
				break;
			case STMT_ROW_OUT_OF_RANGE:
				strcpy(szSqlState, "S1107");
				break;

			case STMT_OPERATION_CANCELLED:
				strcpy(szSqlState, "S1008");
				break;

			case STMT_NOT_IMPLEMENTED_ERROR:
				strcpy(szSqlState, "S1C00");	/* == 'driver not
													 * capable' */
				break;
			case STMT_OPTION_OUT_OF_RANGE_ERROR:
				strcpy(szSqlState, "S1092");
				break;
			case STMT_BAD_PARAMETER_NUMBER_ERROR:
				strcpy(szSqlState, "S1093");
				break;
			case STMT_INVALID_COLUMN_NUMBER_ERROR:
				strcpy(szSqlState, "S1002");
				break;
			case STMT_RESTRICTED_DATA_TYPE_ERROR:
				strcpy(szSqlState, "07006");
				break;
			case STMT_INVALID_CURSOR_STATE_ERROR:
				strcpy(szSqlState, "24000");
				break;
			case STMT_ERROR_IN_ROW:
				strcpy(szSqlState, "01S01");
				break;
			case STMT_OPTION_VALUE_CHANGED:
				strcpy(szSqlState, "01S02");
				break;
			case STMT_POS_BEFORE_RECORDSET:
				strcpy(szSqlState, "01S06");
				break;
			case STMT_INVALID_CURSOR_NAME:
				strcpy(szSqlState, "34000");
				break;
			case STMT_NO_CURSOR_NAME:
				strcpy(szSqlState, "S1015");
				break;
			case STMT_INVALID_ARGUMENT_NO:
				strcpy(szSqlState, "S1009");
				/* invalid argument value */
				break;
			case STMT_INVALID_CURSOR_POSITION:
				strcpy(szSqlState, "S1109");
				break;
			case STMT_RETURN_NULL_WITHOUT_INDICATOR:
				strcpy(szSqlState, "22002");
				break;
			case STMT_VALUE_OUT_OF_RANGE:
				strcpy(szSqlState, "22003");
				break;
			case STMT_OPERATION_INVALID:
				strcpy(szSqlState, "S1011");
				break;
			case STMT_INVALID_DESCRIPTOR_IDENTIFIER:
				strcpy(szSqlState, "HY091");
				break;
			case STMT_INVALID_OPTION_IDENTIFIER:
				strcpy(szSqlState, "HY092");
				break;
			case STMT_OPTION_NOT_FOR_THE_DRIVER:
				strcpy(szSqlState, "HYC00");
				break;
			case STMT_EXEC_ERROR:
			default:
				strcpy(szSqlState, "S1000");
				/* also a general error */
				break;
		}
	mylog("	     szSqlState = '%s',len=%d, szError='%s'\n", szSqlState, pcblen, szErrorMsg);
	if (clear_str)
	{
		stmt->errorpos = stapos + wrtlen;
		if (stmt->errorpos >= msglen)
			SC_clear_error(stmt);
	}
	if (wrtlen == 0)
		return SQL_SUCCESS_WITH_INFO;
	else
		return SQL_SUCCESS;
}

RETCODE		SQL_API
PGAPI_ConnectError(	HDBC hdbc,
			SWORD	RecNumber,
			UCHAR FAR * szSqlState,
			SDWORD FAR * pfNativeError,
			UCHAR FAR * szErrorMsg,
			SWORD cbErrorMsgMax,
			SWORD FAR * pcbErrorMsg,
			UWORD flag)
{
	ConnectionClass *conn = (ConnectionClass *) hdbc;
	char		*msg;
	int		status;
	BOOL	once_again = FALSE;
	SWORD		msglen;

	mylog("**** PGAPI_ConnectError: hdbc=%u <%d>\n", hdbc, cbErrorMsgMax);
	if (RecNumber != 1 && RecNumber != -1)
		return SQL_NO_DATA_FOUND;
	if (cbErrorMsgMax < 0)
		return SQL_ERROR;
	if (!CC_get_error(conn, &status, &msg) || NULL == msg)
	{
		mylog("CC_Get_error returned nothing.\n");
		if (NULL != szSqlState)
			strcpy(szSqlState, "00000");
		if (NULL != pcbErrorMsg)
			*pcbErrorMsg = 0;
		if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
			szErrorMsg[0] = '\0';

		return SQL_NO_DATA_FOUND;
	}
	mylog("CC_get_error: status = %d, msg = #%s#\n", status, msg);

	msglen = strlen(msg);
	if (NULL != pcbErrorMsg)
	{
		*pcbErrorMsg = msglen;
		if (cbErrorMsgMax == 0)
			once_again = TRUE;
		else if (msglen >= cbErrorMsgMax)
			*pcbErrorMsg = cbErrorMsgMax - 1;
	}
	if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
		strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
	if (NULL != pfNativeError)
		*pfNativeError = status;

	if (NULL != szSqlState)
		switch (status)
		{
			case STMT_OPTION_VALUE_CHANGED:
			case CONN_OPTION_VALUE_CHANGED:
				strcpy(szSqlState, "01S02");
				break;
			case STMT_TRUNCATED:
			case CONN_TRUNCATED:
				strcpy(szSqlState, "01004");
				/* data truncated */
				break;
			case CONN_INIREAD_ERROR:
				strcpy(szSqlState, "IM002");
				/* data source not found */
				break;
			case CONNECTION_SERVER_NOT_REACHED:
			case CONN_OPENDB_ERROR:
				strcpy(szSqlState, "08001");
				/* unable to connect to data source */
				break;
			case CONN_INVALID_AUTHENTICATION:
			case CONN_AUTH_TYPE_UNSUPPORTED:
				strcpy(szSqlState, "28000");
				break;
			case CONN_STMT_ALLOC_ERROR:
				strcpy(szSqlState, "S1001");
				/* memory allocation failure */
				break;
			case CONN_IN_USE:
				strcpy(szSqlState, "S1000");
				/* general error */
				break;
			case CONN_UNSUPPORTED_OPTION:
				strcpy(szSqlState, "IM001");
				/* driver does not support this function */
			case CONN_INVALID_ARGUMENT_NO:
				strcpy(szSqlState, "S1009");
				/* invalid argument value */
				break;
			case CONN_TRANSACT_IN_PROGRES:
				strcpy(szSqlState, "S1010");

				/*
				 * when the user tries to switch commit mode in a
				 * transaction
				 */
				/* -> function sequence error */
				break;
			case CONN_NO_MEMORY_ERROR:
				strcpy(szSqlState, "S1001");
				break;
			case CONN_NOT_IMPLEMENTED_ERROR:
			case STMT_NOT_IMPLEMENTED_ERROR:
				strcpy(szSqlState, "S1C00");
				break;
			case STMT_RETURN_NULL_WITHOUT_INDICATOR:
				strcpy(szSqlState, "22002");
				break;
			case CONN_VALUE_OUT_OF_RANGE:
			case STMT_VALUE_OUT_OF_RANGE:
				strcpy(szSqlState, "22003");
				break;
			default:
				strcpy(szSqlState, "S1000");
				/* general error */
				break;
		}

	mylog("	     szSqlState = '%s',len=%d, szError='%s'\n", szSqlState, msglen, szErrorMsg);
	if (once_again)
	{
		conn->errornumber = status;
		return SQL_SUCCESS_WITH_INFO;
	}
	else
		return SQL_SUCCESS;
}

RETCODE		SQL_API
PGAPI_EnvError(		HENV henv,
			SWORD	RecNumber,
			UCHAR FAR * szSqlState,
			SDWORD FAR * pfNativeError,
			UCHAR FAR * szErrorMsg,
			SWORD cbErrorMsgMax,
			SWORD FAR * pcbErrorMsg,
			UWORD flag)
{
	EnvironmentClass *env = (EnvironmentClass *) henv;
	char		*msg;
	int		status;

	mylog("**** PGAPI_EnvError: henv=%u <%d>\n", henv, cbErrorMsgMax);
	if (RecNumber != 1 && RecNumber != -1)
		return SQL_NO_DATA_FOUND;
	if (cbErrorMsgMax < 0)
		return SQL_ERROR;
	if (!EN_get_error(env, &status, &msg) || NULL == msg)
	{
			mylog("EN_get_error: status = %d, msg = #%s#\n", status, msg);
		
		if (NULL != szSqlState)
			strcpy(szSqlState, "00000");
		if (NULL != pcbErrorMsg)
			*pcbErrorMsg = 0;
		if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
			szErrorMsg[0] = '\0';

		return SQL_NO_DATA_FOUND;
	}
	mylog("EN_get_error: status = %d, msg = #%s#\n", status, msg);

	if (NULL != pcbErrorMsg)
		*pcbErrorMsg = (SWORD) strlen(msg);
	if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
		strncpy_null(szErrorMsg, msg, cbErrorMsgMax);
	if (NULL != pfNativeError)
		*pfNativeError = status;

	if (szSqlState)
	{
		switch (status)
		{
			case ENV_ALLOC_ERROR:
				/* memory allocation failure */
				strcpy(szSqlState, "S1001");
				break;
			default:
				strcpy(szSqlState, "S1000");
				/* general error */
				break;
		}
	}

	return SQL_SUCCESS;
}


/*		Returns the next SQL error information. */
RETCODE		SQL_API
PGAPI_Error(
			HENV henv,
			HDBC hdbc,
			HSTMT hstmt,
			UCHAR FAR * szSqlState,
			SDWORD FAR * pfNativeError,
			UCHAR FAR * szErrorMsg,
			SWORD cbErrorMsgMax,
			SWORD FAR * pcbErrorMsg)
{
	RETCODE	ret;
	UWORD	flag = PODBC_ALLOW_PARTIAL_EXTRACT | PODBC_ERROR_CLEAR;

	mylog("**** PGAPI_Error: henv=%u, hdbc=%u hstmt=%d\n", henv, hdbc, hstmt);

	if (cbErrorMsgMax < 0)
		return SQL_ERROR;
	if (SQL_NULL_HSTMT != hstmt)
		ret = PGAPI_StmtError(hstmt, -1, szSqlState, pfNativeError,
			 szErrorMsg, cbErrorMsgMax, pcbErrorMsg, flag);
	else if (SQL_NULL_HDBC != hdbc)
		ret = PGAPI_ConnectError(hdbc, -1, szSqlState, pfNativeError,
			 szErrorMsg, cbErrorMsgMax, pcbErrorMsg, flag);
	else if (SQL_NULL_HENV != hdbc)
		ret = PGAPI_EnvError(henv, -1, szSqlState, pfNativeError,
			 szErrorMsg, cbErrorMsgMax, pcbErrorMsg, flag);
	else
	{
		if (NULL != szSqlState)
			strcpy(szSqlState, "00000");
		if (NULL != pcbErrorMsg)
			*pcbErrorMsg = 0;
		if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
			szErrorMsg[0] = '\0';

		ret = SQL_NO_DATA_FOUND;
	}
	mylog("**** PGAPI_Error exit code=%d\n", ret);
	return ret;
}

/*
 * EnvironmentClass implementation
 */
EnvironmentClass *
EN_Constructor(void)
{
	EnvironmentClass *rv;

	rv = (EnvironmentClass *) malloc(sizeof(EnvironmentClass));
	if (rv)
	{
		rv->errormsg = 0;
		rv->errornumber = 0;
		rv->flag = 0;
	}

	return rv;
}


char
EN_Destructor(EnvironmentClass *self)
{
	int			lf;
	char		rv = 1;

	mylog("in EN_Destructor, self=%u\n", self);

	/*
	 * the error messages are static strings distributed throughout the
	 * source--they should not be freed
	 */

	/* Free any connections belonging to this environment */
	for (lf = 0; lf < MAX_CONNECTIONS; lf++)
	{
		if (conns[lf] && conns[lf]->henv == self)
			rv = rv && CC_Destructor(conns[lf]);
	}
	free(self);

	mylog("exit EN_Destructor: rv = %d\n", rv);
#ifdef	_MEMORY_DEBUG_
	debug_memory_check();
#endif   /* _MEMORY_DEBUG_ */
	return rv;
}


char
EN_get_error(EnvironmentClass *self, int *number, char **message)
{
	if (self && self->errormsg && self->errornumber)
	{
		*message = self->errormsg;
		*number = self->errornumber;
		self->errormsg = 0;
		self->errornumber = 0;
		return 1;
	}
	else
		return 0;
}


char
EN_add_connection(EnvironmentClass *self, ConnectionClass *conn)
{
	int			i;

	mylog("EN_add_connection: self = %u, conn = %u\n", self, conn);

	for (i = 0; i < MAX_CONNECTIONS; i++)
	{
		if (!conns[i])
		{
			conn->henv = self;
			conns[i] = conn;

			mylog("       added at i =%d, conn->henv = %u, conns[i]->henv = %u\n", i, conn->henv, conns[i]->henv);

			return TRUE;
		}
	}

	return FALSE;
}


char
EN_remove_connection(EnvironmentClass *self, ConnectionClass *conn)
{
	int			i;

	for (i = 0; i < MAX_CONNECTIONS; i++)
		if (conns[i] == conn && conns[i]->status != CONN_EXECUTING)
		{
			conns[i] = NULL;
			return TRUE;
		}

	return FALSE;
}


void
EN_log_error(char *func, char *desc, EnvironmentClass *self)
{
	if (self)
		qlog("ENVIRON ERROR: func=%s, desc='%s', errnum=%d, errmsg='%s'\n", func, desc, self->errornumber, self->errormsg);
	else
		qlog("INVALID ENVIRON HANDLE ERROR: func=%s, desc='%s'\n", func, desc);
}
