/*****************************************************************************
******************************************************************************
**<AUTO>
**
** FILE:	tclResolve.cc
**
**<HTML>
**	This file contains TCL functions to resolve multiple Frames Pipeline
**	Runs into primary and secondary objects.
**</HTML>
**</AUTO>
**
**
** ENVIRONMENT:
**	C++.
**
** REQUIRED PRODUCTS:
**	photoSch
**
******************************************************************************
******************************************************************************
*/

#include <arProduct.h>
#include <arParamValue.h>
#include <arCCDProgram.h>
#include <arProgram.h>
#include <arRun.h>
#include <arTrans.h>
#include <arAstromCalib.h>
#include <arPSCalib.h>
#include <arObject.h>
#include <arObjectList.h>
#include <arFramesPipeRun.h>
#include <arFinalPhotoCalib.h>
#include <arSegment.h>
#include <arChunk.h>
#include "tsResolve.h"
#include "tsResolveFuncs.h"
#include "tsRange.h"
#include <dervish.h>
#include <astrotools.h>
extern "C" {
#include <slalib.h>
}
#include <taCosmic.h>

#define MAX_RANGES 100

typedef struct tsSegment1
{
  int run;
  int camCol;
  int rerun;
  int scanline;
  int field0;
  int nFields;
  int startMu;
  int endMu;
  TSSEGMENTSTATUS status;
} TSSEGMENT1;

/*============================================================================
**
** ROUTINE: tsSegmentCreate
**
**	Create a new segment and append it to the chain of segments for the
**	run.  Returns SH_SUCCESS if successful, else SH_GENERIC_ERROR on
**	an error.
**
**============================================================================
*/
RET_CODE
tsSegmentCreate(TSRUN *run,
		int field0,
		int nFields,
		TSSEGMENTSTATUS status,
		int startMu,
		int endMu)
{
  TSSEGMENT *segment;
  if ((segment = (TSSEGMENT*) shMalloc(sizeof(TSSEGMENT)))
      == NULL)
    {
      shErrStackPush("tsSegmentCreate: couldn't allocate space for new segment");
      return SH_GENERIC_ERROR;
    }
  segment->field0 = field0;
  segment->nFields = nFields;
  segment->status = status;
  segment->startMu = startMu;
  segment->endMu = endMu;

  // Add to chain
  if (shChainElementAddByPos(run->segments, segment, "TSSEGMENT", TAIL, AFTER)
      != SH_SUCCESS)
    {
      shFree(segment);
      shErrStackPush("tsSegmentCreate: couldn't append TSSEGMENT toCHAIN");
      return SH_GENERIC_ERROR;
    }

  return SH_SUCCESS;
}

/*============================================================================
**<AUTO EXTRACT>
**
** TCL VERB: segmentsGet
**
**<HTML>
**	Return a CHAIN of TSRUN, with one entry per Frames Pipeline Run
**	(limited to those which actually scanned along a strip).  Each run
**	is broken up into a CHAIN of TSSEGMENTS, where each segment is a
**	consecutive set of fields with the same survey status.  This may be
**	limited to a single run by specify a string "run camCol rerun" with
**	the optional parameter "-run".
**</HTML>
**
**</AUTO>
**============================================================================
*/
static char segmentsGetCmd[] = "segmentsGet";
static int segmentsGetFlg = FTCL_ARGV_NO_LEFTOVERS;
static ftclArgvInfo segmentsGetTbl[] = {
   {NULL, FTCL_ARGV_HELP, NULL, NULL,
    "Fetch all Frames Pipeline Runs as a CHAIN of TSRUNs\n"},
   {"-run", FTCL_ARGV_STRING, NULL, NULL,
    "Limit to Frames Pipeline Run 'run camCol rerun' only"},
   {NULL, FTCL_ARGV_END, NULL, NULL, NULL}
};

