/* Copyright comment */
/*
 * The aim is to get a simpler inteface to the database routines.
 * All the tidieous messing around with tuples is supposed to be hidden
 * by this function.
 */
/* Author: Linus Tolke 
   (actually most if the code is "borrowed" from the distribution and just
    slightly modified)
 */
 
/* Taken over as part of PostgreSQL by Michael Meskes <meskes@debian.org>
   on Feb. 5th, 1998 */
   
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>

#include <ecpgtype.h>
#include <ecpglib.h>
#include <sqlca.h>
#include <libpq-fe.h>
#include <libpq/pqcomm.h>

static PGconn *	simple_connection;
static int	simple_debug = 0;
static int	committed = true;

static void
register_error(int code, char *fmt, ...)
{
    va_list args;

    sqlca.sqlcode = code;
    va_start (args, fmt);
    vsprintf (sqlca.sqlerrm.sqlerrmc, fmt, args);
    va_end (args);
    sqlca.sqlerrm.sqlerrml = strlen (sqlca.sqlerrm.sqlerrmc);
}

/* This function returns a newly malloced string that has the ' and \
   in the argument quoted with \.
 */
static
char *
quote_postgres(char * arg)
{
    char * res = (char *)malloc(2 * strlen(arg) + 1);
    int i, ri;

    for (i = 0, ri = 0; arg[i]; i++, ri++)
    {
        switch (arg[i])
	{
	case '\'':
	case '\\':
	    res[ri++] = '\\';
	default:
	    ;
	}

	res[ri] = arg[i];
    }
    res[ri] = '\0';

    return res;
}


