/*
 * arrayfuncs.c --
 *     Special array in and out functions for arrays.
 *
 *
 * RcsId("$Header: /private/postgres/src/utils/adt/RCS/arrayfuncs.c,v 1.19 1992/08/07 02:31:23 mer Exp $");
 */

#include <ctype.h>
#include "tmp/postgres.h"
#include "tmp/align.h"

#include "catalog/pg_type.h"
#include "catalog/syscache.h"

#include "utils/palloc.h"
#include "utils/fmgr.h"
#include "utils/log.h"

/*
 * Function prototypes - contained in this file as no one outside should
 * be calling these except the function manager.
 */

int array_count ARGS((char *string , char delimiter ));
int system_cache_lookup ARGS((
    ObjectId
    element_type ,
    Boolean input ,
    int *typlen ,
    bool *typbyval ,
    char *typdelim ,
    ObjectId *typelem ,
    ObjectId *proc
));

/*
 *    array_count - counts the number of elements in an array.
 */

int
array_count(string, delimiter)

char *string;
char delimiter;

{
    int i = 1, nelems = 0;
    bool scanning_string = false;
    int nest_level = 1;

    while (nest_level >= 1)
    {
        switch (string[i])
        {
            case '\\':
                i++;
                break;
            case '{':
                if (!scanning_string) nest_level++;
                break;
            case '}':
                if (!scanning_string) nest_level--;
                break;
            case '\"':
                scanning_string = !scanning_string;
                break;
	    case 0:
		elog(WARN, "array constant is missing a closing bracket");
		break;
            default:
                if (!scanning_string
                  && nest_level == 1
                  && string[i] == delimiter)
                {
                    nelems++;
                }
                break;
        }
        i++;
    }

    /*
     * account for last element in list.
     *
     * Careful! If the array string is "{}" there is no 'last element'
     */

    return(((nelems > 0) ? (nelems + 1) : 0));
}

/*
 *    array_in - takes an array surrounded by {...} and returns it in
 *    VARLENA format:
 */

char *
array_in(string, element_type)

char *string;
ObjectId element_type;

{
    static ObjectId charTypid = InvalidObjectId;
    int typlen;
    bool typbyval;
    char typdelim;
    ObjectId typinput;
    ObjectId typelem;
    char *string_save, *p, *q, *r;
    char **values;
    func_ptr inputproc;
    int i, nitems, dummy;
    int32 nbytes;
    char *retval;
    bool scanning_string = false;

    if (charTypid == InvalidObjectId)
    {
	HeapTuple tup;

	tup = (HeapTuple)SearchSysCacheTuple(TYPNAME, "char");
	if ((charTypid = tup->t_oid) == InvalidObjectId)
	    elog(WARN, "type lookup on char failed");
    }

    system_cache_lookup(element_type, true, &typlen, &typbyval,
                        &typdelim, &typelem, &typinput);

    fmgr_info(typinput, & inputproc, &dummy);

    string_save = (char *) palloc(strlen(string) + 3);

    if (string[0] != '{')
    {
	if (element_type == charTypid)
	{
	    /*
	     * this is a hack to allow char arrays to have a { in the
	     * first position.  Of course now \{ can never be the first
	     * two characters, but what else can we do?
	     */
	    string =
		(*string == '\\' && *(string+1) == '{') ? &string[1] : string;
	    return (char *)textin(string);
	}
	else
	    elog(WARN, "array_in:  malformed array constant");
    }
    else
    {
        strcpy(string_save, string);
    }

    if ((nitems = array_count(string, typdelim)) == 0)
    {
	char *emptyArray = palloc(sizeof(int32));

	* (int32 *) emptyArray = sizeof(int32);
	return emptyArray;
    }

    values        = (char **) palloc(nitems * sizeof(char *));

    p = q = string_save;

    p++; q++; /* get past leading '{' */

    for (i = 0; i < nitems; i++)
    {
        int nest_level = 0;
        bool done = false;
	bool eoArray = false;

        while (!done)
        {
            switch (*q)
            {
                case '\\':
                    /* Crunch the string on top of the backslash. */
                    for (r = q; *r != '\0'; r++) *r = *(r+1);
                    break;
                case '\"':
                    if (!scanning_string && nest_level == 0)
                    {
                        scanning_string = true;
                        while (p != q) p++;
                        p++; /* get p past first doublequote */
                        break;
                    }
                    else if (nest_level == 0)
                    {
                        scanning_string = false;
                        *q = '\0';
                    }
                    break;
                case '{':
                    if (!scanning_string) nest_level++;
                    break;
                case '}':
                    if (nest_level == 0 && !scanning_string)
		    {
                        done = true;
			eoArray = true;
		    }
                    else if (!scanning_string) nest_level--;
                    break;
                default:
                    if (*q == typdelim && !scanning_string && nest_level == 0)
			done = true;
                    break;
            }
            if (!done) q++;
        }
        *q = '\0';                    /* Put a null at the end of it */
        values[i] = (*inputproc) (p, typelem);
	if (!typbyval && !values[i])
	{
	    elog(NOTICE, "pass by reference array element is NULL, you may");
	    elog(WARN, "need to quote each individual element in the constant");
	}
	p = ++q;	/* p goes past q */
	if (!eoArray)	/* if not at the end of the array skip white space */
	    while (isspace(*q))
	    {
		p++;
		q++;
	    }
    }

    if (typlen > 0)
    {
        nbytes = nitems * typlen + sizeof(int32);
    }
    else
    {
        for (i = 0, nbytes = 0;
             i < nitems;
             nbytes += LONGALIGN(* (int32 *) values[i]), i++);
        nbytes += sizeof(int32);
    }

    retval = (char *) palloc(nbytes);
    p = retval + 4;

    bcopy(&nbytes, retval, sizeof(int4));

    for (i = 0; i < nitems; i++)
    {
        if (typlen > 0)
        {
            if (typbyval)
            {
		char oneByte;
		short twoBytes;
		switch(typlen) {
		    case 1: 
			*p = DatumGetChar(values[i]);
			break;
		    case 2: 
			* (int16 *) p = DatumGetInt16(values[i]);
			break;
		    case 4:
			* (int32 *) p = (int32)values[i];
			break;
		}
            }
            else
                bcopy(values[i], p, typlen);
            p += typlen;
        }
        else
        {
            int len;

            len = LONGALIGN(* (int32 *) values[i]);
            bcopy(values[i], p, * (int32 *) values[i]);
            p += len;
        }
        if (!typbyval) pfree(values[i]);
    }
    pfree(string_save);
    pfree((char *)values);
    return(retval);
}