static int
tclSegmentsGet (ClientData clientData,
		Tcl_Interp *interp,
		int argc,
		char **argv)
{
  int status;
  char *runString = NULL;

  // Parse the arguments
  segmentsGetTbl[1].dst = &runString;
  if ((status = shTclParseArgv(interp, &argc, argv, segmentsGetTbl,
			       segmentsGetFlg, segmentsGetCmd))
      != FTCL_ARGV_SUCCESS)
    return status;

  // Fetch the global TCL variable "photo_frames" for container with all
  // Frames Pipeline Runs.  Then fetch the associated handle.
  char *photo_frames = Tcl_GetVar(interp, "photo_frames", TCL_GLOBAL_ONLY);
  if (photo_frames == NULL)
    {
      Tcl_AppendResult(interp, segmentsGetCmd,
		       ": global variable $photo_frames doesn't exist", NULL);
      return TCL_ERROR;
    }
  ooHandle(ooContObj) *fpCont;
  if (shTclAddrGetFromName (interp, photo_frames, (void **)&fpCont,
			    "ooHandle(ooContObj)") != TCL_OK)
    {
      Tcl_AppendResult(interp, segmentsGetCmd,
		       ": error getting handle to $photo_frames", NULL);
      return TCL_ERROR;
    }

  // Allocate CHAIN for TSRUNs
  CHAIN *runs;
  if ((runs = shChainNew("TSRUN")) == NULL)
    {
      Tcl_AppendResult(interp, segmentsGetCmd,
		       ": couldn't allocate TSRUN chain", NULL);
      return TCL_ERROR;
    }

  // Limit to single run if requested
  char predicate[250] = "";
  if (ftcl_ArgIsPresent(argc, argv, "-run", segmentsGetTbl) == 1)
    {
      // Translate string to run, camCol, rerun
      int runToGet = 0, camColToGet = 0, rerunToGet = 0;
      if (sscanf(runString, "%d %d %d", &runToGet, &camColToGet, &rerunToGet)
	  != 3)
	{
	  Tcl_AppendResult(interp, segmentsGetCmd,
			   ": illegal formatted 'run camCol rerun' string",
			   NULL);
	  return TCL_ERROR;
	}
      sprintf(predicate, "postageStampCalibration_->sSCPipelineRun_->imagingRun_->id_ == %d && postageStampCalibration_->sSCPipelineRun_->camCol_ == %d && rerun_ == %d", runToGet, camColToGet, rerunToGet);
    }

  // Initialize iterator over all frames pipeline runs
  ooItr(arFramesPipeRun) itr;
  if (itr.scan(*fpCont, oocRead, oocAll, predicate) != oocSuccess)
    {
      shChainDel(runs);
      Tcl_AppendResult(interp, segmentsGetCmd,
		       ": couldn't initalize iterator over frames pipeline runs",
		       NULL);
      return TCL_ERROR;
    }

  // Set up cosmic color for use by atTransApply().
  taTransCosmicSet(cosmicColor, cosmicScatter);

  // For each run ...
  while (itr.next())
    {
      // Get targeted stripe.
      ooHandle(arRun) driftScan = itr->imagingRun();
      int16 stripe = driftScan->stripe();

      // Skip this run if it didn't target a survey stripe.
      if (stripe < 1 ||
	  (stripe > 45 && stripe != 76 && stripe != 82 && stripe != 86))
	continue;

      // Get stripe node and incl
      double node = 95.;
      double incl = at_etaMin + stripe * at_stripeSeparation +
	at_surveyCenterDec;
      int southern = (stripe > 45);
      if (southern) incl -= 180.;

      // HARDWIRED: number of overlap rows per frame.
      int overlapRows = 128;

      // Get number of rows and cols for this run.
      int camRow = 1;  // Any photometric CCD row will do
      ooHandle(arCCDProgram) program =
	driftScan->program()->ccd(camRow, itr->camCol());
      int rowsPerField = program->nRows();
      // HARDWIRED:
      int colsPerField = 2048;

      // First and last rows in a field which are considered in the primary
      // range for that field.
      double firstRow = overlapRows / 2.;
      double lastRow = rowsPerField + overlapRows / 2.;

      // Has this run been fully processed prior to resolving?
      int processed = 1;
      ooHandle(arProduct) report = itr->setObjectStatus();
      if (! report.isValid())
	{
	  // Wasn't processed through setObjectStatus
	  processed = 0;
	}
      else
	{
	  report = itr->merge();
	  if (! report.isValid())
	    {
	      // Wasn't processed through merge
	      processed = 0;
	    }
	}

      // Create TSRUN structure for this run.
      TSRUN *run = (TSRUN*) shMalloc(sizeof(TSRUN));
      if (run == NULL)
	{
	  tsRunsChainDelete(runs);
	  Tcl_AppendResult(interp, segmentsGetCmd,
			   ": couldn't allocate TSRUN", NULL);
	  return TCL_ERROR;
	}
      run->stripe = stripe;
      run->strip = driftScan->strip() == AR_STRIP_NORTH ? 1 : 0;
      run->camCol = itr->camCol();
      run->scanline = run->stripe * 12 + (run->camCol - 1) * 2 + run->strip;
      run->run = itr->run();
      run->rerun = itr->rerun();
      run->field0 = itr->field0();
      run->nFields = itr->nFields();
      if ((run->segments = shChainNew("TSSEGMENT")) == NULL)
	{
	  shFree(run);
	  tsRunsChainDelete(runs);
	  Tcl_AppendResult(interp, segmentsGetCmd,
			   ": couldn't allocate CHAIN of TSSEGMENT", NULL);
	  shTclInterpAppendWithErrStack(interp);
	  return TCL_ERROR;
	}

      // Add run to the CHAIN of runs
      if (shChainElementAddByPos(runs, run, "TSRUN", TAIL, AFTER)
	  != SH_SUCCESS)
	{
	  shChainDel(run->segments);
	  shFree(run);
	  tsRunsChainDelete(runs);
	  Tcl_AppendResult(interp, segmentsGetCmd,
			   ": couldn't append TSRUN to CHAIN", NULL);
	  shTclInterpAppendWithErrStack(interp);
	  return TCL_ERROR;
	}

      // Break run up into segments of consecutive fields, where all fields
      // in a given segment have the same status.  Here, status means one of:
      //    primary         already resolved into a primary segement
      //    secondary       already resolved into a secondary segment
      //    bad             already resolved into a bad segment
      //    unresolved_bad  unresolved unacceptable data
      //    unresolved_ok   unresolved potentially acceptable data
      //    undetermined    unresolved, with data quality not set
      //    unprocessed     not-fully processed up to resolve

      if (processed == 0)
	{
	  // This run has not been fully processed prior to resolve.
	  // Create one segment for entire run.
	  ooHandle(arAstromCalib) astrom = itr->astrometricCalibration();
	  if (fabs(astrom->equinox() - at_surveyEquinox) > 0.000001)
	    {
	      tsRunsChainDelete(runs);
	      Tcl_AppendResult(interp, segmentsGetCmd,
			       ": non-survey equinox for astrometric calibration",
			       NULL);
	      shTclInterpAppendWithErrStack(interp);
	      return TCL_ERROR;
	    }
	  int startMu = tsRowToMu(astrom, run->camCol, run->field0,
				  firstRow + ROW_BUFFER, colsPerField, node,
				  incl, southern);
	  int endMu = tsRowToMu(astrom, run->camCol,
				run->field0 + run->nFields - 1,
				lastRow - ROW_BUFFER, colsPerField, node,
				incl, southern);
	  if (tsSegmentCreate(run, run->field0, run->nFields,
			      TS_UNPROCESSED, startMu, endMu) != SH_SUCCESS)
	    {
	      tsRunsChainDelete(runs);
	      shTclInterpAppendWithErrStack(interp);
	      return TCL_ERROR;
	    }
	  continue;
	}

      // This run has been fully processed.  We need to go through it field
      // by field.
      int startField, endField;
      int startMu, endMu;
      TSSEGMENTSTATUS segmentStatus;

      // Get the astrometric calibration used when the run was merged.
      ooHandle(arAstromCalib) astrom = itr->mergeAstrometricCalibration();
      if (! astrom.isValid())
	{
	  tsRunsChainDelete(runs);
	  Tcl_AppendResult(interp, segmentsGetCmd,
			   ": mergeAstrometricCalibration not set", NULL);
	  return TCL_ERROR;
	}
      if (fabs(astrom->equinox() - at_surveyEquinox) > 0.000001)
	{
	  tsRunsChainDelete(runs);
	  Tcl_AppendResult(interp, segmentsGetCmd,
			   ": non-survey equinox for astrometric calibration",
			   NULL);
	  shTclInterpAppendWithErrStack(interp);
	  return TCL_ERROR;
	}

      // For each field ...
      for (int field = run->field0; field < run->field0+run->nFields; field++)
	{
	  // Get object list for this field.
	  ooHandle(arObjectList) objectList = itr->objectList(field);

	  // Get the field's status.
	  TSSEGMENTSTATUS fieldStatus;
	  if (objectList->quality() == AR_DQ_UNDETERMINED)
	    fieldStatus = TS_UNDETERMINED;
	  else if (objectList->quality() == AR_DQ_BAD)
	    fieldStatus = TS_UNRESOLVED_BAD;
	  else
	    fieldStatus = TS_UNRESOLVED_OK;

	  // If first field, initialize potential first segment.
	  if (field == run->field0)
	    {
	      startMu = tsRowToMu(astrom, run->camCol, field,
				  firstRow + ROW_BUFFER, colsPerField,
				  node, incl, southern);
	      startField = field;
	      segmentStatus = fieldStatus;
	    }
	  
	  // If this field's status differs from the
	  // segment's status, then finish the segment and start a
	  // new one.
	  int newSegment = 0;
	  if (fieldStatus != segmentStatus)
	    {
	      // Where do we divide the two segments?
	      if (fieldStatus == TS_UNRESOLVED_OK ||
		  (segmentStatus == TS_UNDETERMINED &&
		   fieldStatus == TS_UNRESOLVED_BAD))
		{
		  // Divide at beginning of new field (changed from
		  // undetermined or bad to good, or undetermined to bad).
		  endField = field;
		  endMu = tsRowToMu(astrom, run->camCol, endField,
				    firstRow + ROW_BUFFER, colsPerField,
				    node, incl, southern);
		}
	      else
		{
		  // Divide at end of previous field (changed from
		  // good to undetermined or bad, or bad to undetermined).
		  endField = field - 1;
		  endMu = tsRowToMu(astrom, run->camCol, endField,
				    lastRow - ROW_BUFFER, colsPerField,
				    node, incl, southern);
		}
	      if (endMu < startMu)
		{
		  // Impossible error.
		  tsRunsChainDelete(runs);
		  Tcl_AppendResult(interp, segmentsGetCmd,
				   ": impossible error: endMu < startMu",
				   NULL);
		  return TCL_ERROR;
		}
	      else if (endMu == startMu)
		{
		  // This can arise if a previously resolved segment has a
		  // boundary at the end of a field, and the next field has
		  // a different status.  In this case, we don't get a new
		  // segment --- we just change the segment status to the new
		  // field status.
		  if (endField != startField)
		    {
		      tsRunsChainDelete(runs);
		      Tcl_AppendResult(interp, segmentsGetCmd,
				       ": impossible error: endMu == startMu",
				       NULL);
		      return TCL_ERROR;
		    }
		  newSegment = 0;
		  segmentStatus = fieldStatus;
		}
	      else
		{
		  // We've got a new segment.
		  newSegment = 1;
		}
	    }
		      
	  // Does this field begin a previously resolved segment?
	  char predicate[100];
	  sprintf(predicate, "field0_ = %d", field);
	  ooItr(arSegment) segItr;
	  if (objectList->segments(segItr, oocRead, oocPublic, predicate)
	      != oocSuccess)
	    {
	      tsRunsChainDelete(runs);
	      Tcl_AppendResult(interp, segmentsGetCmd,
			       ": couldn't initalize iterator over segments",
			       NULL);
	      return TCL_ERROR;
	    }
	  if (segItr.next())
	    {
	      if (startMu < segItr->startMu())
		{
		  if (newSegment)
		    {
		      if (endMu > segItr->startMu())
			{
			  endMu = segItr->startMu();
			  endField = field;
			}
		      if (tsSegmentCreate(run, startField,
					  endField - startField + 1,
					  segmentStatus, startMu, endMu)
			  != SH_SUCCESS)
			{
			  tsRunsChainDelete(runs);
			  shTclInterpAppendWithErrStack(interp);
			  return TCL_ERROR;
			}
		      startMu = endMu;
		      startField = endField;
		      segmentStatus = fieldStatus;
		    }
		  if (startMu < segItr->startMu())
		    if (tsSegmentCreate(run, startField, field-startField+1,
					segmentStatus, startMu,
					segItr->startMu())
			!= SH_SUCCESS)
		      {
			tsRunsChainDelete(runs);
			shTclInterpAppendWithErrStack(interp);
			return TCL_ERROR;
		      }
		}

	      // Create a segment for this database segment.
	      if (tsSegmentCreate(run, field, segItr->nFields(),
				  segItr->status() == AR_SEGMENT_STATUS_BAD ?
				  TS_BAD : (segItr->status() ==
					    AR_SEGMENT_STATUS_PRIMARY ?
					    TS_PRIMARY : TS_SECONDARY),
				  segItr->startMu(), segItr->endMu())
		  != SH_SUCCESS)
		{
		  tsRunsChainDelete(runs);
		  shTclInterpAppendWithErrStack(interp);
		  return TCL_ERROR;
		}

	      // Initialize new segment, starting from last field in database
	      // segment.
	      startMu = segItr->endMu();
	      field += segItr->nFields() - 1;
	      startField = field;
	      objectList = itr->objectList(field);
	      if (objectList->quality() == AR_DQ_UNDETERMINED)
		segmentStatus = TS_UNDETERMINED;
	      else if (objectList->quality() == AR_DQ_BAD)
		segmentStatus = TS_UNRESOLVED_BAD;
	      else
		segmentStatus = TS_UNRESOLVED_OK;
	      field--;
	    }
	  else if (newSegment)
	    {
	      // This field is not part of a database segment, but does begin
	      // a new segment.

	      // Finish the current segment.
	      if (tsSegmentCreate(run, startField, endField - startField + 1,
				  segmentStatus, startMu, endMu)
		  != SH_SUCCESS)
		{
		  tsRunsChainDelete(runs);
		  shTclInterpAppendWithErrStack(interp);
		  return TCL_ERROR;
		}

	      // Start a new one.
	      startMu = endMu;
	      startField = endField;
	      segmentStatus = fieldStatus;
	    }
	}

      // Finish the last segment.
      endField = field - 1;
      endMu = tsRowToMu(astrom, run->camCol, endField, lastRow - ROW_BUFFER,
			colsPerField, node, incl, southern);
      if (endMu > startMu)
	if (tsSegmentCreate(run, startField, endField - startField + 1,
			    segmentStatus, startMu, endMu)
	    != SH_SUCCESS)
	  {
	    tsRunsChainDelete(runs);
	    shTclInterpAppendWithErrStack(interp);
	    return TCL_ERROR;
	  }
    }

  // Return a handle to the TSRUN chain
  char vName[HANDLE_NAMELEN];
  if (shTclHandleNew(interp, vName, "CHAIN", (void *)runs) != TCL_OK)
    {
      // Error.  Delete the chain and return an error.
      tsRunsChainDelete(runs);
      Tcl_AppendResult(interp, segmentsGetCmd,
		       ": error allocating chain TCL handle", NULL);
      return TCL_ERROR;
    }
  Tcl_SetResult(interp, vName, TCL_VOLATILE);
  return TCL_OK;
}