bool
ECPGdo(int lineno, char * query, ...)
{
    va_list ap;
    bool status = false;
    char * copiedquery;
    PGresult *	results;
    PGnotify *	notify;
    enum ECPGttype type;

    va_start(ap, query);

    sqlca.sqlcode = 0;
    copiedquery = strdup(query);

    type = va_arg(ap, enum ECPGttype);

    /*
     * Now, if the type is one of the fill in types then we take the argument
     * and enter that in the string at the first %s position. Then if there
     * are any more fill in types we fill in at the next and so on.
     */
    while (type != ECPGt_EOIT)
    {
	void * value = NULL;
	short varcharsize;
	short size;
	short arrsize;

        char * newcopy;
	char * mallocedval = NULL;
	char * tobeinserted = NULL;
	char * p;
	char buff[20];

	/* Some special treatment is needed for records since we want their
	   contents to arrive in a comma-separated list on insert (I think). */

	value = va_arg(ap, void *);
	varcharsize = va_arg(ap, short);
	size = va_arg(ap, short);
	arrsize = va_arg(ap, short);

	switch (type) {
	case ECPGt_char:
	case ECPGt_short:
	case ECPGt_int:
	    sprintf(buff, "%d", *(int*)value);
	    tobeinserted = buff;
	    break;

	case ECPGt_unsigned_char:
	case ECPGt_unsigned_short:
	case ECPGt_unsigned_int:
	    sprintf(buff, "%d", *(unsigned int*)value);
	    tobeinserted = buff;
	    break;

	case ECPGt_long:
	    sprintf(buff, "%ld", *(long*)value);
	    tobeinserted = buff;
	    break;

	case ECPGt_unsigned_long:
	    sprintf(buff, "%ld", *(unsigned long*)value);
	    tobeinserted = buff;
	    break;

	case ECPGt_float:
	    sprintf(buff, "%.14g", *(float*)value);
	    tobeinserted = buff;
	    break;

	case ECPGt_double:
	    sprintf(buff, "%.14g", *(double*)value);
	    tobeinserted = buff;
	    break;

	case ECPGt_varchar:
	case ECPGt_varchar2:
	    {
		struct ECPGgeneric_varchar * var =
		    (struct ECPGgeneric_varchar*)value;

		newcopy = (char *)malloc(var->len + 1);
		strncpy(newcopy, var->arr, var->len);
		newcopy[var->len] = '\0';
	    
		mallocedval = (char *)malloc(2 * strlen(newcopy) + 3);
		strcpy(mallocedval, "'");
		strcat(mallocedval, quote_postgres(newcopy));
		strcat(mallocedval, "'");

		free(newcopy);

		tobeinserted = mallocedval;
	    }
	    break;

	default:
	    /* Not implemented yet */
	    register_error(-1, "Unsupported type %s on line %d.",
			   ECPGtype_name(type), lineno);
	    return false;
	    break;
	}

	/* Now tobeinserted points to an area that is to be inserted at 
	   the first %s 
	 */
	newcopy = (char *)malloc(strlen(copiedquery) 
				 + strlen(tobeinserted) 
				 + 1);
	strcpy(newcopy, copiedquery);
	if ((p = strstr(newcopy, ";;")) == NULL)
	{
	    /* We have an argument but we dont have the matched up string 
	       in the string
	     */
	    register_error(-1, "Too many arguments line %d.", lineno);
	    return false;
	}
	else
	{
	    strcpy(p, tobeinserted);
	    /* The strange thing in the second argument is the rest of the
	       string from the old string */
	    strcat(newcopy, 
		   copiedquery
		   + ( p - newcopy ) 
		   + 2 /* Length of ;; */);
	}

	/* Now everything is safely copied to the newcopy. Lets free the
	   oldcopy and let the copiedquery get the value from the newcopy.
	 */
	if (mallocedval != NULL)
	{
	    free(mallocedval);
	    mallocedval = NULL;
	}

	free(copiedquery);
	copiedquery = newcopy;

        type = va_arg(ap, enum ECPGttype);
    }

    /* Check if there are unmatched things left. */
    if (strstr(copiedquery, ";;") != NULL)
    {
	register_error(-1, "Too few arguments line %d.", lineno);
	return false;
    }

    /* Now then request is built. */

    if (committed)
    {
	if ((results = PQexec (simple_connection, "begin")) == NULL) {
	    register_error(-1, "Error starting transaction line %d.", lineno);
	    return false;
	}
	PQclear (results);
	committed = 0;
    }

    ECPGlog("ECPGdo line %d: QUERY: %s\n", lineno, copiedquery);
    results = PQexec(simple_connection, copiedquery); 
    free(copiedquery);

    if (results == NULL)
    {
        ECPGlog("ECPGdo line %d: error: %s", lineno, 
		PQerrorMessage(simple_connection));
	register_error(-1, "Postgres error: %s line %d.",
		       PQerrorMessage(simple_connection), lineno);
    }
    else switch(PQresultStatus(results)) 
    {
        int m,n,x;

    case PGRES_TUPLES_OK:
	/* XXX Cheap Hack. For now, we see only the last group
	 * of tuples.  This is clearly not the right
	 * way to do things !!
	 */

	m = PQnfields(results);
	n = PQntuples(results);
    
	if (n < 1)
	{
	    ECPGlog("ECPGdo lineno %d: Incorrect number of matches: %d\n", 
		    lineno, n);
	    register_error(1, "Data not found line %d.", lineno);
	    break;
	}

	if (n > 1)
	{
	    ECPGlog("ECPGdo line %d: Incorrect number of matches: %d\n", 
		    lineno, n);
	    register_error(-1, "To many matches line %d.", lineno);
	    break;
	}

	status = true;

	for (x = 0; x < m && status; x++) 
	{
	    void * value = NULL;
	    short varcharsize;
	    short size;
	    short arrsize;

	    char 	*pval = PQgetvalue(results,0,x);
	    /*long int	* res_int;
	    char 	** res_charstar;
	    char	* res_char;
	    int		res_len;*/
	    char	* scan_length;

	    ECPGlog("ECPGdo line %d: RESULT: %s\n", lineno, pval ? pval : "");

	    /* No the pval is a pointer to the value. */
	    /* We will have to decode the value */
	    type = va_arg(ap, enum ECPGttype);
	    value = va_arg(ap, void *);
	    varcharsize = va_arg(ap, short);
	    size = va_arg(ap, short);
	    arrsize = va_arg(ap, short);

	    switch (type)
	    {
		long res;
		unsigned long ures;
		double dres;

	    case ECPGt_char:
	    case ECPGt_short:
	    case ECPGt_int:
	    case ECPGt_long:
		if (pval)
		{
		    res = strtol(pval, &scan_length, 10);
		    if (*scan_length != '\0') /* Garbage left */
		    {
			register_error(-1, "Not correctly formatted int type: %s line %d.",
				       pval, lineno);
			status = false;
			res = 0L;
		    }
		}
		else
		    res = 0L;

		/* Again?! Yes */
		switch (type)
		{
		case ECPGt_char:
		    *(char *)value = (char)res;
		    break;
		case ECPGt_short:
		    *(short *)value = (short)res;
		    break;
		case ECPGt_int:
		    *(int *)value = (int)res;
		    break;
		case ECPGt_long:
		    *(long *)value = res;
		    break;
		default:
		    /* Cannot happen */
		    break;
		}
		break;

	    case ECPGt_unsigned_char:
	    case ECPGt_unsigned_short:
	    case ECPGt_unsigned_int:
	    case ECPGt_unsigned_long:
		if (pval)
		{
		    ures = strtoul(pval, &scan_length, 10);
		    if (*scan_length != '\0') /* Garbage left */
		    {
			register_error(-1, "Not correctly formatted unsigned type: %s line %d.",
				       pval, lineno);
			status = false;
			ures = 0L;
		    }
		}
		else
		    ures = 0L;

		/* Again?! Yes */
		switch (type)
		{
		case ECPGt_unsigned_char:
		    *(unsigned char *)value = (unsigned char)ures;
		    break;
		case ECPGt_unsigned_short:
		    *(unsigned short *)value = (unsigned short)ures;
		    break;
		case ECPGt_unsigned_int:
		    *(unsigned int *)value = (unsigned int)ures;
		    break;
		case ECPGt_unsigned_long:
		    *(unsigned long *)value = ures;
		    break;
		default:
		    /* Cannot happen */
		    break;
		}
		break;


	    case ECPGt_float:
	    case ECPGt_double:
		if (pval)
		{
		    dres = strtod(pval, &scan_length);
		    if (*scan_length != '\0') /* Garbage left */
		    {
			register_error(-1, "Not correctly formatted floating point type: %s line %d.",
				       pval, lineno);
			status = false;
			dres = 0.0;
		    }
		}
		else
		    dres = 0.0;

		/* Again?! Yes */
		switch (type)
		{
		case ECPGt_float:
		    *(float *)value = (float)res;
		    break;
		case ECPGt_double:
		    *(double *)value = res;
		    break;
		default:
		    /* Cannot happen */
		    break;
		}
		break;


	    case ECPGt_varchar:
		{
		    struct ECPGgeneric_varchar * var = 
			(struct ECPGgeneric_varchar*)value;

		    strncpy(var->arr, pval, varcharsize);
		    var->len = strlen(pval);
		    if (var->len > varcharsize)
			var->len = varcharsize;
		}
		break;

	    case ECPGt_EORT:
	        ECPGlog("ECPGdo line %d: Too few arguments.\n", lineno);
		register_error(-1, "Too few arguments line %d.", lineno);
		status = false;
		break;

	    default:
		register_error(-1, "Unsupported type %s on line %d.",
			       ECPGtype_name(type), lineno);
		return false;
		break;
	    }
	}

	type = va_arg(ap, enum ECPGttype);

	if (status && type != ECPGt_EORT)
	{
	    register_error(-1, "Too many arguments line %d.", lineno);
	    return false;
	}

	PQclear(results);
	break;
    case PGRES_EMPTY_QUERY:
        /* do nothing */
	register_error(-1, "Empty query line %d.", lineno);
        break;
    case PGRES_COMMAND_OK:
        status = true;
        ECPGlog("ECPGdo line %d Ok: %s\n", lineno, PQcmdStatus(results));
	break;
    case PGRES_NONFATAL_ERROR:
    case PGRES_FATAL_ERROR:
    case PGRES_BAD_RESPONSE:
        ECPGlog("ECPGdo line %d: Error: %s",
		lineno, PQerrorMessage(simple_connection));
	register_error(-1, "Error: %s line %d.", 
		       PQerrorMessage(simple_connection), lineno);
	break;
    case PGRES_COPY_OUT:
        ECPGlog("ECPGdo line %d: Got PGRES_COPY_OUT ... tossing.\n", lineno);
        PQendcopy(results->conn);
	break;
    case PGRES_COPY_IN:
        ECPGlog("ECPGdo line %d: Got PGRES_COPY_IN ... tossing.\n", lineno);
        PQendcopy(results->conn);
	break;
    default:
        ECPGlog("ECPGdo line %d: Got something else, postgres error.\n", 
		lineno);
	register_error(-1, "Postgres error line %d.", lineno);
	break;
    }

    /* check for asynchronous returns */
    notify = PQnotifies(simple_connection);
    if (notify) {
        ECPGlog("ECPGdo line %d: ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
		lineno, notify->relname, notify->be_pid);
	free(notify);
    }

    va_end(ap);
    return status;
}