char *
array_out(items, element_type)

char *items;
ObjectId element_type;

{
    /*
     * statics so we don't do excessive system cache lookups.
     */

    int typlen;
    bool typbyval;
    char typdelim;
    ObjectId typoutput;
    ObjectId typelem;

    char *p;
    char *retval;
    char **values;
    int32 nitems, nbytes, overall_length;
    int i, dummy;
    func_ptr outputproc;
    char delim[2];

    system_cache_lookup(element_type, false, &typlen, &typbyval,
                        &typdelim, &typelem, &typoutput);

    fmgr_info(typoutput, & outputproc, &dummy);
    sprintf(delim, "%c", typdelim);

    /*
     * It's an array of fixed-length things (either fixed-length arrays
     * or non-array objects.  We can compute the number of items just by
     * dividing the number of bytes in the blob of memory by the length
     * of each element.
     */

    if (typlen > 0)
    {
        nitems = (* (int32 *) items - 4)/ typlen;
    }
    else

    /*
     * It's an array of variable length objects.  We have to manually walk
     * through each variable length element to count the number of elements.
     */

    {
        nbytes = (* (int32 *) items) - sizeof(int32);
        nitems = 0;
        p = items + sizeof(int32);

        while (nbytes != 0)
        {
            nbytes -= LONGALIGN(* (int32 *) p);
            p += LONGALIGN(* (int32 *) p);
            nitems++;
        }
    }

    items += sizeof(int32);

    if (nitems == 0)
    {
	char *emptyArray = palloc(3);
	emptyArray[0] = '{';
	emptyArray[1] = '}';
	emptyArray[2] = '\0';
	return emptyArray;
    }
    values = (char **) palloc(nitems * sizeof (char *));
    overall_length = 0;

    for (i = 0; i < nitems; i++)
    {
        if (typbyval)
        {
            switch(typlen)
            {
                case 1:
                    values[i] = (*outputproc) (*items, typelem);
                    break;
                case 2:
                    values[i] = (*outputproc) (* (int16 *) items, typelem);
                    break;
                case 3:
                case 4:
                    values[i] = (*outputproc) (* (int32 *) items, typelem);
                    break;
            }
            items += typlen;
        }
        else
        {
            values[i] = (*outputproc) (items, typelem);
            if (typlen > 0)
                items += typlen;
            else
                items += LONGALIGN(* (int32 *) items);
	    /*
	     * For the pair of double quotes
	     */
	    overall_length += 2;
        }
        overall_length += strlen(values[i]);

        /*
         * So that the mapping between input and output functions is preserved
         * in the case of array_out(array_in(string)), we have to put double
         * quotes around strings that look like text strings.  To be sure, and
         * since it will not break anything, we will put double quotes around
         * anything that is not passed by value.
         */

        if (!typbyval) overall_length += 2;
    }

    p = (char *) palloc(overall_length + 3);
    retval = p;

    strcpy(p, "{");

    for (i = 0; i < nitems; i++)
    {
        /*
         * Surround anything that is not passed by value in double quotes.
         * See above for more details.
         */

        if (!typbyval)
        {
            strcat(p, "\"");
            strcat(p, values[i]);
            strcat(p, "\"");
        }
        else
        {
            strcat(p, values[i]);
        }

        if (i != nitems - 1) strcat(p, delim); else strcat(p, "}");
        pfree(values[i]);
    }

    pfree((char *)values);

    return(retval);
}

system_cache_lookup(element_type, input, typlen, typbyval, typdelim,
                    typelem, proc)

ObjectId element_type;
Boolean input;
int *typlen;
bool *typbyval;
char *typdelim;
ObjectId *typelem;
ObjectId *proc;

{
    HeapTuple typeTuple;
    TypeTupleForm typeStruct;

    typeTuple = SearchSysCacheTuple(TYPOID, element_type, NULL, NULL, NULL);

    if (!HeapTupleIsValid(typeTuple))
    {
        elog(WARN, "array_out: Cache lookup failed for type %d\n",
             element_type);
        return NULL;
    }
    typeStruct = (TypeTupleForm) GETSTRUCT(typeTuple);
    *typlen    = typeStruct->typlen;
    *typbyval  = typeStruct->typbyval;
    *typdelim  = typeStruct->typdelim;
    *typelem   = typeStruct->typelem;
    if (input)
    {
        *proc = typeStruct->typinput;
    }
    else
    {
        *proc = typeStruct->typoutput;
    }
}