/*============================================================================
**<AUTO EXTRACT>
**
** TCL VERB: resolvableChunksGet
**
**<HTML>
**	Process a CHAIN of TSRUN, returning a TCL list of resolvable chunks.
**	A resolvable chunk is a length of a stripe, with a
**	starting and ending mu, which has not previously been resolved,
**	and which has acceptable data in all 12 scanlines.  Each chunk is as
**	long as it can be.  Each element in the returned TCL list (each
**	resolvable chunk) is itself a TCL list, where the first element is
**	the stripe id, and the second and third elements are the starting
**	and ending great circle longitude (mu), respectively, in integer
**	arcsecs.
**<P>
**	The input CHAIN of TSRUN must be sorted by increasing scanline.
**</HTML>
**
**</AUTO>
**============================================================================
*/
static char resolvableChunksGetCmd[] = "resolvableChunksGet";
static int resolvableChunksGetFlg = FTCL_ARGV_NO_LEFTOVERS;
static ftclArgvInfo resolvableChunksGetTbl[] = {
   {NULL, FTCL_ARGV_HELP, NULL, NULL,
    "Return a list of all resolvable chunks\n"},
   {"<runs>", FTCL_ARGV_STRING, NULL, NULL,
    "CHAIN of input TSRUNs"},
   {NULL, FTCL_ARGV_END, NULL, NULL, NULL}
};

static int
tclResolvableChunksGet (ClientData clientData,
		Tcl_Interp *interp,
		int argc,
		char **argv)
{
  CHAIN *runs = NULL;
  char *runsHandle = NULL;
  int status;

  // Parse the arguments
  resolvableChunksGetTbl[1].dst = &runsHandle;
  if ((status = shTclParseArgv(interp, &argc, argv, resolvableChunksGetTbl,
			       resolvableChunksGetFlg, resolvableChunksGetCmd))
      != FTCL_ARGV_SUCCESS)
    return status;
   
  // Get handle to container for TSRUNs
  if (shTclAddrGetFromName (interp, runsHandle, (void **)&runs, "CHAIN")
      != TCL_OK)
    {
      Tcl_AppendResult(interp, resolvableChunksGetCmd,
		       ": error getting handle to runs", NULL);
      return TCL_ERROR;
    }
   
  // Verify that chain is for TSRUNs
  if (shChainTypeGet(runs) != shTypeGetFromName("TSRUN"))
    {
      Tcl_AppendResult(interp, resolvableChunksGetCmd,
		       ": input CHAIN not of type TSRUN", NULL);
      return TCL_ERROR;
    }

  // Clear result
  Tcl_SetResult(interp, "", TCL_VOLATILE);

  // Some initializations
  TSRANGE range[MAX_RANGES];
  TSRANGE unRange[MAX_RANGES];
  TSRANGE primRange[MAX_RANGES];
  int nRanges = 0;
  int nUnRanges = 0;
  int nPrimRanges = 0;
  int currentStripe = -1;
  int currentScanline = -1;
  int skip = 1;
  CURSOR_T cursor = shChainCursorNew(runs);
  TSRUN *run;

  // For each Frames Pipeline Run (ordered by increasing scanline) ...
  while ((run = (TSRUN*) shChainWalk(runs, cursor, NEXT)) != NULL)
    {
      // Get the scanline number within the stripe (0-11).
      int scanline = (run->camCol - 1) * 2 + run->strip;

      // Handle if we're skipping the current stripe
      if (skip)
	{
	  if (run->stripe == currentStripe)
	    {
	      // Skip current stripe
	      continue;
	    }
	  else
	    {
	      // Initialize new stripe.  Skip it if this first scanline is
	      // not 0.
	      currentStripe = run->stripe;
	      if (scanline != 0)
		{
		  skip = 1;
		  continue;
		}
	      currentScanline = scanline;
	      nRanges = 0;
	      nUnRanges = 0;
	      nPrimRanges = 0;
	      skip = 0;
	    }
	}
      
      // Is this a new scanline ?
      if (scanline != currentScanline || run->stripe != currentStripe)
	{
	  // If we missed a scanline in the current stripe, then skip to the
	  // next stripe.
	  if (run->stripe == currentStripe && scanline - currentScanline != 1)
	    {
	      skip = 1;
	      continue;
	    }

	  // Finish current scanline

	  // Remove sections of unresolved-ok ranges that overlap with
	  // primary ranges
	  tsRangeNotIntersect(unRange, &nUnRanges, primRange, nPrimRanges);
	  
	  if (nUnRanges == 0)
	    {
	      // There are no unresolved-ok segments.  Skip this stripe.
	      skip = 1;
	      if (run->stripe == currentStripe) continue;
	    }
	  else if (currentScanline == 0)
	    {
	      // This is the first scanline in the stripe.
	      // Initialize this stripe's ranges to the ranges for this first
	      // scanline.
	      nRanges = nUnRanges;
	      for (int i = 0; i < nUnRanges; i++)
		{
		  range[i].start = unRange[i].start;
		  range[i].end = unRange[i].end;
		}
	    }
	  else
	    {
	      // This is not the first scanline in the stripe.
	      // Make this stripe's ranges the intersection of their current
	      // values and this scanline's ranges.
	      tsRangeIntersect(range, &nRanges, unRange, nUnRanges);

	      // If no ranges left in this stripe, skip the stripe
	      if (nRanges == 0)
		{
		  skip = 1;
		  if (run->stripe == currentStripe) continue;
		}
	    }
	      
	  // Have we hit a new stripe ?
	  if (run->stripe != currentStripe)
	    {
	      if (currentScanline == 11 && skip == 0)
		{
		  // The current stripe is good.  Finish it.
		  for (int i = 0; i < nRanges; i++)
		    {
		      char buffer[40];
		      sprintf(buffer, "{%d %d %d} ", currentStripe,
			      range[i].start, range[i].end);
		      Tcl_AppendResult(interp, buffer, NULL);
		    }
		}

	      // Initialize new stripe.  Skip it if this first scanline is
	      // not 0.
	      currentStripe = run->stripe;
	      if (scanline != 0)
		{
		  skip = 1;
		  continue;
		}
	      nRanges = 0;
	      skip = 0;
	    }

	  // Initialize new scanline
	  currentScanline = scanline;
	  nUnRanges = 0;
	  nPrimRanges = 0;
	}

      // For each segment ...
      CURSOR_T cur2 = shChainCursorNew(run->segments);
      TSSEGMENT *segment;
      while ((segment = (TSSEGMENT*) shChainWalk(run->segments, cur2, NEXT))
	     != NULL)
	{
	  // Track unresolved ok segments and resolved primary segments.
	  if (segment->status == TS_UNRESOLVED_OK)
	    tsRangeAdd(unRange, &nUnRanges, segment->startMu,
		       segment->endMu);
	  else if (segment->status == TS_PRIMARY)
	    tsRangeAdd(primRange, &nPrimRanges, segment->startMu,
		       segment->endMu);
	}
      shChainCursorDel(run->segments, cur2);
    }
  shChainCursorDel(runs, cursor);

  // Finish the last stripe
  if (skip == 0 && currentScanline == 11)
    {
      // The current stripe is good.  Finish it.
      tsRangeIntersect(range, &nRanges, unRange, nUnRanges);
      for (int i = 0; i < nRanges; i++)
	{
	  char buffer[40];
	  sprintf(buffer, "{%d %d %d} ", currentStripe,
		  range[i].start, range[i].end);
	  Tcl_AppendResult(interp, buffer, NULL);
	}
    }

  return TCL_OK;
}

