/*-------
 * 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 "readme.txt" for copyright and license information.
 *-------
 */

#include "environ.h"
#include "misc.h"

#include "connection.h"
#include "dlg_specific.h"
#include "statement.h"
#include <stdlib.h>
#include <string.h>
#include "pgapifunc.h"
#ifdef	WIN32
#include <winsock2.h>
#endif /* WIN32 */
#include "loadlib.h"


/* The one instance of the handles */
static int conns_count = 0;
static ConnectionClass **conns = NULL;

#if defined(WIN_MULTITHREAD_SUPPORT)
CRITICAL_SECTION	conns_cs;
CRITICAL_SECTION	common_cs; /* commonly used for short term blocking */
CRITICAL_SECTION	common_lcs; /* commonly used for not necessarily short term blocking */
#elif defined(POSIX_MULTITHREAD_SUPPORT)
pthread_mutex_t     conns_cs;
pthread_mutex_t     common_cs;
pthread_mutex_t     common_lcs;
#endif /* WIN_MULTITHREAD_SUPPORT */

void	shortterm_common_lock(void)
{
	ENTER_COMMON_CS;
}
void	shortterm_common_unlock(void)
{
	LEAVE_COMMON_CS;
}

int	getConnCount(void)
{
	return conns_count;
}
ConnectionClass * const *getConnList(void)
{
	return conns;
}

RETCODE		SQL_API
PGAPI_AllocEnv(HENV * phenv)
{
	CSTR func = "PGAPI_AllocEnv";
	SQLRETURN	ret = SQL_SUCCESS;

	MYLOG(0, "entering\n");

	/*
	 * For systems on which none of the constructor-making
	 * techniques in psqlodbc.c work:
	 * It's ok to call initialize_global_cs() twice.
	 */
	{
		initialize_global_cs();
	}

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

	MYLOG(0, "leaving phenv=%p\n", *phenv);
	return ret;
}


RETCODE		SQL_API
PGAPI_FreeEnv(HENV henv)
{
	CSTR func = "PGAPI_FreeEnv";
	SQLRETURN	ret = SQL_SUCCESS;
	EnvironmentClass *env = (EnvironmentClass *) henv;

	MYLOG(0, "entering env=%p\n", env);

	if (env && EN_Destructor(env))
	{
		MYLOG(0, "   ok\n");
		goto cleanup;
	}

	ret = SQL_ERROR;
	EN_log_error(func, "Error freeing environment", NULL);
cleanup:
	return ret;
}

#define	SIZEOF_SQLSTATE	6

static void
pg_sqlstate_set(const EnvironmentClass *env, UCHAR *szSqlState, const char *ver3str, const char *ver2str)
{
	strncpy_null((char *) szSqlState, EN_is_odbc3(env) ? ver3str : ver2str, SIZEOF_SQLSTATE);
}

PG_ErrorInfo *
ER_Constructor(SDWORD errnumber, const char *msg)
{
	PG_ErrorInfo	*error;
	ssize_t		aladd, errsize;

	if (DESC_OK == errnumber)
		return NULL;
	if (msg)
	{
		errsize = strlen(msg);
		aladd = errsize - sizeof(error->__error_message) + 1;
		if (aladd < 0)
			aladd = 0;
	}
	else
	{
		errsize = -1;
		aladd = 0;
	}
	error = (PG_ErrorInfo *) malloc(sizeof(PG_ErrorInfo) + aladd);
	if (error)
	{
		memset(error, 0, sizeof(PG_ErrorInfo));
		error->status = errnumber;
		error->errorsize = (Int2) errsize;
		if (errsize > 0)
			memcpy(error->__error_message, msg, errsize);
		error->__error_message[errsize] = '\0';
		error->recsize = -1;
	}
	return error;
}

void
ER_Destructor(PG_ErrorInfo *self)
{
	free(self);
}

PG_ErrorInfo *
ER_Dup(const PG_ErrorInfo *self)
{
	PG_ErrorInfo	*new;
	Int4		alsize;

	if (!self)
		return NULL;
	alsize = sizeof(PG_ErrorInfo);
	if (self->errorsize  > 0)
		alsize += self->errorsize;
	new = (PG_ErrorInfo *) malloc(alsize);
	if (new)
		memcpy(new, self, alsize);

	return new;
}

