/*
 *  array.c -- Array large object type for Postgres, Sequoia project
 *
 *	Arrays are pointers to a large object and some descriptive
 *	information (bytes/element, number of dimensions, a vector
 *	of dimensions, and a format key).
 */

#include <stdio.h>
#include <string.h>

#include "tmp/c.h"
#include "tmp/postgres.h"
#include "tmp/libpq.h"
#include "tmp/libpq-fs.h"
#include "catalog/pg_lobj.h"

#include "utils/log.h"

#include "array.h"

extern char	*_array_mktemp();

RcsId("$Header: /home/postgres/mao/sequoia/retreat/RCS/array.c,v 1.6 1993/01/11 01:58:52 mao Exp $");

#define SPACE(c)	((c) == ' ' || (c) == '\t')

/*
 *  ARRAY_in -- Read an array spec.
 *
 *	The format of the input string is
 *
 *	    "<large object name>, <format>, <elt size>, [<d1>, ..., <dn>]"
 */

ARRAY *
ARRAY_in(s)
	char *s;
{
	ARRAY *v;
	char *comma;
	char *name, *format, *eltsize, *dims;
	char *save_s;
	int ndims;
	int *dimv;
	int nbytes;

	if (s == (char *) NULL)
		return ((ARRAY *) NULL);

	save_s = s;

	name = s;
	while (SPACE(*name))
		name++;

	if ((comma = index(s, ',')) == (char *) NULL)
		elog(WARN, "bad array spec '%s'", save_s);
	s = comma + 1;
	do
		*comma-- = '\0';
	while (SPACE(*comma));

	if (FilenameToOID(name) == InvalidObjectId)
		elog(WARN, "Large object '%s' does not exist", name);

	format = s;
	while (SPACE(*format))
		format++;

	if ((comma = index(s, ',')) == (char *) NULL)
		elog(WARN, "bad array spec '%s'", save_s);
	s = comma + 1;
	do
		*comma-- = '\0';
	while (SPACE(*comma));

	eltsize = s;
	while (SPACE(*eltsize))
		eltsize++;

	if ((comma = index(s, ',')) == (char *) NULL)
		elog(WARN, "bad array spec '%s'", save_s);
	s = comma + 1;
	do
		*comma-- = '\0';
	while (SPACE(*comma));

	dims = s;
	while (SPACE(*dims))
		dims++;

	if (*dims != '[')
		elog(WARN, "no dimension spec in array string '%s'",
			save_s);

	if ((s = index(dims, ']')) == (char *) NULL)
		elog(WARN, "unterminated dimension spec in array string '%s'",
			save_s);

	*dims++ = '\0';
	*s = '\0';

	ndims = 1;
	for (s = dims; *s != '\0'; s++)
		if (*s == ',')
			ndims++;

	nbytes = sizeof(ARRAY) + (strlen(name) + 1) + (ndims * sizeof(int));

	v = (ARRAY *) palloc(nbytes);
	v->arr_varlen = nbytes;
	sprintf(v->arr_format, format);

	if ((v->arr_eltsize = atoi(eltsize)) <= 0)
		elog(WARN, "array element size must be positive");

	v->arr_ndims = ndims;

	dimv = ARR_DIMS(v);

	s = dims;
	while (--ndims >= 0) {
		while (SPACE(*s))
			s++;
		dims = s;
		if ((comma = index(s, ',')) != (char *) NULL) {
			*comma = '\0';
			s = comma + 1;
		}
		if ((*dimv++ = atoi(dims)) <= 0)
			elog(WARN, "array dimensions must be positive");
	}

	strcpy(ARR_NAME(v), name);

	return (v);
}

char *
ARRAY_out(v)
	ARRAY *v;
{
	char *p, *save_p;
	int i;
	int nbytes;
	int *dimv;

	if (v == (ARRAY *) NULL)
		return ((char *) NULL);

	/* get a wide string to print to */
	nbytes = v->arr_varlen + (4 * v->arr_ndims) + 20;

	save_p = p = (char *) palloc(nbytes);

	sprintf(p, "%s, %.8s, %d, [", ARR_NAME(v), v->arr_format,
		v->arr_eltsize);

	dimv = ARR_DIMS(v);
	for (i = 0; i < v->arr_ndims; i++) {
		p += strlen(p);
		sprintf(p, "%s%d", (i > 0 ? ", " : ""), dimv[i]);
	}

	(void) strcat(p, "]");

	return (save_p);
}