/*============================================================================
**<AUTO EXTRACT>
**
** TCL VERB: repeatSegmentsGet
**
**<HTML>
**	Process a CHAIN of TSRUN, returning a new TCL list of
**	TS_UNRESOLVED_OK segments which overlap previously resolved chunks.
**	Each segment will be shortened to that part of the segment which
**	overlaps a previously resolved chunk (and split into multiple segments
**	if it intersects multiple chunks).  Each element in the returned TCL
**	list is itself a TCL list, where the first element is
**	the run number, the second element the camera column, the third
**	element the rerun number, and the fourth and fifth elements the
**	starting and ending great circle longitude (mu, integer arcsecs),
**	respectively, for the "repeat" segment.  A "repeat" segment is thus
**	an unresolved segment overlapping a previously resolved chunk, and
**	which therefore can never be a primary segment.
**</HTML>
**
**</AUTO>
**============================================================================
*/
static char repeatSegmentsGetCmd[] = "repeatSegmentsGet";
static int repeatSegmentsGetFlg = FTCL_ARGV_NO_LEFTOVERS;
static ftclArgvInfo repeatSegmentsGetTbl[] = {
   {NULL, FTCL_ARGV_HELP, NULL, NULL,
    "Return a list of all 'repeat' segments\n"},
   {"<runs>", FTCL_ARGV_STRING, NULL, NULL,
    "CHAIN of input TSRUNs"},
   {NULL, FTCL_ARGV_END, NULL, NULL, NULL}
};

static int
tclRepeatSegmentsGet (ClientData clientData,
		Tcl_Interp *interp,
		int argc,
		char **argv)
{
  CHAIN *runs = NULL;
  char *runsHandle = NULL;
  int status;

  // Parse the arguments
  repeatSegmentsGetTbl[1].dst = &runsHandle;
  if ((status = shTclParseArgv(interp, &argc, argv, repeatSegmentsGetTbl,
			       repeatSegmentsGetFlg, repeatSegmentsGetCmd))
      != FTCL_ARGV_SUCCESS)
    return status;
   
  // Get handle to container for TSRUNs
  if (shTclAddrGetFromName (interp, runsHandle, (void **)&runs, "CHAIN")
      != TCL_OK)
    {
      Tcl_AppendResult(interp, repeatSegmentsGetCmd,
		       ": error getting handle to runs", NULL);
      return TCL_ERROR;
    }
   
  // Verify that chain is for TSRUNs
  if (shChainTypeGet(runs) != shTypeGetFromName("TSRUN"))
    {
      Tcl_AppendResult(interp, repeatSegmentsGetCmd,
		       ": input CHAIN not of type TSRUN", NULL);
      return TCL_ERROR;
    }

  // Fetch the global TCL variable "photo_segments" for container with all
  // resolved segments and chunks.  Then fetch the associated handle.
  char *photo_segments = Tcl_GetVar(interp, "photo_segments", TCL_GLOBAL_ONLY);
  if (photo_segments == NULL)
    {
      Tcl_AppendResult(interp, repeatSegmentsGetCmd,
		       ": global variable $photo_segments doesn't exist",
		       NULL);
      return TCL_ERROR;
    }
  ooHandle(ooContObj) *segCont;
  if (shTclAddrGetFromName (interp, photo_segments, (void **)&segCont,
			    "ooHandle(ooContObj)") != TCL_OK)
    {
      Tcl_AppendResult(interp, repeatSegmentsGetCmd,
		       ": error getting handle to $photo_segments", NULL);
      return TCL_ERROR;
    }

  // Initialize iterator over resolved primary chunks
  ooItr(arChunk) chunkItr;
  if (chunkItr.scan(*segCont, oocRead,oocPublic, "stripe_ != 0") != oocSuccess)
    {
      Tcl_AppendResult(interp, repeatSegmentsGetCmd,
		       ": couldn't initialize iterator over chunks", NULL);
      return TCL_ERROR;
    }
  
  // Clear result
  Tcl_SetResult(interp, "", TCL_VOLATILE);

  // For each chunk ...
  while (chunkItr.next())
    {
      // Get chunk limits
      int stripe = chunkItr->stripe();
      int startMu = chunkItr->startMu();
      int endMu = chunkItr->endMu();

      // For each Frames Pipeline Run ...
      CURSOR_T cursor = shChainCursorNew(runs);
      TSRUN *run;
      while ((run = (TSRUN*) shChainWalk(runs, cursor, NEXT)) != NULL)
	{
	  // Skip this run if not for stripe of current chunk
	  if (run->stripe != stripe) continue;

	  // For each segment ...
	  CURSOR_T cur2 = shChainCursorNew(run->segments);
	  TSSEGMENT *segment;
	  while ((segment = (TSSEGMENT*) shChainWalk(run->segments, cur2,NEXT))
		 != NULL)
	    {
	      // If an unresolved but OK segment interseecting the chunk,
	      // return its intersection with the chunk
	      if (segment->status == TS_UNRESOLVED_OK)
		if (segment->startMu < endMu && segment->endMu > startMu)
		  {
		    char buffer[40];
		    sprintf(buffer, "{%d %d %d %d %d} ",
			    run->run, run->camCol, run->rerun,
			    (segment->startMu > startMu ? segment->startMu :
			     startMu),
			    (segment->endMu < endMu ? segment->endMu :
			     endMu));
		    Tcl_AppendResult(interp, buffer, NULL);
		  }
	    }
	  shChainCursorDel(run->segments, cur2);
	}
      shChainCursorDel(runs, cursor);
    }
  
  return TCL_OK;
}

/*============================================================================
**<AUTO EXTRACT>
**
** TCL VERB: chunkResolve
**
**<HTML>
**	Resolve a chunk of a stripe.  A chunk is a range in great circle
**	longitude of a stripe.  For all Frames Pipeline Runs which
**	have been processed through Set Object
**	Status and Merge, and which target the stripe of interest, those
**	fields intersecting the range in mu, and whose data quality has
**	been set (i.e., is not set to UNDETERMINED) will be resolved into
**	PRIMARY, SECONDARY, and BAD segments.  This is controlled by the input
**	chain of TSRUNS (<em>runs</em>), thus this chain had better be in sync
**	with the databases.
**<P>
**	The list of TSSEGMENTS to use as PRIMARY segments may be explicitly
**	specified via the argument "-segments".  This is a prioritized list,
**	in that for a given scanline, the first TSSEGMENT in the list is used
**	in its entirety (that portion that overlaps with the chunk, that is)
**	as a PRIMARY segment, while subsequent ones
v**	fill in the portion of the chunk not covered by the previous ones.
**	"-segments" is a TCL list, where each element is itself a TCL list,
**	where the elements are run number, camCol, rerun number, and the first
**	field in the segment.  If "-segments" is not specified, then runs are
**	prioritized by run number, the effect of which is that the earliest
**	run is used in its entirety as a PRIMARY segment, with latter ones used
**	in order to fill in the gaps.
**<P>
**	The input CHAIN of TSRUN must be sorted by increasing scanline.  If an
**	error is returned, then a database <em>rollback</em> should be
**	performed, as some object flags may have been set.
**</HTML>
**
**</AUTO>
**============================================================================
*/
static char chunkResolveCmd[] = "chunkResolve";
static int chunkResolveFlg = FTCL_ARGV_NO_LEFTOVERS;
static ftclArgvInfo chunkResolveTbl[] = {
   {NULL, FTCL_ARGV_HELP, NULL, NULL,
    "Resolve a chunk\n"},
   {"<runs>", FTCL_ARGV_STRING, NULL, NULL,
    "CHAIN of input TSRUNs"},
   {"<stripe>", FTCL_ARGV_INT, NULL, NULL,
    "ID of stripe to resolve"},
   {"<startMu>", FTCL_ARGV_INT, NULL, NULL,
    "great circle longitude (arcsecs) at start of chunk to resolve"},
   {"<endMu>", FTCL_ARGV_INT, NULL, NULL,
    "great circle longitude (arcsecs) at end of chunk to resolve"},
   {"<minWidth>", FTCL_ARGV_INT, NULL, NULL,
    "minimum width to accept for a primary or secondary segment (arcsecs)"},
   {"<lambdaMin>", FTCL_ARGV_DOUBLE, NULL, NULL,
    "lower lambda limit of stripe"},
   {"<lambdaMax>", FTCL_ARGV_DOUBLE, NULL, NULL,
    "upper lambda limit of stripe"},
   {"<etaMin>", FTCL_ARGV_DOUBLE, NULL, NULL,
    "lower eta limit of stripe"},
   {"<etaMax>", FTCL_ARGV_DOUBLE, NULL, NULL,
    "upper eta limit of stripe"},
   {"-segments", FTCL_ARGV_STRING, NULL, NULL,
    "list of segments to use"},
   {NULL, FTCL_ARGV_END, NULL, NULL, NULL}
};

