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

#include "ip.h"

#define	MAXSR		20		/* max star radius, ie, pix to noise */
#define	MINSR		1		/* min star radius */
#define	SKYR		2		/* measure sky @ starr * SKYR */
#define	MINSS		2		/* min star separation */
#define	NWALK		8		/* n pixels surrounding a pixel */
#define	NCONN		3		/* min connected pixels to qualify */
#define	PKP		5		/* dips must be > this % of peak-med */

static double gauss2d (Gaussian *hgp, Gaussian *vgp, int x, int y);

/* extract some "stars" from fip, no guarantees but fast.
 * return malloced arrays of centroided locations sorted by brightest pixel.
 * N.B. caller must always free *xpp and *ypp if return >= 0.
 */
int
quickStars (FImage *fip, ImStats *isp, int border, int burnt, double nsd,
double **xpp, double **ypp)
{
#define	IFDP	if (dfp && abs(x-dx)<=dr && abs(y-dy)<=dr)
	CamPix *ip, *ip0 = (CamPix *)fip->image;
	int bsr = border+MAXSR;		/* handy border-MAXSR */
	int w = fip->sw;		/* handy */
	int h = fip->sh;		/* handy */
	int walk[NWALK];		/* fast walk about */
	int *br;			/* temp array to sort by brightness */
	int dx, dy, dr;			/* dump region */
	FILE *dfp;			/* dump file, if any */
	double *xp, *yp;
	int x, y;
	int nis;

	/* prime the arrays */
	xp = (double *) malloc (10*sizeof(double));
	yp = (double *) malloc (10*sizeof(double));
	br = (int *)    malloc (10*sizeof(int));
	if (!xp || !yp || !br) {
	    if (xp) free (xp);
	    if (yp) free (yp);
	    if (br) free (br);
	    return (-1);
	}
	nis = 0;

	/* set up walk-about */
	walk[0] =  1;
	walk[1] =  1 - w;
	walk[2] =    - w;
	walk[3] = -1 - w;
	walk[4] = -1;
	walk[5] = -1 + w;
	walk[6] =      w;
	walk[7] =  1 + w;

	/* check for dump file */
	dfp = fopen ("x.dump", "r");
	if (dfp) {
	    char fname[128];
	    int ok = fscanf (dfp, "%s %d %d %d", fname, &dx, &dy, &dr) == 4;
	    fclose (dfp);
	    dfp = ok ? fopen (fname, "w") : NULL;
	}

	/* scan inside border+MAXSR for local maxima */
	ip = ip0 + bsr*w + bsr;
	for (y = bsr; y < h-bsr; y++) {
	    for (x = bsr; x < w-bsr; x++) {
		int p0 = (int)*ip;	/* current central pixel */
		double cx, cy;		/* centroided position */
		int sky, std, cut;	/* sky, 1 sigma and cutoff */
		int rmin, rmax;		/* min and max of a given ring */
		int lmax;		/* rmax in last radii */
		int starr;		/* radius of star edge */
		int mindip;		/* minimum dip before peaking */
		int mass;		/* star brightness */
		int ncon;
		int i, r;

		/* immediately discard burnt pixels or ones below median */
		mindip = PKP*(p0 - isp->median);
		if (mindip < 0) {
		    IFDP fprintf (dfp, "%4d %4d: %5d < median %5d\n", x, y,
							    p0, isp->median);
		    goto no;
		}
		if (p0 >= burnt) {
		    IFDP fprintf (dfp, "%4d %4d: burnt: %5d >= %5d\n", x, y,
								    p0, burnt);
		    goto no;
		}

		/* init ranges */
		lmax = p0;
		starr = 0;

		/* check successively greater rings. Found star outter edge
		 * when max has decreased and increased again.
		 */
		for (r = 1; r < MAXSR; r++) {

		    /* get stats for ring with radius r */
		    ringStats (ip, w, r, NULL, &rmax, NULL);

		    /* reject any burnt pixels */
		    if (rmax >= burnt) {
			IFDP fprintf (dfp, "%4d %4d: ring %d burnt %5d\n",
								x, y, r, rmax);
			goto no;
		    }

		    /* found star edge when max first increases.
		     * allow for a little benign bumpiness near the top
		     */
		    if (rmax > lmax && 100*(p0-lmax) > mindip) {
			starr = r - 1;	/* stats are for last radius */
			break;
		    }

		    /* p0 is not a peak if neighbors are brighter */
		    if (rmax > p0) {
			IFDP fprintf (dfp,
			    "%4d %4d: %5d but %5d in ring @ r= %d\n",
							x, y, p0, rmax, r);
			goto no;
		    }

		    /* save last ring extreme */
		    lmax = rmax;
		}

		if (r == MAXSR) {
		    IFDP fprintf (dfp, "%4d %4d: starr %d too large\n",
								x, y, r);
		    goto no;
		}
		if (starr < MINSR) {
		    IFDP fprintf (dfp, "%4d %4d: starr %d too small\n",
								x, y, starr);
		    goto no;
		}

		/* estimate sky and noise and set cutoff */
		r = SKYR*starr;
		if (r > MAXSR)
		    r = MAXSR;
		ringStats (ip, w, r, &rmin, NULL, &sky);
		std = (sky - rmin)/3;	/* ?? */
		cut = sky + (int)(nsd*std);
		if (p0 < cut) {
		    IFDP fprintf (dfp,
				"%4d %4d: %5d too dim r= %d sky= %d std= %d\n",
						    x, y, p0, starr, sky, std);
		    goto no;
		}

		/* want several connected neighbors above cutoff */
		ncon = 0;
		for (i = 0; i < NWALK+NCONN; i++)
		    if (ip[walk[i%NWALK]] >= cut) {
			if (++ncon == NCONN)
			    break;
		    } else
			ncon = 0;
		if (ncon < NCONN) {
		    IFDP fprintf (dfp,
			    "%4d %4d: only %d connected neighbors above %d\n",
							    x, y, ncon, cut);
		    goto no;
		}

		/* find centroided position */
		starCentroid (ip, w, starr, sky, &cx, &cy, &mass);
		cx += x;
		cy += y;

		/* must be a few pixels from any other */
		for (i = 0; i < nis; i++)
		    if (fabs(cx-xp[i]) < MINSS && fabs(cy-yp[i]) < MINSS) {
			IFDP fprintf (dfp,
				    "%4d %4d: %5d dup @ %4.0f %4.0f\n",
						x, y, p0, xp[i], yp[i]);
			goto no;
		    }

		/* Ok! insert in order of decreasing brightness */
		xp = (double *) realloc ((char *)xp, (nis+1)*sizeof(double));
		yp = (double *) realloc ((char *)yp, (nis+1)*sizeof(double));
		br = (int *)    realloc ((char *)br, (nis+1)*sizeof(int));
		if (!xp || !yp || !br) {
		    if (xp) free (xp);
		    if (yp) free (yp);
		    if (br) free (br);
		    return (-1);
		}
		for (i = nis; i > 0 && mass > br[i-1]; --i) {
		    br[i] = br[i-1];
		    xp[i] = xp[i-1];
		    yp[i] = yp[i-1];
		}
		br[i] = mass;
		xp[i] = cx;
		yp[i] = cy;

		IFDP fprintf (dfp,
		    "%4d %4d: OK %5d > %5d %6.1f %6.1f r= %d sky= %d std= %d\n",
				    x, y, p0, cut, cx, cy, starr, sky, std);

		/* inc number of stars found */
		nis++;

	      no:

		/* move to next pixel right */
		ip++;
	    }

	    /* move to start of next row */
	    ip += 2*bsr;
	}

	free ((char *)br);
	*xpp = xp;
	*ypp = yp;
	if (dfp)
	    fclose (dfp);
	return (nis);
}