#define	DRVMNGRDIV	511
/*		Returns the next SQL error information. */
RETCODE		SQL_API
ER_ReturnError(PG_ErrorInfo *pgerror,
			   SQLSMALLINT	RecNumber,
			   SQLCHAR * szSqlState,
			   SQLINTEGER * pfNativeError,
			   SQLCHAR * szErrorMsg,
			   SQLSMALLINT cbErrorMsgMax,
			   SQLSMALLINT * pcbErrorMsg,
			   UWORD flag)
{
	/* CC: return an error of a hstmt  */
	PG_ErrorInfo	*error;
	BOOL		partial_ok = ((flag & PODBC_ALLOW_PARTIAL_EXTRACT) != 0);
	const char	*msg;
	SWORD		msglen, stapos, wrtlen, pcblen;

	if (!pgerror)
		return SQL_NO_DATA_FOUND;
	error = pgerror;
	msg = error->__error_message;
	MYLOG(0, "entering status = %d, msg = #%s#\n", error->status, msg);
	msglen = (SQLSMALLINT) 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 (error->recsize < 0)
	{
		if (cbErrorMsgMax > 0)
			error->recsize = cbErrorMsgMax - 1; /* apply the first request */
		else
			error->recsize = DRVMNGRDIV;
	}
	else if (1 == RecNumber && cbErrorMsgMax > 0)
		error->recsize = cbErrorMsgMax - 1;
	if (RecNumber < 0)
	{
		if (0 == error->errorpos)
			RecNumber = 1;
		else
			RecNumber = 2 + (error->errorpos - 1) / error->recsize;
	}
	stapos = (RecNumber - 1) * error->recsize;
	if (stapos > msglen)
		return SQL_NO_DATA_FOUND;
	pcblen = wrtlen = msglen - stapos;
	if (pcblen > error->recsize)
		pcblen = error->recsize;
	if (0 == cbErrorMsgMax)
		wrtlen = 0;
	else if (wrtlen >= cbErrorMsgMax)
	{
		if (partial_ok)
			wrtlen = cbErrorMsgMax - 1;
		else if (cbErrorMsgMax <= error->recsize)
			wrtlen = cbErrorMsgMax - 1;
		else
			wrtlen = 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 = error->status;

	if (NULL != szSqlState)
		strncpy_null((char *) szSqlState, error->sqlstate, 6);

	MYLOG(0, "	     szSqlState = '%s',len=%d, szError='%s'\n", szSqlState, pcblen, szErrorMsg);
	if (wrtlen < pcblen)
		return SQL_SUCCESS_WITH_INFO;
	else
		return SQL_SUCCESS;
}


RETCODE		SQL_API
PGAPI_ConnectError(HDBC hdbc,
				   SQLSMALLINT	RecNumber,
				   SQLCHAR * szSqlState,
				   SQLINTEGER * pfNativeError,
				   SQLCHAR * szErrorMsg,
				   SQLSMALLINT cbErrorMsgMax,
				   SQLSMALLINT * pcbErrorMsg,
				   UWORD flag)
{
	ConnectionClass *conn = (ConnectionClass *) hdbc;
	EnvironmentClass *env = (EnvironmentClass *) conn->henv;
	char		*msg;
	int		status;
	BOOL	once_again = FALSE;
	ssize_t		msglen;

	MYLOG(0, "entering hdbc=%p <%d>\n", hdbc, cbErrorMsgMax);
	if (RecNumber != 1 && RecNumber != -1)
		return SQL_NO_DATA_FOUND;
	if (cbErrorMsgMax < 0)
		return SQL_ERROR;
	if (CONN_EXECUTING == conn->status || !CC_get_error(conn, &status, &msg) || NULL == msg)
	{
		MYLOG(0, "CC_Get_error returned nothing.\n");
		if (NULL != szSqlState)
			strncpy_null((char *) szSqlState, "00000", SIZEOF_SQLSTATE);
		if (NULL != pcbErrorMsg)
			*pcbErrorMsg = 0;
		if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
			szErrorMsg[0] = '\0';

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

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

	if (NULL != szSqlState)
	{
		if (conn->sqlstate[0])
			strncpy_null((char *) szSqlState, conn->sqlstate, SIZEOF_SQLSTATE);
		else
		switch (status)
		{
			case CONN_OPTION_VALUE_CHANGED:
				pg_sqlstate_set(env, szSqlState, "01S02", "01S02");
				break;
			case CONN_TRUNCATED:
				pg_sqlstate_set(env, szSqlState, "01004", "01004");
				/* data truncated */
				break;
			case CONN_INIREAD_ERROR:
				pg_sqlstate_set(env, szSqlState, "IM002", "IM002");
				/* data source not found */
				break;
			case CONNECTION_SERVER_NOT_REACHED:
			case CONN_OPENDB_ERROR:
				pg_sqlstate_set(env, szSqlState, "08001", "08001");
				/* unable to connect to data source */
				break;
			case CONN_INVALID_AUTHENTICATION:
			case CONN_AUTH_TYPE_UNSUPPORTED:
				pg_sqlstate_set(env, szSqlState, "28000", "28000");
				break;
			case CONN_STMT_ALLOC_ERROR:
				pg_sqlstate_set(env, szSqlState, "HY001", "S1001");
				/* memory allocation failure */
				break;
			case CONN_IN_USE:
				pg_sqlstate_set(env, szSqlState, "HY000", "S1000");
				/* general error */
				break;
			case CONN_UNSUPPORTED_OPTION:
				pg_sqlstate_set(env, szSqlState, "HYC00", "IM001");
				/* driver does not support this function */
				break;
			case CONN_INVALID_ARGUMENT_NO:
				pg_sqlstate_set(env, szSqlState, "HY009", "S1009");
				/* invalid argument value */
				break;
			case CONN_TRANSACT_IN_PROGRES:
				pg_sqlstate_set(env, szSqlState, "HY011", "S1011");
				break;
			case CONN_NO_MEMORY_ERROR:
				pg_sqlstate_set(env, szSqlState, "HY001", "S1001");
				break;
			case CONN_NOT_IMPLEMENTED_ERROR:
				pg_sqlstate_set(env, szSqlState, "HYC00", "S1C00");
				break;
			case CONN_ILLEGAL_TRANSACT_STATE:
				pg_sqlstate_set(env, szSqlState, "25000", "S1010");
				break;
			case CONN_VALUE_OUT_OF_RANGE:
				pg_sqlstate_set(env, szSqlState, "HY019", "22003");
				break;
			case CONNECTION_COULD_NOT_SEND:
			case CONNECTION_COULD_NOT_RECEIVE:
			case CONNECTION_COMMUNICATION_ERROR:
			case CONNECTION_NO_RESPONSE:
				pg_sqlstate_set(env, szSqlState, "08S01", "08S01");
				break;
			default:
				pg_sqlstate_set(env, szSqlState, "HY000", "S1000");
				/* general error */
				break;
		}
	}

	MYLOG(0, "	     szSqlState = '%s',len=" FORMAT_SSIZE_T ", szError='%s'\n", szSqlState ? (char *) szSqlState : PRINT_NULL, msglen, szErrorMsg ? (char *) szErrorMsg : PRINT_NULL);
	if (once_again)
	{
		CC_set_errornumber(conn, status);
		return SQL_SUCCESS_WITH_INFO;
	}
	else
		return SQL_SUCCESS;
}

RETCODE		SQL_API
PGAPI_EnvError(HENV henv,
			   SQLSMALLINT	RecNumber,
			   SQLCHAR * szSqlState,
			   SQLINTEGER * pfNativeError,
			   SQLCHAR * szErrorMsg,
			   SQLSMALLINT cbErrorMsgMax,
			   SQLSMALLINT * pcbErrorMsg,
			   UWORD flag)
{
	EnvironmentClass *env = (EnvironmentClass *) henv;
	char		*msg = NULL;
	int		status;

	MYLOG(0, "entering henv=%p <%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(0, "EN_get_error: msg = #%s#\n", msg);

		if (NULL != szSqlState)
			pg_sqlstate_set(env, szSqlState, "00000", "00000");
		if (NULL != pcbErrorMsg)
			*pcbErrorMsg = 0;
		if ((NULL != szErrorMsg) && (cbErrorMsgMax > 0))
			szErrorMsg[0] = '\0';

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

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

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

	return SQL_SUCCESS;
}


/*
 * EnvironmentClass implementation
 */
EnvironmentClass *
EN_Constructor(void)
{
	EnvironmentClass *rv = NULL;
#ifdef WIN32
	WORD		wVersionRequested;
	WSADATA		wsaData;
	const int	major = 2, minor = 2;

	/* Load the WinSock Library */
	wVersionRequested = MAKEWORD(major, minor);

	if (WSAStartup(wVersionRequested, &wsaData))
	{
		MYLOG(0, " WSAStartup error\n");
		return rv;
	}
	/* Verify that this is the minimum version of WinSock */
	if (LOBYTE(wsaData.wVersion) >= 1 &&
	    (LOBYTE(wsaData.wVersion) >= 2 ||
	     HIBYTE(wsaData.wVersion) >= 1))
		;
	else
	{
		MYLOG(0, " WSAStartup version=(%d,%d)\n",
			LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion));
		goto cleanup;
	}
#endif /* WIN32 */

	rv = (EnvironmentClass *) malloc(sizeof(EnvironmentClass));
	if (NULL == rv)
	{
		MYLOG(0, " malloc error\n");
		goto cleanup;
	}
	rv->errormsg = 0;
	rv->errornumber = 0;
	rv->flag = 0;
	INIT_ENV_CS(rv);
cleanup:
#ifdef WIN32
	if (NULL == rv)
	{
		WSACleanup();
	}
#endif /* WIN32 */

	return rv;
}


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

	MYLOG(0, "entering self=%p\n", self);
	if (!self)
		return 0;

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

	/* Free any connections belonging to this environment */
	ENTER_CONNS_CS;
	for (lf = 0, nullcnt = 0; lf < conns_count; lf++)
	{
		if (NULL == conns[lf])
			nullcnt++;
		else if (conns[lf]->henv == self)
		{
			if (CC_Destructor(conns[lf]))
				conns[lf] = NULL;
			else
				rv = 0;
			nullcnt++;
		}
	}
	if (conns && nullcnt >= conns_count)
	{
		MYLOG(0, "clearing conns count=%d\n", conns_count);
		free(conns);
		conns = NULL;
		conns_count = 0;
	}
	LEAVE_CONNS_CS;
	DELETE_ENV_CS(self);
	free(self);

#ifdef WIN32
	WSACleanup();
#endif
	MYLOG(0, "leaving 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;
}

#define	INIT_CONN_COUNT	128

char
EN_add_connection(EnvironmentClass *self, ConnectionClass *conn)
{
	int	i, alloc;
	ConnectionClass	**newa;
	char	ret = FALSE;

	MYLOG(0, "entering self = %p, conn = %p\n", self, conn);

	ENTER_CONNS_CS;
	for (i = 0; i < conns_count; i++)
	{
		if (!conns[i])
		{
			conn->henv = self;
			conns[i] = conn;
			ret = TRUE;
			MYLOG(0, "       added at i=%d, conn->henv = %p, conns[i]->henv = %p\n", i, conn->henv, conns[i]->henv);
			goto cleanup;
		}
	}
	if (conns_count > 0)
		alloc = 2 * conns_count;
	else
		alloc = INIT_CONN_COUNT;
	if (newa = (ConnectionClass **) realloc(conns, alloc * sizeof(ConnectionClass *)), NULL == newa)
		goto cleanup;
	conn->henv = self;
	newa[conns_count] = conn;
	conns = newa;
	ret = TRUE;
	MYLOG(0, "       added at %d, conn->henv = %p, conns[%d]->henv = %p\n", conns_count, conn->henv, conns_count, conns[conns_count]->henv);
	for (i = conns_count + 1; i < alloc; i++)
		conns[i] = NULL;
	conns_count = alloc;
cleanup:
	LEAVE_CONNS_CS;
	return ret;
}


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

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

	return FALSE;
}


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