static int
tclChunkResolve (ClientData clientData,
		Tcl_Interp *interp,
		int argc,
		char **argv)
{
  CHAIN *runs = NULL;
  char *runsHandle = NULL;
  int stripe;
  int startMu;
  int endMu;
  int minWidth;
  double lambdaMin, lambdaMax, etaMin, etaMax;
  char *segmentsToUse = NULL;
  int status;

  // Parse the arguments
  chunkResolveTbl[1].dst = &runsHandle;
  chunkResolveTbl[2].dst = &stripe;
  chunkResolveTbl[3].dst = &startMu;
  chunkResolveTbl[4].dst = &endMu;
  chunkResolveTbl[5].dst = &minWidth;
  chunkResolveTbl[6].dst = &lambdaMin;
  chunkResolveTbl[7].dst = &lambdaMax;
  chunkResolveTbl[8].dst = &etaMin;
  chunkResolveTbl[9].dst = &etaMax;
  chunkResolveTbl[10].dst = &segmentsToUse;
  if ((status = shTclParseArgv(interp, &argc, argv, chunkResolveTbl,
			       chunkResolveFlg, chunkResolveCmd))
      != FTCL_ARGV_SUCCESS)
    return status;
   
  // Error if startMu > endMu, or if range is less than minimum width.
  if (startMu >= endMu)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": startMu >= endMu", NULL);
      return TCL_ERROR;
    }
  if (endMu - startMu < minWidth)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": chunk width < minWidth", NULL);
      return TCL_ERROR;
    }

  // Fetch the global TCL variable "photo_frames" for container with all
  // Frames Pipeline Runs.  Then fetch the associated handle.
  char *photo_frames = Tcl_GetVar(interp, "photo_frames", TCL_GLOBAL_ONLY);
  if (photo_frames == NULL)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": global variable $photo_frames doesn't exist", NULL);
      return TCL_ERROR;
    }
  ooHandle(ooContObj) *fpCont;
  if (shTclAddrGetFromName (interp, photo_frames, (void **)&fpCont,
			    "ooHandle(ooContObj)") != TCL_OK)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": error getting handle to $photo_frames", NULL);
      return TCL_ERROR;
    }

  // Fetch the global TCL variable "photo_segments" for container with all
  // resolved segments.  Then fetch the associated handle.
  char *photo_segments = Tcl_GetVar(interp, "photo_segments", TCL_GLOBAL_ONLY);
  if (photo_segments == NULL)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": global variable $photo_segments doesn't exist",
		       NULL);
      return TCL_ERROR;
    }
  ooHandle(ooContObj) *segCont;
  if (shTclAddrGetFromName (interp, photo_segments, (void **)&segCont,
			    "ooHandle(ooContObj)") != TCL_OK)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": error getting handle to $photo_segments", NULL);
      return TCL_ERROR;
    }

  // Get handle to container for TSRUNs
  if (shTclAddrGetFromName (interp, runsHandle, (void **)&runs, "CHAIN")
      != TCL_OK)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": error getting handle to runs", NULL);
      return TCL_ERROR;
    }
   
  // Verify that chain is for TSRUNs
  if (shChainTypeGet(runs) != shTypeGetFromName("TSRUN"))
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": input CHAIN not of type TSRUN", NULL);
      return TCL_ERROR;
    }

  // Set up cosmic color for use by atTransApply().
  taTransCosmicSet(cosmicColor, cosmicScatter);

  // If the list of segments to use is specified, then prepare an index array.
  const int maxSegments = 100;
  struct {
    int run;
    int camCol;
    int rerun;
    int field0;
    int found;
  } segmentsIndex[maxSegments];
  int nSegmentsToUse = 0;
  if (ftcl_ArgIsPresent(argc, argv, "-segments", chunkResolveTbl) == 1)
    {
      // Fetch TCL list of segments
      char **listArgv;
      if (Tcl_SplitList(interp, segmentsToUse, &nSegmentsToUse, &listArgv)
	  != TCL_OK)
	{
	  Tcl_AppendResult(interp, chunkResolveCmd,
			   ": couldn't parse list of segments to use", NULL);
	  return TCL_ERROR;
	}

      // Error if empty list specified
      if (nSegmentsToUse == 0)
	{
	  Tcl_AppendResult(interp, chunkResolveCmd,
			   ": empty list of segments-to-use specified", NULL);
	  return TCL_ERROR;
	}

      // Error if number of segments exceeds maximum
      if (nSegmentsToUse > maxSegments)
	{
	  Tcl_AppendResult(interp, chunkResolveCmd,
			   ": too many segments-to-use specified", NULL);
	  return TCL_ERROR;
	}

      // Translate TCL list to array
      for (int i = 0; i < nSegmentsToUse; i++)
	{
	  if (sscanf(listArgv[i], "%d %d %d %d", &(segmentsIndex[i].run),
		     &(segmentsIndex[i].camCol),
		     &(segmentsIndex[i].rerun),
		     &(segmentsIndex[i].field0)) != 4)
	    {
	      Tcl_AppendResult(interp, chunkResolveCmd,
			       ": illegal segments-to-use", NULL);
	      return TCL_ERROR;
	    }
	  segmentsIndex[i].found = 0;
	}
    }

  // Some initializations
  TSSEGMENT1 segArray[maxSegments];
  int nGoodSegs = 0;
  int nSegs = 0;
  TSRANGE unRange[MAX_RANGES];
  int nUnRanges = 0;
  int currentScanline = -1;
  CURSOR_T cursor = shChainCursorNew(runs);
  char errMessage[100];

  // Fetch list of segments to use from chain of TSRUNs
  while (1)
    {
      // Get next run
      TSRUN *run = (TSRUN*) shChainWalk(runs, cursor, NEXT);

      int scanline;
      int stripeDone;

      if (run != NULL)
	{
	  // Skip if we haven't yet reached the stripe of interest.
	  if (run->stripe < stripe) continue;

	  // Get the scanline number within the stripe (0-11).
	  scanline = (run->camCol - 1) * 2 + run->strip;

	  // No more segments if we've moved onto the next stripe.
	  stripeDone = (run->stripe != stripe);
	}
      else
	{
	  // No more segments.
	  stripeDone = 1;
	}

      // Have we finished off a scanline?
      if (scanline != currentScanline || stripeDone)
	{
	  // Error if we missed a scanline.
	  if ((! stripeDone && scanline - currentScanline != 1) ||
	      (stripeDone && currentScanline != 11))
	    {
	      sprintf(errMessage, ": no segments for scanline %d",
		      currentScanline + 1);
	      Tcl_AppendResult(interp, chunkResolveCmd, errMessage, NULL);
	      return TCL_ERROR;
	    }

	  if (currentScanline != -1)
	    {
	      // We should only have one range of unresolved-ok segments.
	      // Error if it doesn't cover the chunk.
	      if (nUnRanges != 1)
		{
		  sprintf(errMessage, ": unresolved segments don't cover chunk for scanline %d",
			  currentScanline);
		  Tcl_AppendResult(interp, chunkResolveCmd, errMessage, NULL);
		  return TCL_ERROR;
		}
	      if (unRange[0].start > startMu || unRange[0].end < endMu)
		{
		  sprintf(errMessage, ": unresolved segments don't cover chunk for scanline %d %d %d %d %d",
			  currentScanline, unRange[0].start, startMu, unRange[0].end, endMu);
		  Tcl_AppendResult(interp, chunkResolveCmd, errMessage, NULL);
		  return TCL_ERROR;
		}

	      // Break out if we've finished the stripe.
	      if (stripeDone) break;
	    }

	  // Initialize new scanline
	  currentScanline = scanline;
	  nUnRanges = 0;
	}

      // For each segment ...
      CURSOR_T cur2 = shChainCursorNew(run->segments);
      TSSEGMENT *segment;
      while ((segment = (TSSEGMENT*) shChainWalk(run->segments, cur2, NEXT))
	     != NULL)
	{
	  // Skip this segment if it lies outside the resolved chunk.
	  if (segment->startMu >= endMu || segment->endMu <= startMu)
	    continue;

	  switch (segment->status)
	    {
	    case TS_PRIMARY:
	      // Error if a primary chunk overlaps the chunk to resolve.
	      Tcl_AppendResult(interp, chunkResolveCmd,
			       ": primary segment overlaps chunk", NULL);
	      return TCL_ERROR;

	    case TS_UNRESOLVED_OK:
	      // Skip segment if its smaller than the minimum width
	      if ((segment->endMu < endMu ? segment->endMu : endMu) -
		  (segment->startMu > startMu ? segment->startMu : startMu) <
		  minWidth)
		break;

	      // Add to list of segments
	      if (nSegmentsToUse > 0)
		{
		  // Only add primary segment included in the
		  // segments-to-use list, and insert it in the proper order.
		  for (int i = 0; i < nSegmentsToUse; i++)
		    {
		      if (run->run == segmentsIndex[i].run &&
			  run->camCol == segmentsIndex[i].camCol &&
			  run->rerun == segmentsIndex[i].rerun &&
			  segment->field0 == segmentsIndex[i].field0)
			{
			  if (segmentsIndex[i].found)
			    {
			      char errMessage[100];
			      sprintf(errMessage, "segment-to-use (run %d, camCol %d, rerun %d, field0 %d) specified twice",
				      run->run, run->camCol, run->rerun,
				      segment->field0);
			      Tcl_AppendResult(interp, chunkResolveCmd,
					       errMessage, NULL);
			      return TCL_ERROR;
			    }
			  segArray[i].run = run->run;
			  segArray[i].camCol = run->camCol;
			  segArray[i].rerun = run->rerun;
			  segArray[i].scanline = run->scanline;
			  segArray[i].field0 = segment->field0;
			  segArray[i].nFields = segment->nFields;
			  segArray[i].startMu = segment->startMu;
			  segArray[i].endMu = segment->endMu;
			  segArray[i].status = TS_PRIMARY;
			  segmentsIndex[i].found = 1;
			  nGoodSegs++;
			  nSegs++;

			  // Add to the list of unresolved ranges for this
			  // scanline.
			  tsRangeAdd(unRange, &nUnRanges, segment->startMu,
				     segment->endMu);
			  break;
			}
		    }
		  
		  // If not found in segments-to-use list, make it a
		  // secondary segment.
		  if (i == nSegmentsToUse)
		    {
		      int index = nSegmentsToUse + (nSegs - nGoodSegs);
		      segArray[index].run = run->run;
		      segArray[index].camCol = run->camCol;
		      segArray[index].rerun = run->rerun;
		      segArray[index].scanline = run->scanline;
		      segArray[index].field0 = segment->field0;
		      segArray[index].nFields = segment->nFields;
		      segArray[index].startMu = segment->startMu;
		      segArray[index].endMu = segment->endMu;
		      segArray[index].status = TS_SECONDARY;
		      nSegs++;
		    }
		}
	      else
		{
		  // Add it, keeping the segments for this scanline sorted
		  // in the array by increasing run number.  Remember that
		  // the array is already sorted by scanline.
		  for (int i = nSegs - 1; i >= 0; i--)
		    if (scanline != segArray[i].scanline ||
			run->run >= segArray[i].run)
		      {
			break;
		      }
		    else
		      {
			segArray[i+1].run = segArray[i].run;
			segArray[i+1].camCol = segArray[i].camCol;
			segArray[i+1].rerun = segArray[i].rerun;
			segArray[i+1].scanline = segArray[i].scanline;
			segArray[i+1].field0 = segArray[i].field0;
			segArray[i+1].nFields = segArray[i].nFields;
			segArray[i+1].startMu = segArray[i].startMu;
			segArray[i+1].endMu = segArray[i].endMu;
			segArray[i+1].status = segArray[i].status;
		      }
		  segArray[i+1].run = run->run;
		  segArray[i+1].camCol = run->camCol;
		  segArray[i+1].rerun = run->rerun;
		  segArray[i+1].scanline = run->scanline;
		  segArray[i+1].field0 = segment->field0;
		  segArray[i+1].nFields = segment->nFields;
		  segArray[i+1].startMu = segment->startMu;
		  segArray[i+1].endMu = segment->endMu;
		  segArray[i+1].status = TS_PRIMARY;
		  nSegs++;

		  // Add to the list of unresolved ranges for this
		  // scanline.
		  tsRangeAdd(unRange, &nUnRanges, segment->startMu,
			     segment->endMu);
		}
	      break;

	    case TS_UNRESOLVED_BAD:
	      // Add to list of segments
	      int index;
	      if (nSegmentsToUse > 0)
		index = nSegmentsToUse + nSegs - nGoodSegs;
	      else
		index = nSegs;
	      segArray[index].run = run->run;
	      segArray[index].camCol = run->camCol;
	      segArray[index].rerun = run->rerun;
	      segArray[index].scanline = run->scanline;
	      segArray[index].field0 = segment->field0;
	      segArray[index].nFields = segment->nFields;
	      segArray[index].startMu = segment->startMu;
	      segArray[index].endMu = segment->endMu;
	      segArray[index].status = TS_BAD;
	      nSegs++;
	      break;

	    case TS_SECONDARY:
	    case TS_BAD:
	    case TS_UNDETERMINED:
	    case TS_UNPROCESSED:
	      // Skip these
	      break;
	    }
	}
      shChainCursorDel(run->segments, cur2);
    }
  shChainCursorDel(runs, cursor);

  // If list of segments-to-use was specified, then its an error if they
  // weren't all found.
  if (nSegmentsToUse > 0 && nGoodSegs != nSegmentsToUse)
    {
      Tcl_AppendResult(interp, chunkResolveCmd,
		       ": not all segments-to-use found", NULL);
      return TCL_ERROR;
    }

  // OK, we have a set of segments which spans the chunk in each scanline.
  // Within the array, the good segments are ordered by increasing priority.
  // By priority, we mean that, within a scanline, all of the first segment
  // within the chunk will be made primary, and for later segments only that
  // portion which does not intersect previous segments will be made primary.

  // Allocate array for this chunk's segments
  ooTVArray(ooHandle(arSegment)) dbSegments;

  // For each segment ...
  for (int seg = 0; seg < nSegs; seg++)
    {
      // Get the handle to the run
      char predicate[200];
      sprintf(predicate, "postageStampCalibration_->sSCPipelineRun_->imagingRun_->id_ == %d && postageStampCalibration_->sSCPipelineRun_->camCol_ == %d && rerun_ == %d", segArray[seg].run, segArray[seg].camCol, segArray[seg].rerun);
      ooItr(arFramesPipeRun) itr;
      if (itr.scan(*fpCont, oocRead, oocPublic, predicate)
	  != oocSuccess)
	{
	  Tcl_AppendResult(interp, chunkResolveCmd,
			   ": couldn't get run handle", NULL);
	  return TCL_ERROR;
	}
      if (! itr.next())
	{
	  Tcl_AppendResult(interp, chunkResolveCmd,
			   ": couldn't get run handle", NULL);
	  return TCL_ERROR;
	}
      ooHandle(arFramesPipeRun) framesH = itr;
      if (itr.next())
	{
	  Tcl_AppendResult(interp, chunkResolveCmd,
			   ": impossible error --- two runs with same run, camCol, and rerun numbers", NULL);
	  return TCL_ERROR;
	}

      // Limit segment to great circle longitude range of the chunk.
      int segStartMu =
	(segArray[seg].startMu > startMu ? segArray[seg].startMu : startMu);
      int segEndMu =
	(segArray[seg].endMu < endMu ? segArray[seg].endMu : endMu);

      // Resolve this segment
      printf("run %d, camCol %d, rerun %d\n", segArray[seg].run,
	     segArray[seg].camCol, segArray[seg].rerun);
      int code;
      if (segArray[seg].status == TS_PRIMARY)
	code = tsPrimarySegmentResolve(framesH, segStartMu, segEndMu,
				       lambdaMin, lambdaMax, etaMin, etaMax,
				       *segCont, dbSegments);
      else
	code = tsSegmentResolve(framesH, segStartMu, segEndMu, lambdaMin,
				lambdaMax, etaMin, etaMax,
				segArray[seg].status, *segCont,
				dbSegments, NULL, NULL);
      if (code != SH_SUCCESS)
	{
	  // Error.  Delete all of the segments from the database.
	  for (uint i = 0; i < dbSegments.size(); i++)
	    delete (arSegment*) dbSegments[i];
	  shTclInterpAppendWithErrStack(interp);
	  return TCL_ERROR;
	}
    }

  // Create the chunk
  ooHandle(arChunk) *chunk = new ooHandle(arChunk);
  *chunk = new(*segCont) arChunk(stripe, startMu, endMu, dbSegments);
  if (! (*chunk).isValid())
    {
      for (uint i = 0; i < dbSegments.size(); i++)
	delete (arSegment*) dbSegments[i];
      delete chunk;
      Tcl_SetResult(interp, "chunkResolve: couldn't create chunk",
		    TCL_VOLATILE);
      return TCL_ERROR;
    }

  // Return a handle to it
  char chunkHandleName[HANDLE_NAMELEN];
  if (shTclHandleNew(interp, chunkHandleName, "ooHandle(arChunk)",
		     (void *)chunk) != TCL_OK)
    {
      // Error.  Delete the chunk and return an error.
      delete (arChunk*) *chunk;
      delete chunk;
      Tcl_SetResult(interp, "chunkResolve: error allocating chunk TCL handle",
		    TCL_VOLATILE);
      return TCL_ERROR;
    }
  Tcl_SetResult(interp, chunkHandleName, TCL_VOLATILE);
  return TCL_OK;
}