text *
arrayname(v)
	ARRAY *v;
{
	int nbytes;
	text *p;
	int namelen;

	if (v == (ARRAY *) NULL)
		return ((text *) NULL);

	namelen = strlen(ARR_NAME(v));
	nbytes = sizeof(VARSIZE(p)) + namelen;
	p = (text *) palloc(nbytes);
	VARSIZE(p) = nbytes;
	bcopy(ARR_NAME(v), VARDATA(p), namelen);

	return (p);
}

int
arrayndims(v)
	ARRAY *v;
{
	if (v == (ARRAY *) NULL)
		return (0);

	return (v->arr_ndims);
}

/*
 *  arrayformat() -- Return the format string for this array.
 *
 *	This routine actually returns a char8, but that's a Sequoia type,
 *	not a type the Postgres backend knows about.
 */

char *
arrayformat(v)
	ARRAY *v;
{
	char *res;

	if (v == (ARRAY *) NULL)
		return ((char *) NULL);

	res = (char *) palloc(8);
	strncpy(res, &(v->arr_format[0]), 8);

	return (res);
}

/*
 *  arrayclip() -- Clip an array to a bounding box, producing a new array.
 *
 *	This routine takes an array and a bounding box, creates a new
 *	large object, fills it with the appropriate bytes, and returns
 *	it.  The additional format parameter is hacked for the demo.
 *	We originally planned to have a table of array formats, and to
 *	dispatch through the table in the same way that GEOLOC does,
 *	when we wanted to do things like clip and convert formats.  I
 *	didn't have time to finish that (and there's some serious overhead
 *	in creating a bunch of temporary large objects), so right now,
 *	arrayclip understands two formats:  avhrr and pgm.  Pgm format
 *	is viewable using xv, and avhrr is the native AVHRR biweekly
 *	composite format off the NOAA CDs.
 *
 *	The fmt arg is actually a char8.
 */

ARRAY *
arrayclip(a, fmt, ul_x, ul_y, lr_x, lr_y)
	ARRAY *a;
	char *fmt;
	int ul_x;
	int ul_y;
	int lr_x;
	int lr_y;
{
	ARRAY *newa;
	char *oldfmt;
	char *newname;
	char *name;
	int arrsize;
	int *dims;

	if (strcmp(fmt, "avhrr") != 0 && strcmp(fmt, "pgm") != 0)
		elog(WARN, "can't create array in format '%.8s'", fmt);

	oldfmt = &(a->arr_format[0]);
	if (strcmp(oldfmt, "avhrr") != 0 && strcmp(oldfmt, "pgm") != 0)
		elog(WARN, "can't clip array in format '%.8s'", oldfmt);

	if (a->arr_ndims != 2)
		elog(WARN, "can't clip non-2d array to a 2d rectangle");

	/* be sure the coords are in bounds */
	dims = ARR_DIMS(a);
	if (ul_x < 0)
		ul_x = 0;
	if (ul_y < 0)
		ul_y = 0;
	if (lr_x > dims[0])
		lr_x = dims[0];
	if (lr_y > dims[1])
		lr_y = dims[1];

	if (lr_x < ul_x || lr_y < ul_y)
		elog(WARN, "clip rectangle corners are inverted: (%d, %d), (%d, %d)",
			ul_x, ul_y, lr_x, lr_y);

	newname = _array_mktemp();
	arrsize = sizeof(ARRAY) + (strlen(newname) + 1)
		  + (a->arr_ndims * sizeof(int));

	newa = (ARRAY *) palloc(arrsize);

	newa->arr_varlen = arrsize;
	if (strcmp(fmt, "same") == 0)
		strncpy(&(newa->arr_format[0]), &(a->arr_format[0]), 8);
	else
		strncpy(&(newa->arr_format[0]), fmt, 8);

	newa->arr_eltsize = a->arr_eltsize;
	newa->arr_ndims = a->arr_ndims;

	/* set up the new dimensions */
	dims = ARR_DIMS(newa);
	dims[0] = lr_x - ul_x;
	dims[1] = lr_y - ul_y;
	name = ARR_NAME(newa);
	strcpy(name, newname);

	/*
	 *  Okay, the array value is all set up.  Now we need to open the
	 *  large object containing the old array, and create the new one
	 *  containing the clipped array.
	 */

	_array_doclip(a, newa, ul_x, ul_y);

	/*
	 *  This is necessary because LOcreat() inserts nulls into the
	 *  name it parses in order to break the string into path
	 *  components, and doesn't put them back again afterwards.
	 */

	strcpy(name, newname);

	return (newa);
}

/*
 *  _array_doclip() -- Do the copying from the old array to the new one.
 *
 *	We are given the two array objects and the logical origin for
 *	the clip in the old array.  We create the new large object and
 *	fill it in.
 */

