/* src/interfaces/ecpg/ecpglib/data.c */

#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"

#include <float.h>
#include <math.h>

#include "ecpgtype.h"
#include "ecpglib.h"
#include "ecpgerrno.h"
#include "extern.h"
#include "sqlca.h"
#include "pgtypes_numeric.h"
#include "pgtypes_date.h"
#include "pgtypes_timestamp.h"
#include "pgtypes_interval.h"

/* returns true if character c is a delimiter for the given array type */
static bool
array_delimiter(enum ARRAY_TYPE isarray, char c)
{
	if (isarray == ECPG_ARRAY_ARRAY && c == ',')
		return true;

	if (isarray == ECPG_ARRAY_VECTOR && c == ' ')
		return true;

	return false;
}

/* returns true if character c marks the boundary for the given array type */
static bool
array_boundary(enum ARRAY_TYPE isarray, char c)
{
	if (isarray == ECPG_ARRAY_ARRAY && c == '}')
		return true;

	if (isarray == ECPG_ARRAY_VECTOR && c == '\0')
		return true;

	return false;
}

/* returns true if some garbage is found at the end of the scanned string */
static bool
garbage_left(enum ARRAY_TYPE isarray, char **scan_length, enum COMPAT_MODE compat)
{
	/*
	 * INFORMIX allows for selecting a numeric into an int, the result is
	 * truncated
	 */
	if (isarray == ECPG_ARRAY_NONE)
	{
		if (INFORMIX_MODE(compat) && **scan_length == '.')
		{
			/* skip invalid characters */
			do
			{
				(*scan_length)++;
			} while (isdigit((unsigned char) **scan_length));
		}

		if (**scan_length != ' ' && **scan_length != '\0')
			return true;
	}
	else if (ECPG_IS_ARRAY(isarray) && !array_delimiter(isarray, **scan_length) && !array_boundary(isarray, **scan_length))
		return true;

	return false;
}

/* stolen code from src/backend/utils/adt/float.c */
#if defined(WIN32) && !defined(NAN)
static const uint32 nan[2] = {0xffffffff, 0x7fffffff};

#define NAN (*(const double *) nan)
#endif

static double
get_float8_infinity(void)
{
#ifdef INFINITY
	return (double) INFINITY;
#else
	return (double) (HUGE_VAL * HUGE_VAL);
#endif
}

static double
get_float8_nan(void)
{
	/* (double) NAN doesn't work on some NetBSD/MIPS releases */
#if defined(NAN) && !(defined(__NetBSD__) && defined(__mips__))
	return (double) NAN;
#else
	return (double) (0.0 / 0.0);
#endif
}

static bool
check_special_value(char *ptr, double *retval, char **endptr)
{
	if (pg_strncasecmp(ptr, "NaN", 3) == 0)
	{
		*retval = get_float8_nan();
		*endptr = ptr + 3;
		return true;
	}
	else if (pg_strncasecmp(ptr, "Infinity", 8) == 0)
	{
		*retval = get_float8_infinity();
		*endptr = ptr + 8;
		return true;
	}
	else if (pg_strncasecmp(ptr, "-Infinity", 9) == 0)
	{
		*retval = -get_float8_infinity();
		*endptr = ptr + 9;
		return true;
	}

	return false;
}