/*============================================================================
**<AUTO EXTRACT>
**
** TCL VERB: repeatChunkResolve
**
**<HTML>
**	Resolve a repeat chunk --- that is, a chunk composed of segments
**	overlapping previously resolved primary chunks, and thus all segments
**	in the new chunk will be secondary chunks.
**	The list of segments comprising the chunk are specified on input as
**	a TCL list, one element per segment.  Each element in the input TCL
**	list is itself a TCL list, where the first element is
**	the run number, the second element the camera column, the third
**	element the rerun number, and the fourth and fifth elements the
**	starting and ending great circle longitude (mu, integer arcsecs),
**	respectively, for the "repeat" segment.  Normally, the input list
**	of segments is the returned list fetched by running
**	"repeatSegmentsGet".  No error checking is performed on the input
**	segment list, so it had better be valid.  A handle to the new chunk
**	is returned.
**</HTML>
**
**</AUTO>
**============================================================================
*/
static char repeatChunkResolveCmd[] = "repeatChunkResolve";
static int repeatChunkResolveFlg = FTCL_ARGV_NO_LEFTOVERS;
static ftclArgvInfo repeatChunkResolveTbl[] = {
   {NULL, FTCL_ARGV_HELP, NULL, NULL,
    "Resolve a 'repeat' chunk\n"},
   {"<segments>", FTCL_ARGV_STRING, NULL, NULL,
    "TCL list of segments comprising the new chunk"},
   {"<minWidth>", FTCL_ARGV_INT, NULL, NULL,
    "minimum width to accept for a segment (arcsecs)"},
   {NULL, FTCL_ARGV_END, NULL, NULL, NULL}
};