_array_doclip(src, dest, x, y)
	ARRAY *src;
	ARRAY *dest;
	int x, y;
{
	unsigned char max;
	int *dims;
	int srcfd, destfd;
	int srcloc, destloc;
	int step;
	int destpgm, srcpgm;
	int bufbytes, nbytes;
	int nwritten;
	int lno;
	struct varlena *v;
	char *s;
	int i;

	/* for the demo, we use Unix large objects -- smaller and faster */
	if ((srcfd = LOopen(ARR_NAME(src), O_RDONLY)) < 0)
		elog(WARN, "cannot open array %s", ARR_NAME(src));

	if ((destfd = LOcreat(ARR_NAME(dest), O_WRONLY, Unix)) < 0)
		elog(WARN, "cannot create array %s", ARR_NAME(dest));

	/* step over the pgm header or the ms/dos header */
	if (strcmp(&(src->arr_format[0]), "pgm") == 0) {
		srcpgm = 1;
		srcloc = 28;
	} else {
		srcpgm = 0;
		srcloc = 491;
	}

	if (strcmp(&(dest->arr_format[0]), "pgm") == 0) {
		destpgm = 1;
		if (LOlseek(destfd, 28, L_SET) < 0)
			elog(WARN, "can't seek on clip output array");
	} else {
		destpgm = 0;
		if (LOlseek(destfd, 491, L_SET) < 0)
			elog(WARN, "can't seek on clip output array");
	}

	/*
	 *  AVHRR image lines are rounded up to the nearest 512 bytes
	 *  in order to avoid offending some MS/DOS program.
	 */

	dims = ARR_DIMS(src);
	if (srcpgm)
		step = *dims;
	else
		step = (((*dims - 1) / 512) + 1) * 512;

	/* start of first clipped line in the output */
	srcloc += (step * y) + x;

	/* figure out how much we need to write to be 512-byte aligned */
	dims = ARR_DIMS(dest);

	if (destpgm)
		bufbytes = *dims;
	else
		bufbytes = (((*dims - 1) / 512) + 1) * 512;

	nbytes = *dims;
	max = 0;

	/* do it */
	for (lno = 0; lno < dims[1]; lno++) {

		if (LOlseek(srcfd, srcloc, L_SET) < 0)
			elog(WARN, "can't seek on array to be clipped");

		srcloc += step;
		v = (struct varlena *) LOread(srcfd, bufbytes);

		if ((nbytes = VARSIZE(v) - 4) < bufbytes) {

			/* okay for the last line to be partial */
			if (nbytes > 0 && lno == *dims - 1) {

				if (destpgm) {
					s = VARDATA(v);
					for (i = 1; i < nbytes; i++, s++) {
						if ((unsigned char) *s++ > max)
							max = (unsigned char) *s++;
					}
				}
				if (!destpgm)
					bzero(&(v->vl_dat[nbytes]),
						bufbytes - nbytes);
				if ((nwritten = LOwrite(destfd, v)) < nbytes)
					elog(WARN, "array write failed: %d, %d",
						nbytes, nwritten);
			} else
				elog(WARN, "array read failed");
		} else {

			if (destpgm && max < 255) {
				s = VARDATA(v);
				for (i = 0; i < nbytes; i++, s++) {
					if ((unsigned char) *s > max)
						max = (unsigned char) *s;
				}
			}

			if (!destpgm)
				bzero(&(v->vl_dat[nbytes]), bufbytes - nbytes);
			if ((nwritten = LOwrite(destfd, v)) < nbytes)
				elog(WARN, "array write failed: %d, %d",
					nbytes, nwritten);
		}

		pfree (v);
	}

	if (destpgm) {

		if (LOlseek(destfd, 0, L_SET) < 0)
			elog(WARN, "can't seek to start of pgm array");

		/* my, this is pretty... */
		v = (struct varlena *) palloc(30);
		sprintf(VARDATA(v), "P5 \n%7d %7d \n%3d\n",
			bufbytes, dims[1], max);

		nbytes = strlen(VARDATA(v));
		VARSIZE(v) = 4 + strlen(VARDATA(v));

		if ((nwritten = LOwrite(destfd, v)) != nbytes)
			elog(WARN, "can't write pgm array header");

		pfree (v);
	}

	(void) LOclose(srcfd);
	(void) LOclose(destfd);
}

char *
_array_mktemp()
{
	char *p;

	p = (char *) palloc(30);
	sprintf(p, "/tmp/clip.%d", newoid());

	return (p);
}