bool
ecpg_get_data(const PGresult *results, int act_tuple, int act_field, int lineno,
			  enum ECPGttype type, enum ECPGttype ind_type,
			  char *var, char *ind, long varcharsize, long offset,
			  long ind_offset, enum ARRAY_TYPE isarray, enum COMPAT_MODE compat, bool force_indicator)
{
	struct sqlca_t *sqlca = ECPGget_sqlca();
	char	   *pval = (char *) PQgetvalue(results, act_tuple, act_field);
	int			binary = PQfformat(results, act_field);
	int			size = PQgetlength(results, act_tuple, act_field);
	int			value_for_indicator = 0;
	long		log_offset;

	if (sqlca == NULL)
	{
		ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
				   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
		return false;
	}

	/*
	 * If we are running in a regression test, do not log the offset variable,
	 * it depends on the machine's alignment.
	 */
	if (ecpg_internal_regression_mode)
		log_offset = -1;
	else
		log_offset = offset;

	ecpg_log("ecpg_get_data on line %d: RESULT: %s offset: %ld; array: %s\n", lineno, pval ? (binary ? "BINARY" : pval) : "EMPTY", log_offset, ECPG_IS_ARRAY(isarray) ? "yes" : "no");

	/* pval is a pointer to the value */
	if (!pval)
	{
		/*
		 * This should never happen because we already checked that we found
		 * at least one tuple, but let's play it safe.
		 */
		ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
		return false;
	}

	/* We will have to decode the value */

	/*
	 * check for null value and set indicator accordingly, i.e. -1 if NULL and
	 * 0 if not
	 */
	if (PQgetisnull(results, act_tuple, act_field))
		value_for_indicator = -1;

	switch (ind_type)
	{
		case ECPGt_short:
		case ECPGt_unsigned_short:
			*((short *) (ind + ind_offset * act_tuple)) = value_for_indicator;
			break;
		case ECPGt_int:
		case ECPGt_unsigned_int:
			*((int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
			break;
		case ECPGt_long:
		case ECPGt_unsigned_long:
			*((long *) (ind + ind_offset * act_tuple)) = value_for_indicator;
			break;
#ifdef HAVE_LONG_LONG_INT
		case ECPGt_long_long:
		case ECPGt_unsigned_long_long:
			*((long long int *) (ind + ind_offset * act_tuple)) = value_for_indicator;
			break;
#endif							/* HAVE_LONG_LONG_INT */
		case ECPGt_NO_INDICATOR:
			if (value_for_indicator == -1)
			{
				if (force_indicator == false)
				{
					/*
					 * Informix has an additional way to specify NULLs note
					 * that this uses special values to denote NULL
					 */
					ECPGset_noind_null(type, var + offset * act_tuple);
				}
				else
				{
					ecpg_raise(lineno, ECPG_MISSING_INDICATOR,
							   ECPG_SQLSTATE_NULL_VALUE_NO_INDICATOR_PARAMETER,
							   NULL);
					return false;
				}
			}
			break;
		default:
			ecpg_raise(lineno, ECPG_UNSUPPORTED,
					   ECPG_SQLSTATE_ECPG_INTERNAL_ERROR,
					   ecpg_type_name(ind_type));
			return false;
			break;
	}

	if (value_for_indicator == -1)
		return true;

	/* let's check if it really is an array if it should be one */
	if (isarray == ECPG_ARRAY_ARRAY)
	{
		if (*pval != '{')
		{
			ecpg_raise(lineno, ECPG_DATA_NOT_ARRAY,
					   ECPG_SQLSTATE_DATATYPE_MISMATCH, NULL);
			return false;
		}

		switch (type)
		{
			case ECPGt_char:
			case ECPGt_unsigned_char:
			case ECPGt_varchar:
			case ECPGt_string:
				break;

			default:
				pval++;
				break;
		}
	}

	do
	{
		if (binary)
		{
			if (varcharsize == 0 || varcharsize * offset >= size)
				memcpy(var + offset * act_tuple, pval, size);
			else
			{
				memcpy(var + offset * act_tuple, pval, varcharsize * offset);

				if (varcharsize * offset < size)
				{
					/* truncation */
					switch (ind_type)
					{
						case ECPGt_short:
						case ECPGt_unsigned_short:
							*((short *) (ind + ind_offset * act_tuple)) = size;
							break;
						case ECPGt_int:
						case ECPGt_unsigned_int:
							*((int *) (ind + ind_offset * act_tuple)) = size;
							break;
						case ECPGt_long:
						case ECPGt_unsigned_long:
							*((long *) (ind + ind_offset * act_tuple)) = size;
							break;
#ifdef HAVE_LONG_LONG_INT
						case ECPGt_long_long:
						case ECPGt_unsigned_long_long:
							*((long long int *) (ind + ind_offset * act_tuple)) = size;
							break;
#endif							/* HAVE_LONG_LONG_INT */
						default:
							break;
					}
					sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';
				}
			}
			pval += size;
		}
		else
		{
			switch (type)
			{
					long		res;
					unsigned long ures;
					double		dres;
					char	   *scan_length;
					numeric    *nres;
					date		ddres;
					timestamp	tres;
					interval   *ires;
					char	   *endptr,
								endchar;

				case ECPGt_short:
				case ECPGt_int:
				case ECPGt_long:
					res = strtol(pval, &scan_length, 10);
					if (garbage_left(isarray, &scan_length, compat))
					{
						ecpg_raise(lineno, ECPG_INT_FORMAT,
								   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
						return false;
					}
					pval = scan_length;

					switch (type)
					{
						case ECPGt_short:
							*((short *) (var + offset * act_tuple)) = (short) res;
							break;
						case ECPGt_int:
							*((int *) (var + offset * act_tuple)) = (int) res;
							break;
						case ECPGt_long:
							*((long *) (var + offset * act_tuple)) = (long) res;
							break;
						default:
							/* Cannot happen */
							break;
					}
					break;

				case ECPGt_unsigned_short:
				case ECPGt_unsigned_int:
				case ECPGt_unsigned_long:
					ures = strtoul(pval, &scan_length, 10);
					if (garbage_left(isarray, &scan_length, compat))
					{
						ecpg_raise(lineno, ECPG_UINT_FORMAT,
								   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
						return false;
					}
					pval = scan_length;

					switch (type)
					{
						case ECPGt_unsigned_short:
							*((unsigned short *) (var + offset * act_tuple)) = (unsigned short) ures;
							break;
						case ECPGt_unsigned_int:
							*((unsigned int *) (var + offset * act_tuple)) = (unsigned int) ures;
							break;
						case ECPGt_unsigned_long:
							*((unsigned long *) (var + offset * act_tuple)) = (unsigned long) ures;
							break;
						default:
							/* Cannot happen */
							break;
					}
					break;

#ifdef HAVE_LONG_LONG_INT
#ifdef HAVE_STRTOLL
				case ECPGt_long_long:
					*((long long int *) (var + offset * act_tuple)) = strtoll(pval, &scan_length, 10);
					if (garbage_left(isarray, &scan_length, compat))
					{
						ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
						return false;
					}
					pval = scan_length;

					break;
#endif							/* HAVE_STRTOLL */
#ifdef HAVE_STRTOULL
				case ECPGt_unsigned_long_long:
					*((unsigned long long int *) (var + offset * act_tuple)) = strtoull(pval, &scan_length, 10);
					if (garbage_left(isarray, &scan_length, compat))
					{
						ecpg_raise(lineno, ECPG_UINT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
						return false;
					}
					pval = scan_length;

					break;
#endif							/* HAVE_STRTOULL */
#endif							/* HAVE_LONG_LONG_INT */

				case ECPGt_float:
				case ECPGt_double:
					if (isarray && *pval == '"')
						pval++;

					if (!check_special_value(pval, &dres, &scan_length))
						dres = strtod(pval, &scan_length);

					if (isarray && *scan_length == '"')
						scan_length++;

					/* no special INFORMIX treatment for floats */
					if (garbage_left(isarray, &scan_length, ECPG_COMPAT_PGSQL))
					{
						ecpg_raise(lineno, ECPG_FLOAT_FORMAT,
								   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
						return false;
					}
					pval = scan_length;

					switch (type)
					{
						case ECPGt_float:
							*((float *) (var + offset * act_tuple)) = dres;
							break;
						case ECPGt_double:
							*((double *) (var + offset * act_tuple)) = dres;
							break;
						default:
							/* Cannot happen */
							break;
					}
					break;

				case ECPGt_bool:
					if (pval[0] == 'f' && pval[1] == '\0')
					{
						*((bool *) (var + offset * act_tuple)) = false;
						pval++;
						break;
					}
					else if (pval[0] == 't' && pval[1] == '\0')
					{
						*((bool *) (var + offset * act_tuple)) = true;
						pval++;
						break;
					}
					else if (pval[0] == '\0' && PQgetisnull(results, act_tuple, act_field))
					{
						/* NULL is valid */
						break;
					}

					ecpg_raise(lineno, ECPG_CONVERT_BOOL,
							   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
					return false;
					break;

				case ECPGt_char:
				case ECPGt_unsigned_char:
				case ECPGt_string:
					{
						char	   *str = (char *) (var + offset * act_tuple);

						/*
						 * If varcharsize is unknown and the offset is that of
						 * char *, then this variable represents the array of
						 * character pointers. So, use extra indirection.
						 */
						if (varcharsize == 0 && offset == sizeof(char *))
							str = *(char **) str;

						if (varcharsize == 0 || varcharsize > size)
						{
							/*
							 * compatibility mode, blank pad and null
							 * terminate char array
							 */
							if (ORACLE_MODE(compat) && (type == ECPGt_char || type == ECPGt_unsigned_char))
							{
								memset(str, ' ', varcharsize);
								memcpy(str, pval, size);
								str[varcharsize - 1] = '\0';

								/*
								 * compatibility mode empty string gets -1
								 * indicator but no warning
								 */
								if (size == 0)
								{
									/* truncation */
									switch (ind_type)
									{
										case ECPGt_short:
										case ECPGt_unsigned_short:
											*((short *) (ind + ind_offset * act_tuple)) = -1;
											break;
										case ECPGt_int:
										case ECPGt_unsigned_int:
											*((int *) (ind + ind_offset * act_tuple)) = -1;
											break;
										case ECPGt_long:
										case ECPGt_unsigned_long:
											*((long *) (ind + ind_offset * act_tuple)) = -1;
											break;
#ifdef HAVE_LONG_LONG_INT
										case ECPGt_long_long:
										case ECPGt_unsigned_long_long:
											*((long long int *) (ind + ind_offset * act_tuple)) = -1;
											break;
#endif							/* HAVE_LONG_LONG_INT */
										default:
											break;
									}
								}
							}
							else
							{
								strncpy(str, pval, size + 1);
							}
							/* do the rtrim() */
							if (type == ECPGt_string)
							{
								char	   *last = str + size;

								while (last > str && (*last == ' ' || *last == '\0'))
								{
									*last = '\0';
									last--;
								}
							}
						}
						else
						{
							strncpy(str, pval, varcharsize);

							/* compatibility mode, null terminate char array */
							if (ORACLE_MODE(compat) && (varcharsize - 1) < size)
							{
								if (type == ECPGt_char || type == ECPGt_unsigned_char)
									str[varcharsize - 1] = '\0';
							}

							if (varcharsize < size || (ORACLE_MODE(compat) && (varcharsize - 1) < size))
							{
								/* truncation */
								switch (ind_type)
								{
									case ECPGt_short:
									case ECPGt_unsigned_short:
										*((short *) (ind + ind_offset * act_tuple)) = size;
										break;
									case ECPGt_int:
									case ECPGt_unsigned_int:
										*((int *) (ind + ind_offset * act_tuple)) = size;
										break;
									case ECPGt_long:
									case ECPGt_unsigned_long:
										*((long *) (ind + ind_offset * act_tuple)) = size;
										break;
#ifdef HAVE_LONG_LONG_INT
									case ECPGt_long_long:
									case ECPGt_unsigned_long_long:
										*((long long int *) (ind + ind_offset * act_tuple)) = size;
										break;
#endif							/* HAVE_LONG_LONG_INT */
									default:
										break;
								}
								sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';
							}
						}
						pval += size;
					}
					break;

				case ECPGt_varchar:
					{
						struct ECPGgeneric_varchar *variable =
						(struct ECPGgeneric_varchar *) (var + offset * act_tuple);

						variable->len = size;
						if (varcharsize == 0)
							strncpy(variable->arr, pval, variable->len);
						else
						{
							strncpy(variable->arr, pval, varcharsize);

							if (variable->len > varcharsize)
							{
								/* truncation */
								switch (ind_type)
								{
									case ECPGt_short:
									case ECPGt_unsigned_short:
										*((short *) (ind + ind_offset * act_tuple)) = variable->len;
										break;
									case ECPGt_int:
									case ECPGt_unsigned_int:
										*((int *) (ind + ind_offset * act_tuple)) = variable->len;
										break;
									case ECPGt_long:
									case ECPGt_unsigned_long:
										*((long *) (ind + ind_offset * act_tuple)) = variable->len;
										break;
#ifdef HAVE_LONG_LONG_INT
									case ECPGt_long_long:
									case ECPGt_unsigned_long_long:
										*((long long int *) (ind + ind_offset * act_tuple)) = variable->len;
										break;
#endif							/* HAVE_LONG_LONG_INT */
									default:
										break;
								}
								sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';

								variable->len = varcharsize;
							}
						}
						pval += size;
					}
					break;

				case ECPGt_decimal:
				case ECPGt_numeric:
					for (endptr = pval; *endptr && *endptr != ',' && *endptr != '}'; endptr++);
					endchar = *endptr;
					*endptr = '\0';
					nres = PGTYPESnumeric_from_asc(pval, &scan_length);
					*endptr = endchar;

					/* did we get an error? */
					if (nres == NULL)
					{
						ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
								 lineno, pval, errno);

						if (INFORMIX_MODE(compat))
						{
							/*
							 * Informix wants its own NULL value here instead
							 * of an error
							 */
							nres = PGTYPESnumeric_new();
							if (nres)
								ECPGset_noind_null(ECPGt_numeric, nres);
							else
							{
								ecpg_raise(lineno, ECPG_OUT_OF_MEMORY,
										   ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
								return false;
							}
						}
						else
						{
							ecpg_raise(lineno, ECPG_NUMERIC_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}
					else
					{
						if (!isarray && garbage_left(isarray, &scan_length, compat))
						{
							free(nres);
							ecpg_raise(lineno, ECPG_NUMERIC_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}
					pval = scan_length;

					if (type == ECPGt_numeric)
						PGTYPESnumeric_copy(nres, (numeric *) (var + offset * act_tuple));
					else
						PGTYPESnumeric_to_decimal(nres, (decimal *) (var + offset * act_tuple));

					PGTYPESnumeric_free(nres);
					break;

				case ECPGt_interval:
					if (*pval == '"')
						pval++;

					for (endptr = pval; *endptr && *endptr != ',' && *endptr != '"' && *endptr != '}'; endptr++);
					endchar = *endptr;
					*endptr = '\0';
					ires = PGTYPESinterval_from_asc(pval, &scan_length);
					*endptr = endchar;

					/* did we get an error? */
					if (ires == NULL)
					{
						ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
								 lineno, pval, errno);

						if (INFORMIX_MODE(compat))
						{
							/*
							 * Informix wants its own NULL value here instead
							 * of an error
							 */
							ires = (interval *) ecpg_alloc(sizeof(interval), lineno);
							if (!ires)
								return false;

							ECPGset_noind_null(ECPGt_interval, ires);
						}
						else
						{
							ecpg_raise(lineno, ECPG_INTERVAL_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}
					else
					{
						if (*scan_length == '"')
							scan_length++;

						if (!isarray && garbage_left(isarray, &scan_length, compat))
						{
							free(ires);
							ecpg_raise(lineno, ECPG_INTERVAL_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}
					pval = scan_length;

					PGTYPESinterval_copy(ires, (interval *) (var + offset * act_tuple));
					free(ires);
					break;

				case ECPGt_date:
					if (*pval == '"')
						pval++;

					for (endptr = pval; *endptr && *endptr != ',' && *endptr != '"' && *endptr != '}'; endptr++);
					endchar = *endptr;
					*endptr = '\0';
					ddres = PGTYPESdate_from_asc(pval, &scan_length);
					*endptr = endchar;

					/* did we get an error? */
					if (errno != 0)
					{
						ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
								 lineno, pval, errno);

						if (INFORMIX_MODE(compat))
						{
							/*
							 * Informix wants its own NULL value here instead
							 * of an error
							 */
							ECPGset_noind_null(ECPGt_date, &ddres);
						}
						else
						{
							ecpg_raise(lineno, ECPG_DATE_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}
					else
					{
						if (*scan_length == '"')
							scan_length++;

						if (!isarray && garbage_left(isarray, &scan_length, compat))
						{
							ecpg_raise(lineno, ECPG_DATE_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}

					*((date *) (var + offset * act_tuple)) = ddres;
					pval = scan_length;
					break;

				case ECPGt_timestamp:
					if (*pval == '"')
						pval++;

					for (endptr = pval; *endptr && *endptr != ',' && *endptr != '"' && *endptr != '}'; endptr++);
					endchar = *endptr;
					*endptr = '\0';
					tres = PGTYPEStimestamp_from_asc(pval, &scan_length);
					*endptr = endchar;

					/* did we get an error? */
					if (errno != 0)
					{
						ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n",
								 lineno, pval, errno);

						if (INFORMIX_MODE(compat))
						{
							/*
							 * Informix wants its own NULL value here instead
							 * of an error
							 */
							ECPGset_noind_null(ECPGt_timestamp, &tres);
						}
						else
						{
							ecpg_raise(lineno, ECPG_TIMESTAMP_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}
					else
					{
						if (*scan_length == '"')
							scan_length++;

						if (!isarray && garbage_left(isarray, &scan_length, compat))
						{
							ecpg_raise(lineno, ECPG_TIMESTAMP_FORMAT,
									   ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
							return false;
						}
					}

					*((timestamp *) (var + offset * act_tuple)) = tres;
					pval = scan_length;
					break;

				default:
					ecpg_raise(lineno, ECPG_UNSUPPORTED,
							   ECPG_SQLSTATE_ECPG_INTERNAL_ERROR,
							   ecpg_type_name(type));
					return false;
					break;
			}
			if (ECPG_IS_ARRAY(isarray))
			{
				bool		string = false;

				/* set array to next entry */
				++act_tuple;

				/* set pval to the next entry */

				/*
				 * *pval != '\0' should not be needed, but is used as a safety
				 * guard
				 */
				for (; *pval != '\0' && (string || (!array_delimiter(isarray, *pval) && !array_boundary(isarray, *pval))); ++pval)
					if (*pval == '"')
						string = string ? false : true;

				if (array_delimiter(isarray, *pval))
					++pval;
			}
		}
	} while (*pval != '\0' && !array_boundary(isarray, *pval));

	return true;
}