static int
tclRepeatChunkResolve (ClientData clientData,
		       Tcl_Interp *interp,
		       int argc,
		       char **argv)
{
  char *segments = NULL;
  int minWidth;
  int status;

  // Parse the arguments
  repeatChunkResolveTbl[1].dst = &segments;
  repeatChunkResolveTbl[2].dst = &minWidth;
  if ((status = shTclParseArgv(interp, &argc, argv, repeatChunkResolveTbl,
			       repeatChunkResolveFlg, repeatChunkResolveCmd))
      != FTCL_ARGV_SUCCESS)
    return status;

  // Fetch the global TCL variable "photo_frames" for container with all
  // Frames Pipeline Runs.  Then fetch the associated handle.
  char *photo_frames = Tcl_GetVar(interp, "photo_frames", TCL_GLOBAL_ONLY);
  if (photo_frames == NULL)
    {
      Tcl_AppendResult(interp, repeatChunkResolveCmd,
		       ": global variable $photo_frames doesn't exist", NULL);
      return TCL_ERROR;
    }
  ooHandle(ooContObj) *fpCont;
  if (shTclAddrGetFromName (interp, photo_frames, (void **)&fpCont,
			    "ooHandle(ooContObj)") != TCL_OK)
    {
      Tcl_AppendResult(interp, repeatChunkResolveCmd,
		       ": error getting handle to $photo_frames", NULL);
      return TCL_ERROR;
    }

  // Fetch the global TCL variable "photo_segments" for container with all
  // resolved segments.  Then fetch the associated handle.
  char *photo_segments = Tcl_GetVar(interp, "photo_segments", TCL_GLOBAL_ONLY);
  if (photo_segments == NULL)
    {
      Tcl_AppendResult(interp, repeatChunkResolveCmd,
		       ": global variable $photo_segments doesn't exist",
		       NULL);
      return TCL_ERROR;
    }
  ooHandle(ooContObj) *segCont;
  if (shTclAddrGetFromName (interp, photo_segments, (void **)&segCont,
			    "ooHandle(ooContObj)") != TCL_OK)
    {
      Tcl_AppendResult(interp, repeatChunkResolveCmd,
		       ": error getting handle to $photo_segments", NULL);
      return TCL_ERROR;
    }

  // Set up cosmic color for use by atTransApply().
  taTransCosmicSet(cosmicColor, cosmicScatter);

  // Set "endMu" for this chunk to one more than the highest "endMu" number
  // in the current set of repeat chunks.  This gives each repeat chunk a
  // unique identifying "endMu" number.
  ooItr(arChunk) chunkItr;
  if (chunkItr.scan(*segCont, oocRead,oocPublic, "stripe_ == 0") != oocSuccess)
    {
      Tcl_AppendResult(interp, repeatSegmentsGetCmd,
		       ": couldn't initialize iterator over chunks", NULL);
      return TCL_ERROR;
    }
  int chunkEndMu = 0;
  while (chunkItr.next())
    if (chunkItr->endMu() > chunkEndMu) chunkEndMu = chunkItr->endMu();
  chunkEndMu++;

  // Fetch TCL list of segments
  char **listArgv;
  int nSegments;
  if (Tcl_SplitList(interp, segments, &nSegments, &listArgv) != TCL_OK)
    {
      Tcl_AppendResult(interp, repeatChunkResolveCmd,
		       ": couldn't parse list of segments", NULL);
      return TCL_ERROR;
    }

  // Error if empty list specified
  if (nSegments == 0)
    {
      Tcl_AppendResult(interp, repeatChunkResolveCmd,
		       ": empty list of segments specified", NULL);
      return TCL_ERROR;
    }
  
  // Allocate array for this chunk's segments
  ooTVArray(ooHandle(arSegment)) dbSegments;

  // For each segment ...
  int run, camCol, rerun, startMu, endMu;
  for (int iseg = 0; iseg < nSegments; iseg++)
    {
      if (sscanf(listArgv[iseg], "%d %d %d %d %d", &run, &camCol, &rerun,
		 &startMu, &endMu) != 5)
	{
	  for (uint i = 0; i < dbSegments.size(); i++)
	    delete (arSegment*) dbSegments[i];
	  Tcl_AppendResult(interp, repeatChunkResolveCmd,
			   ": illegal segment specification", NULL);
	  return TCL_ERROR;
	}

      // Skip segment if less than minimum width
      if (endMu - startMu < minWidth)
	{
	  printf("run %d, camCol %d, rerun %d: skipping: less than minWidth\n",
		 run, camCol, rerun);
	  continue;
	}

      // Get the handle to the run
      char predicate[200];
      sprintf(predicate, "postageStampCalibration_->sSCPipelineRun_->imagingRun_->id_ == %d && postageStampCalibration_->sSCPipelineRun_->camCol_ == %d && rerun_ == %d", run, camCol, rerun);
      ooItr(arFramesPipeRun) itr;
      if (itr.scan(*fpCont, oocRead, oocPublic, predicate) != oocSuccess)
	{
	  for (uint i = 0; i < dbSegments.size(); i++)
	    delete (arSegment*) dbSegments[i];
	  Tcl_AppendResult(interp, repeatChunkResolveCmd,
			   ": couldn't get run handle", NULL);
	  return TCL_ERROR;
	}
      if (! itr.next())
	{
	  for (uint i = 0; i < dbSegments.size(); i++)
	    delete (arSegment*) dbSegments[i];
	  Tcl_AppendResult(interp, repeatChunkResolveCmd,
			   ": couldn't get run handle", NULL);
	  return TCL_ERROR;
	}
      ooHandle(arFramesPipeRun) framesH = itr;
      if (itr.next())
	{
	  for (uint i = 0; i < dbSegments.size(); i++)
	    delete (arSegment*) dbSegments[i];
	  Tcl_AppendResult(interp, repeatChunkResolveCmd,
			   ": impossible error --- two runs with same run, camCol, and rerun numbers", NULL);
	  return TCL_ERROR;
	}

      // Resolve this segment
      printf("run %d, camCol %d, rerun %d\n", run, camCol, rerun);
      if (tsSegmentResolve(framesH, startMu, endMu, 0., 0., 0., 0.,
			   TS_SECONDARY, *segCont, dbSegments, NULL, NULL)
	  != SH_SUCCESS)
	{
	  // Error.  Delete all of the segments from the database.
	  for (uint i = 0; i < dbSegments.size(); i++)
	    delete (arSegment*) dbSegments[i];
	  shTclInterpAppendWithErrStack(interp);
	  return TCL_ERROR;
	}
    }

  // Create the chunk
  ooHandle(arChunk) *chunk = new ooHandle(arChunk);
  *chunk = new(*segCont) arChunk(0, 0, chunkEndMu, dbSegments);
  if (! (*chunk).isValid())
    {
      for (uint i = 0; i < dbSegments.size(); i++)
	delete (arSegment*) dbSegments[i];
      delete chunk;
      Tcl_SetResult(interp, "repeatChunkResolve: couldn't create chunk",
		    TCL_VOLATILE);
      return TCL_ERROR;
    }

  // Return a handle to it
  char chunkHandleName[HANDLE_NAMELEN];
  if (shTclHandleNew(interp, chunkHandleName, "ooHandle(arChunk)",
		     (void *)chunk) != TCL_OK)
    {
      // Error.  Delete the chunk and return an error.
      delete (arChunk*) *chunk;
      delete chunk;
      Tcl_SetResult(interp, "repeatChunkResolve: error allocating chunk TCL handle",
		    TCL_VOLATILE);
      return TCL_ERROR;
    }
  Tcl_SetResult(interp, chunkHandleName, TCL_VOLATILE);
  return TCL_OK;
}