bool
ECPGcommit(int lineno)
{
    PGresult *res;

    ECPGlog("ECPGcommit line %d\n", lineno);
    if ((res = PQexec (simple_connection, "end")) == NULL) {
	register_error(-1, "Error committing line %d.", lineno);
	return (FALSE);
    }
    PQclear (res);
    committed = 1;
    return (TRUE);
}

bool
ECPGrollback(int lineno)
{
    PGresult *res;

    ECPGlog("ECPGrollback line %d\n", lineno);
    if ((res = PQexec (simple_connection, "abort")) == NULL) {
	register_error(-1, "Error rolling back line %d.", lineno);
	return (FALSE);
    }
    PQclear (res);
    committed = 1;
    return(TRUE);
}



bool
ECPGsetdb(PGconn * newcon)
{
    ECPGfinish();
    simple_connection = newcon;
    return true;
}

bool
ECPGconnect(const char * dbname)
{
    char * name = strdup(dbname);
    ECPGlog("ECPGconnect: opening database %s\n", name);

    sqlca.sqlcode = 0;

    ECPGsetdb(PQsetdb(NULL, NULL, NULL, NULL, name));

    free(name);
    name = NULL;

    if (PQstatus(simple_connection) == CONNECTION_BAD)
    {
        ECPGfinish();
	ECPGlog("ECPGconnect: could not open database %s\n", dbname);
	register_error(-1, "ECPGconnect: could not open database %s.", dbname);
	return false;
    }
    return true;
}


bool
ECPGstatus()
{
    return PQstatus(simple_connection) != CONNECTION_BAD;
}


bool
ECPGfinish()
{
    if (simple_connection != NULL)
    {
        ECPGlog("ECPGfinish: finishing.\n");
        PQfinish(simple_connection);
    }
    else
        ECPGlog("ECPGfinish: called an extra time.\n");
    return true;
}

void
ECPGdebug(int n)
{
    simple_debug = n;
    ECPGlog("ECPGdebug: set to %d\n", simple_debug);
}

void
ECPGlog(const char * format, ...)
{
    va_list ap;
    if (simple_debug) 
    {
        char * f = (char *) malloc(strlen(format) + 100);

	sprintf(f, "[%d]: %s", getpid(), format);

	va_start(ap, format);
        vfprintf(stderr, f, ap);
	va_end(ap);

	free(f);
    }
}
