/*****************************************************************************
******************************************************************************
**<AUTO>
**
** FILE:	resolve.c
**
**<HTML>
**	This file contains C functions to resolve an object.
**</HTML>
**</AUTO>
**
**
** ENVIRONMENT:
**	ANSI C.
**
******************************************************************************
******************************************************************************
*/
#include <dervish.h>
#include <astrotools.h>
#include "taCalibObj.h"
#include "arObjectType.h"
#include "arObjectStatus.h"


/********************************************************************/
/* qsort helper function. Final list is ordered from smallest to largest */
static int cmpLambda (const void *d1, const void *d2)
{
  TA_CALIB_OBJ *f1, *f2;

  f1 = (TA_CALIB_OBJ *)d1;
  f2 = (TA_CALIB_OBJ *)d2;
  if (f1->lambda < f2->lambda) return -1;
  if (f1->lambda > f2->lambda) return 1;
  return 0;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taRowBorderMatch
**
**<HTML>
**	Match a single object to an array of objects in a border region from
**	the previous row.  The border array coordinates are assumed to be
**	in pixel coordinates for the object's field.
**<p>
**	Returns the index of the closest matching object in the array, or
**	-1 if no match is found.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
taRowBorderMatch(TA_CALIB_OBJ *obj,  /* MOD: object to match against border */
		 const TA_BORDER_OBJ *border, /* IN:  array of previously
					       * resolved border objects */
		 int nBorder,          /* IN:  number of border objects */
		 double matchRadius    /* IN:  matching radius (pixels) */)
{
  double negMatchRadius, matchRadius2;
  double rowDiff, colDiff, distance2, closestDistance2;
  int closestIndex, i;

  /* Verify inputs */
  shAssert(obj != NULL);
  shAssert(border != NULL);
  shAssert(nBorder >= 0);
  shAssert(matchRadius > 0);

  /* Return if no objects in border */
  if (nBorder == 0) return -1;

  /* Set the lower end of the matching range */
  negMatchRadius = -matchRadius;
  matchRadius2 = matchRadius * matchRadius;

  /* Now check each object. */
  closestIndex = -1;
  closestDistance2 = 1000. * matchRadius2;
  for (i = 0; i < nBorder; i++)
    {
      /* Do we match? */
      rowDiff = obj->objc_rowc.val - border[i].lambda;
      if (rowDiff > matchRadius || rowDiff < negMatchRadius) continue;
      colDiff = obj->objc_colc.val - border[i].eta;
      if (colDiff > matchRadius || colDiff < negMatchRadius) continue;
      distance2 = rowDiff * rowDiff + colDiff * colDiff;
      if (distance2 <= matchRadius2)
	{
	  /* A match.  Save if its the closest matching one thus far.*/
	  if (distance2 < closestDistance2)
	    {
	      closestIndex = i;
	      closestDistance2 = distance2;
	    }
	}
    }
      
  /* Return the index of the closest match */
  return closestIndex;
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taBorderMatch
**
**<HTML>
**	Match a single object to an array of objects in a border region.
**	The border region array may be ordered by increasing lambda to speed
**	the search.  The starting point for the search may be passed in,
**	allowing a guess to be given based on the last call to the routine.
**<p>
**	Returns the index of the closest matching object in the array, or
**	-1 if no match is found.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
taBorderMatch(TA_CALIB_OBJ *obj,  /* MOD: object to match against border */
	      const TA_BORDER_OBJ *border, /* IN:  array of previously resolved
					   *      border objects */
	      int nBorder,          /* IN:  number of border objects */
	      int ordered,          /* IN:  1=ordered by increasing lambda,
				     *      0=unordered */
	      double matchRadius,   /* IN:  matching radius (deg) */
	      int *startSearch      /* MOD: start searching from this index
				     *      in the array.  Returned with
				     *      value where this search started.
				     *      Must be NULL for unordered
				     *      searches. */)
{
  double negMatchRadius, matchRadius2, lambdaMin, lambdaMax, cosLambda;
  double lambdaDiff, etaDiff, distance2, closestDistance2;
  int closestIndex, startIdx, i;

  /* Verify inputs */
  shAssert(obj != NULL);
  shAssert(border != NULL);
  shAssert(nBorder >= 0);
  shAssert(matchRadius > 0);
  shAssert(ordered == 0 || ordered == 1);
  if (ordered == 0) shAssert(startSearch == NULL);
  if (startSearch != NULL)
    shAssert(*startSearch >= 0 && *startSearch <= nBorder);

  /* Return if no objects in border */
  if (nBorder == 0) return -1;

  /* Set the lower end of the matching range */
  negMatchRadius = -matchRadius;
  matchRadius2 = matchRadius * matchRadius;
  lambdaMin = obj->lambda - matchRadius;

  /* If starting point specified, decrement, then increment, start
   * index until we hit minimum lambda for this search.  Else just start from
   * the beginning. */
  if (startSearch != NULL)
    {
      while (*startSearch > 0)
	{
	  if (border[*startSearch-1].lambda < lambdaMin) break;
	  *startSearch--;
	}
      while (*startSearch < nBorder)
	{
	  if (border[*startSearch].lambda >= lambdaMin) break;
	  *startSearch++;
	}
      startIdx = *startSearch;
    }
  else
    startIdx = 0;

  /* Return if all objects in the previous array are at a smaller lambda. */
  if (startIdx == nBorder) return -1;

  /* Now check each object until we're beyond the match radius or we
   * run out of the array. */
  closestIndex = -1;
  closestDistance2 = 1000. * matchRadius2;
  lambdaMax = obj->lambda + matchRadius;
  cosLambda = cos(obj->lambda * at_deg2Rad);
  for (i = startIdx; i < nBorder; i++)
    {
      /* Break out or skip if we're beyond the match radius */
      if (ordered)
	{
	  if (border[i].lambda > lambdaMax) break;
	}
      else
	{
	  if (border[i].lambda < lambdaMin || border[i].lambda > lambdaMax)
	    continue;
	}

      /* Do we match? */
      etaDiff = obj->eta - border[i].eta;
      etaDiff *= cosLambda;
      if (etaDiff > matchRadius || etaDiff < negMatchRadius) continue;
      lambdaDiff = obj->lambda - border[i].lambda;
      distance2 = lambdaDiff * lambdaDiff + etaDiff * etaDiff;
      if (distance2 <= matchRadius2)
	{
	  /* A match.  Save if its the closest matching one thus far.*/
	  if (distance2 < closestDistance2)
	    {
	      closestIndex = i;
	      closestDistance2 = distance2;
	    }
	}
    }
      
  /* Return the index of the closest match */
  return closestIndex;
}


/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taObjectStatusSet
**
**<HTML>
**	Set the SET, GOOD, and OK_RUN flags as appropriate for an object.
**	A border resolution is performed with objects at the border of the
**	previous field (where the border width is twice the matching radius),
**	assuring than any matched pairs in the border region between fields
**	has exactly one OK_RUN object.  Returns 1 if the object is in the
**	border region with the next field, else 0.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
taObjectStatusSet(TA_CALIB_OBJ *obj  /* MOD: calibrated object */,
		  int rowsPerField   /* IN:  non-overlap binned rows/field*/,
		  int overlapRows    /* IN:  overlap binned rows */,
		  double matchRadius /* IN:  match radius (pixels) */,
		  const TA_BORDER_OBJ *border /* IN: objects from previous
					       *     field in border region
					       *     with this field */,
		  int nBorder        /* IN:  number of border objects in
				      *      previous field */)
{
  double okMin, okMax, row;
  int match;

  /* Set the SET flag, indicating its been through this step.*/
  obj->status = AR_OBJECT_STATUS_SET;

  /* Determine if object is GOOD.  Done if its not. */
  if (! taGood(obj->objc_flags, obj->nchild)) return 0;
  obj->status |= AR_OBJECT_STATUS_GOOD;

  /* We divide the overlap regions into two, and assign
   * half of each overlap region to the adjacent fields, and half to the
   * primary region of this field.  We then consider an object to belong to a
   * given field (OK_RUN) if it falls between those limits. */
  okMin = overlapRows / 2.;
  okMax = rowsPerField + overlapRows / 2.;
  row = obj->objc_rowc.val;
  if (row >= okMin && row < okMax)
    obj->status |= AR_OBJECT_STATUS_OK_RUN;

  /* Match only stars, galaxies, and unknown */
  if (obj->objc_type != AR_OBJECT_TYPE_STAR &&
      obj->objc_type != AR_OBJECT_TYPE_GALAXY &&
      obj->objc_type != AR_OBJECT_TYPE_UNK)
    return 0;

  /* Does this object lie within the border region of the previous field? */
  if (row > okMin - matchRadius && row < okMin + matchRadius)
    {
      /* Yes.  Match it against the border objects from the previous field.
       * Whether it matches one of them or not, it can't match an object in
       * the next field, so return 0.
       */
      match = taRowBorderMatch(obj, border, nBorder, matchRadius);
      if (match == -1) return 0;

      /* We have a match.
       * Resolve the pair, so that only one object in the pair is set to
       * OK_RUN.  Since an object in this field can have at most one match,
       * while objects in the previous field can have more than one match,
       * we keep the OK_RUNedness of the object in the previous field and
       * adjust the object in this field accordingly.  More importantly,
       * this is necessary also because we can't go back and change previously
       * set objects.
       */
      if ((obj->status & AR_OBJECT_STATUS_OK_RUN) ==
	  (border[match].status & AR_OBJECT_STATUS_OK_RUN))
	{
	  if (border[match].status & AR_OBJECT_STATUS_OK_RUN)
	    obj->status &= ~AR_OBJECT_STATUS_OK_RUN;
	  else
	    obj->status |= AR_OBJECT_STATUS_OK_RUN;
	}
      return 0;
    }

  /* If this object lies within the border region with the next field,
   * return 1; else return 0. */
  return (row > okMax - matchRadius && row < okMax + matchRadius);
}

/*============================================================================
**<AUTO EXTRACT>
**
** ROUTINE: taSegmentResolve
**
**<HTML>
**	Resolve all objects within a segment, where the segment geometry
**	is fully specified by the input TA_GEOMETRY structure.
**	databases.  All objects whose flag OK_RUN is set and which lie within
**	the range of mu are considered to belong to the segment; if there are
**	abutting PRIMARY segments along the mu border then we must
**	guarantee a unique and complete set of resolved obects in the border
**	region (of radius <em>matchRadius</em>) where the segments meet.
**	This is done by removing from the segment any object with a matching
**	object which belongs to an abutting PRIMARY segment, and adding to the
**	segment any object outside the mu limits but within
**	<em>matchRadius</em> which has a matching object an
**	abutting PRIMARY segments but which does not
**	belong to that segment.  All objects belonging to a segment have
**	their RESOLVED flag set, as well as the PSEGMENT flag if the segment
**	is a PRIMARY segment.  Additionally, if
**	the object belongs to the first field in the segment, then its
**	FIRST_FIELD flag is set to distinguish objects lying in
**	fields shared by two segments.
**	<P>
**	If the object lies
**	within the primary range of great circle latitude for the scanline,
**	then its OK_SCANLINE flag is set.  A resolution process similar to
**	that performed in mu is also done here to guarantee a set of unique
**	and complete detections in the border region between PRIMARY segments
**	in adjacent scanlines.  For objects whose OK_SCANLINE flag is set,
**	if its lies within the primary range of survey latitude for the
**	stripe, then its OK_STRIPE flag is set.  For objects whose OK_STRIPE
**	flag is set, if it lies within the primary range of survey longitude
**	for the stripe then its PRIMARY flag is set.  Again, a resolution
**	process
**	is performed to guarantee a set of unique and complete detections
**	around the border region between stripes (no latitude cut is applied
**	to stripes in the southern survey).  If the PRIMARY flag is not
**	set, then the SECONDARY flag is (the PRIMARY or SECONDARY flag is set
**	for objects of class STAR, GALAXY, UNK, and SKY only).
**	<P>
**	For PRIMARY segments, it is assumed that there are no overlapping
**	PRIMARY segments within the same scanline already defined in the
**	database.
**	<P>
**	It is assumed that segments within a given chunk are processed in
**	order of increasing scanline number;  thus, we need to check
**	matching objects in adjacent segments only along the lower nu
**	border, and not along the upper nu border.  However, we want to
**	know if this object could match an object along the upper nu border
**	or either mu border to facilitate processing an entire chunk.
**	To accomodate that, the routine returns 1 if the object is matchable
**	(OK_RUN, STAR || GALAXY || UNKNOWN), else 0.
**</HTML>
**
**</AUTO>
**============================================================================
*/
int
taSegmentResolve(TA_CALIB_OBJ *obj  /* MOD: calibrated object */,
		 int firstField,    /* IN: 1=object in first field
				     *     in segments, 0=not */
		 const TA_GEOMETRY *geometry, /* IN: survey geometry */
		 const TA_BORDER_OBJ *muBorder[2],
		 /* IN: objects from previous/next segment in border region
		  *      with this field */
		 int nMuBorder[2], /* IN:  number of border objects in
				    *      previous/next segment */
		 const TA_BORDER_OBJ *nuBorder, /* IN: objects from previous
						 *    scanline in border region
						 *    with this field */
		 int nNuBorder,     /* IN:  number of border objects in
				     *      previous scanline */
		 const TA_BORDER_OBJ *etaBorder[2],
		 /* IN: objects from previous/next chunk in border region
		  *      with this chunk */
		 int nEtaBorder[2] /* IN:  number of border objects in
				    *      previous/next chunk */)
{
  int i, sgu, inSurvey, ok, match;
  double mu, nu, matchRadius;

  /* Skip objects which have already been resolved */
  shAssert(! (obj->status & AR_OBJECT_STATUS_RESOLVED));

  /* Skip objects which are not OK_RUN. */
  if (! (obj->status & AR_OBJECT_STATUS_OK_RUN)) return 0;

  /* Only objects of class STAR, GALAXY, UNK, and SKY will be classed
   * as either SECONDARY or PRIMARY.  Only objects of class STAR,
   * GALAXY, or UNK are matched.
   */
  sgu = (obj->objc_type == AR_OBJECT_TYPE_STAR ||
	 obj->objc_type == AR_OBJECT_TYPE_GALAXY ||
	 obj->objc_type == AR_OBJECT_TYPE_UNK);
  inSurvey = (sgu || obj->objc_type == AR_OBJECT_TYPE_SKY);

  /* Get great circle coordinates. */
  atEqToGC(obj->ra, obj->dec, &mu, &nu, geometry->stripeNode,
	   geometry->stripeIncl);

	if (mu < geometry->muLimits[0] - 30.) {
		mu += 360.0;
	} 
  /* Does this fall within the segment's limits in mu?
   * If it abuts another primary segment, then we need to guarantee
   * complete and unique detections within a region of radius
   * "matchRadius" around the border between the two.  Only objects of class
   * STAR, GALAXY, or UNK are matched, so we need check borders for those.
   */
  matchRadius = geometry->matchRadius * at_asec2Deg;
  ok = (mu >= geometry->muLimits[0] && mu < geometry->muLimits[1]);
  if (sgu)
    for (i = 0; i < 2; i++)
      if (nMuBorder[i] > 0)
	if (mu > geometry->muLimits[i] - matchRadius &&
	    mu < geometry->muLimits[i] + matchRadius)
	  {
	    shAssert(muBorder[i] != NULL);
	    match = taBorderMatch(obj, muBorder[i], nMuBorder[i], 0,
				  matchRadius, NULL);
	    if (match >= 0)
	      {
		if (muBorder[i][match].status & AR_OBJECT_STATUS_PSEGMENT)
		  ok = 0;
		else
		  ok = 1;
	      }
	    break;
	  }

  /* If outside mu range, then not in the segment. */
  if (! ok) return 0;

  /* This object belongs to this segment.  Flag accordingly. */
  obj->status |= (AR_OBJECT_STATUS_RESOLVED | AR_OBJECT_STATUS_PSEGMENT);
  if (firstField) obj->status |= AR_OBJECT_STATUS_FIRST_FIELD;

  /* Is this object in primary nu range for this scanline?
   * Within a given chunk, we require that segments be processed in order
   * of increasing scanline number, so we need check only for matches along
   * the lower nu border.
   */
  ok = (nu >= geometry->nuLimits[0] && nu < geometry->nuLimits[1]);
  if (sgu && nNuBorder > 0)
    if (nu > geometry->nuLimits[0] - matchRadius &&
	nu < geometry->nuLimits[0] + matchRadius)
      {
	match = taBorderMatch(obj, nuBorder, nNuBorder, 0, matchRadius, NULL);
	if (match >= 0)
	  {
	    if (nuBorder[match].status & AR_OBJECT_STATUS_OK_SCANLINE)
	      ok = 0;
	    else
	      ok = 1;
	  }
      }
  if (! ok)
    {
      if (inSurvey) obj->status |= AR_OBJECT_STATUS_SECONDARY;
      return sgu;
    }
  obj->status |= AR_OBJECT_STATUS_OK_SCANLINE;
     
  /* Is this object in primary eta range for this stave? */
  if (geometry->stripe > 45)
    ok = 1;
  else
    {
      ok = (obj->eta >= geometry->etaLimits[0] &&
	    obj->eta < geometry->etaLimits[1]);
      if (sgu)
	for (i = 0; i < 2; i++)
	  if (nEtaBorder[i] > 0)
	    if (obj->eta > geometry->etaLimits[i] - matchRadius &&
		obj->eta < geometry->etaLimits[i] + matchRadius)
	      {
		match = taBorderMatch(obj, etaBorder[i], nEtaBorder[i], 0,
				      matchRadius, NULL);
		if (match >= 0)
		  {
		    if (etaBorder[i][match].status&AR_OBJECT_STATUS_OK_STRIPE)
		      ok = 0;
		    else
		      ok = 1;
		  }
		break;
	      }
    }
  if (! ok)
    {
      if (inSurvey) obj->status |= AR_OBJECT_STATUS_SECONDARY;
      return sgu;
    }
  obj->status |= AR_OBJECT_STATUS_OK_STRIPE;

  /* Is this object in the lambda range for the stripe? */
  if (inSurvey)
    {
      if (obj->lambda >= geometry->lambdaLimits[0] &&
	  obj->lambda < geometry->lambdaLimits[1])
	obj->status |= AR_OBJECT_STATUS_PRIMARY;
      else
	obj->status |= AR_OBJECT_STATUS_SECONDARY;
    }
  return sgu;
}