/* given a region that presumably contains a single star, fill in sp
 * with best-fit 2d commensurate gaussian.
 * return 0 if ok, else -1
 * N.B. we assume rp has already been clamped.
 */
int
getStar (ImRegion *rp, Star *sp)
{
	CamPix *im = &rp->im[rp->ry*rp->iw + rp->rx];
	int wrap = rp->iw - rp->rw;
	int nr = rp->rw * rp->rh;
	double sum = 0, sum2 = 0;
	double v;
	int x, y;

	/* on guard */
	if (nr < 2) {
	    printf ("getStar called with region of 1\n");
	    return (-1);
	}

	/* find commensurate gaussians */
	if (gauss2fit (rp, &sp->hg, &sp->vg) < 0)
	    return (-1);

	/* position is with respect to corner of region */
	sp->x = rp->rx + sp->hg.m;
	sp->y = rp->ry + sp->vg.m;

	/* compare ideal to real to estimate noise */
	for (y = 0; y < rp->rh; y++) {
	    for (x = 0; x < rp->rw; x++) {
		double ideal = gauss2d (&sp->hg, &sp->vg, x, y);
		double real = (double)*im++;
		double err = ideal - real;
		sum += err;
		sum2 += sqr(err);
	    }
	    im += wrap;
	}

	/* error is STARSIGMA sigma noise */
	v = (sum2 - sum*sum/nr)/(nr-1);
	sp->err = v <= 0 ? 0 : STARSIGMA*sqrt(v);

	return (0);
}

/* find the magnitude of s1 wrt s0 and the error in the estimate.
 * gaussian volume taken to be proportional to height * sigmax * sigmay.
 * return 0 if ok, else -1 if numerical troubles.
 */
int
cmpStars (Star *s0, Star *s1, double *magp, double *errp)
{
	double v0, v1, r;

	/* magnitude is ratio of guassian volumes */
	v0 = s0->hg.A * s0->hg.s * s0->vg.s;
	v1 = s1->hg.A * s1->hg.s * s1->vg.s;
	if (v0 == 0 || (r = v1/v0) <= 0)
	    return (-1);
	*magp = -2.511886*log10(r);		/* + is dimmer */

	/* error = (log(largest ratio) - log(smallest ratio))/2.
	 *       = (log((largest ratio) / (smallest ratio)))/2.
	 * final /2 because we want to report as +/-
	 */
	v1 = (s1->hg.A + s1->err)/(s0->hg.A - s0->err);	/* largest */
	v0 = (s1->hg.A - s1->err)/(s0->hg.A + s0->err);	/* smallest */
	if (v0 == 0)
	    return (-1);
	*errp = (2.511886/2)*log10(fabs(v1/v0));

	/* made it */
	return (0);
}


/* compute value of 2d gaussian at [x,y] */
static double
gauss2d (Gaussian *hgp, Gaussian *vgp, int x, int y)
{
	return (hgp->B + hgp->A*exp(-.5*(sqr((x-hgp->m)/hgp->s)+
	                                 sqr((y-vgp->m)/vgp->s))));
}

/* For RCS Only -- Do Not Edit */
static char *rcsid[2] = {(char *)rcsid, "@(#) $RCSfile: stars.c,v $ $Date: 2002/01/04 19:28:23 $ $Revision: 1.6 $ $Name:  $"};