/*============================================================================
**<AUTO EXTRACT>
**
** TCL VERB: chunkUnresolve
**
**<HTML>
**	Delete a chunk from the database, clearing the flags for objects
**	belonging to that chunk that were set when the chunk was resolved.
**	This may be done only if target selection has not been run on this
**	chunk.
**</HTML>
**
**</AUTO>
**============================================================================
*/
static char chunkUnresolveCmd[] = "chunkUnresolve";
static int chunkUnresolveFlg = FTCL_ARGV_NO_LEFTOVERS;
static ftclArgvInfo chunkUnresolveTbl[] = {
   {NULL, FTCL_ARGV_HELP, NULL, NULL,
    "Unresolve a chunk\n"},
   {"<chunk>", FTCL_ARGV_STRING, NULL, NULL,
    "Handle to chunk to delete"},
   {NULL, FTCL_ARGV_END, NULL, NULL, NULL}
};

static int
tclChunkUnresolve (ClientData clientData,
		   Tcl_Interp *interp,
		   int argc,
		   char **argv)
{
  ooHandle(arChunk) *chunk = NULL;
  char *chunkHandle = NULL;
  int status;

  // Parse the arguments
  chunkUnresolveTbl[1].dst = &chunkHandle;
  if ((status = shTclParseArgv(interp, &argc, argv, chunkUnresolveTbl,
			       chunkUnresolveFlg, chunkUnresolveCmd))
      != FTCL_ARGV_SUCCESS)
    return status;
   
  // Get handle to chunk
  if (shTclAddrGetFromName (interp, chunkHandle, (void **)&chunk,
			    "ooHandle(arChunk)") != TCL_OK)
    {
      Tcl_AppendResult(interp, chunkUnresolveCmd,
		       ": error getting handle to chunk", NULL);
      return TCL_ERROR;
    }
  
  // Verify that this run has not been through target selection
  ooHandle(arProduct) report = (*chunk)->selectTargets();
  if (report.isValid() == oocTrue)
    {
      Tcl_AppendResult(interp, chunkUnresolveCmd,
		       ": chunk has been through target selection --- can't delete",
		       NULL);
      return TCL_ERROR;
    }
   
  // Unresolve it.  Just delete it.  The destructor takes care of the rest.
  delete (arChunk*) (*chunk);
  p_shTclHandleDel(interp, chunkHandle);
  Tcl_SetResult(interp, "", TCL_VOLATILE);
  return TCL_OK;
}

//****************************************************************************/
//
// Declare my new tcl verbs to tcl
//
void
tsTclResolveDeclare(Tcl_Interp *interp) 
{
  char *tclHelpFacil = "ts";
  shTclDeclare(interp, segmentsGetCmd,
	       (Tcl_CmdProc *)tclSegmentsGet,
	       (ClientData) 0,	(Tcl_CmdDeleteProc *)NULL, tclHelpFacil,
	       shTclGetArgInfo(interp, segmentsGetTbl, segmentsGetFlg,
			       segmentsGetCmd),
	       shTclGetUsage(interp, segmentsGetTbl, segmentsGetFlg,
			     segmentsGetCmd));

  shTclDeclare(interp, resolvableChunksGetCmd,
	       (Tcl_CmdProc *)tclResolvableChunksGet,
	       (ClientData) 0,	(Tcl_CmdDeleteProc *)NULL, tclHelpFacil,
	       shTclGetArgInfo(interp, resolvableChunksGetTbl,
			       resolvableChunksGetFlg,
			       resolvableChunksGetCmd),
	       shTclGetUsage(interp, resolvableChunksGetTbl,
			     resolvableChunksGetFlg,
			     resolvableChunksGetCmd));

  shTclDeclare(interp, repeatSegmentsGetCmd,
	       (Tcl_CmdProc *)tclRepeatSegmentsGet,
	       (ClientData) 0,	(Tcl_CmdDeleteProc *)NULL, tclHelpFacil,
	       shTclGetArgInfo(interp, repeatSegmentsGetTbl,
			       repeatSegmentsGetFlg,
			       repeatSegmentsGetCmd),
	       shTclGetUsage(interp, repeatSegmentsGetTbl,
			     repeatSegmentsGetFlg,
			     repeatSegmentsGetCmd));

  shTclDeclare(interp, chunkResolveCmd,
	       (Tcl_CmdProc *)tclChunkResolve,
	       (ClientData) 0,	(Tcl_CmdDeleteProc *)NULL, tclHelpFacil,
	       shTclGetArgInfo(interp, chunkResolveTbl, chunkResolveFlg,
			       chunkResolveCmd),
	       shTclGetUsage(interp, chunkResolveTbl, chunkResolveFlg,
			     chunkResolveCmd));

  shTclDeclare(interp, repeatChunkResolveCmd,
	       (Tcl_CmdProc *)tclRepeatChunkResolve,
	       (ClientData) 0,	(Tcl_CmdDeleteProc *)NULL, tclHelpFacil,
	       shTclGetArgInfo(interp, repeatChunkResolveTbl,
			       repeatChunkResolveFlg,
			       repeatChunkResolveCmd),
	       shTclGetUsage(interp, repeatChunkResolveTbl,
			     repeatChunkResolveFlg,
			     repeatChunkResolveCmd));

  shTclDeclare(interp, chunkUnresolveCmd,
	       (Tcl_CmdProc *)tclChunkUnresolve,
	       (ClientData) 0,	(Tcl_CmdDeleteProc *)NULL, tclHelpFacil,
	       shTclGetArgInfo(interp, chunkUnresolveTbl, chunkUnresolveFlg,
			       chunkUnresolveCmd),
	       shTclGetUsage(interp, chunkUnresolveTbl, chunkUnresolveFlg,
			     chunkUnresolveCmd));
}
