# include "addstar.h"

# define RESETTIME { gettimeofday (&startTimer, (void *) NULL); }

// LARGEFILES: this program currently limits Nstars (input file) to < 2^31
int main (int argc, char **argv) {

  unsigned int Nstars, Nsubset;
  int Nmatch, status, loadObjects;
  off_t i, Nimages;
  off_t Naverage, Nmeasure, Nlensing;
  Stars *stars, **subset;
  Image *images;
  Catalog catalog;
  FITS_DB db;
  AddstarClientOptions options;

  SkyTable *sky = NULL;
  SkyList *skylist = NULL;
  SkyList *newlist = NULL;

  struct timeval startAddstar, stopAddstar;
  gettimeofday (&startAddstar, (void *) NULL);

  INITTIME;

  SetSignals ();
  options = ConfigInit (&argc, argv);
  options = args (argc, argv, options);

  if (options.mode == ADDSTAR_MODE_CREATE_ID) {
    addstar_create_ID ();
    exit (0);
  }

  sky = SkyTableLoadOptimal (CATDIR, SKY_TABLE, GSCFILE, TRUE, SKY_DEPTH, VERBOSE);
  if (sky == NULL) {
      fprintf (stderr, "ERROR: unable to load sky table data\n");
      exit (2);
  }
  SkyTableSetFilenames (sky, CATDIR, "cpt");
  
  if (options.mode == ADDSTAR_MODE_RESORT) {
    resort_catalogs (&options, sky);
    exit (0);
  }

  MARKTIME ("init and config: %f sec\n", dtime); RESETTIME; 

  stars = NULL;

  /*** load in the new data (images, stars) ***/
  switch (options.mode) {
    case ADDSTAR_MODE_IMAGE:
      stars = LoadStars (argv[1], &Nstars, &images, &Nimages, &options);
      MARKTIME ("load smf: %f sec\n", dtime); RESETTIME; 

      // set and update the imageID sequence
      UpdateImageIDs (stars, Nstars, images, Nimages);

      if ((DUMP != NULL) && !strcmp (DUMP, "rawstars")) dump_rawstars (stars, Nstars);
      for (i = 0; i < Nimages; i++) {
	newlist = SkyListByImage (sky, -1, &images[i]);
	SkyListMerge (&skylist, newlist);
	if (VERBOSE) fprintf (stderr, "added "OFF_T_FMT" regions to yield "OFF_T_FMT" total\n",  newlist[0].Nregions,  skylist[0].Nregions);
	SkyListFree (newlist);
      }
      ImageOptions (&options, images, Nimages);
      break;
    case ADDSTAR_MODE_REFLIST:
      stars = grefstars (argv[1], options.photcode, &Nstars);
      skylist = SkyListForStars (sky, -1, stars, Nstars);
      break;
    case ADDSTAR_MODE_REFCAT:
      skylist = SkyListByPatch (sky, -1, &UserPatch);
      break;
    case ADDSTAR_MODE_FAKEIMAGE:
      images = fakeimage (argv[1], &Nimages, options.photcode);
      ALLOCATE (skylist, SkyList, 1);
      skylist[0].Nregions = 0;
      skylist[0].ownElements = FALSE;
      break;

    default:
      fprintf (stderr, "ERROR: invalid mode\n");
      exit (2);
  }

  // in these cases, limit the sky catalogs to an existing subset
  if (options.only_match || options.existing_regions) {
    SkyList *tmp;
    tmp = SkyListExistingSubset (skylist, CATDIR);
    SkyListFree (skylist);
    skylist = tmp;
  }
  if (VERBOSE) fprintf (stderr, "writing to "OFF_T_FMT" regions\n",  skylist[0].Nregions);

  /* don't load the object tables for only_images, unless we are getting the calibration. */
  loadObjects = !options.only_images || options.calibrate;

  /* match stars to existing catalog data (or otherwise manipulate catalog data) */
  Nmatch = Naverage = Nmeasure = Nlensing = 0;
  for (i = 0; loadObjects && (i < skylist[0].Nregions); i++) {

    // set the parameters which guide catalog open/load/create
    catalog.filename  = skylist[0].filename[i];
    catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
    catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
    catalog.catflags  = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF | LOAD_LENSING;
    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();

    // XXX need to do something to enforce consistency.  we can change options.update
    // based on the value of catmode as below.  however, this seems to be a problem if
    // there is an inconsistency between the config values and the existing db.  not so
    // bad if the existing db is SPLIT, but a problem if we request SPLIT and the existing
    // db is MEF.
    if (catalog.catmode != DVO_MODE_SPLIT) options.update = FALSE;

    if (options.update) catalog.catflags = LOAD_AVES | LOAD_MISS | LOAD_SECF;

    // open as read or write, depending on desire
    if (options.only_images && options.calibrate) {
      if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE, "r")) {
	continue;
      }
    } else {
      // an error exit status here is a significant error (disk I/O or file access)
      // XXX should this be "a" for options.update?
      if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE, "w")) {
	fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
	exit (2);
      }
    }
    if (VERBOSE) MARKTIME ("load cpt: %f sec\n", dtime); RESETTIME; 

    // Naves_disk == 0 implies an empty catalog file
    // for only_match, skip empty catalogs
    if ((catalog.Naves_disk == 0) && options.only_match) {
      dvo_catalog_unlock (&catalog);
      dvo_catalog_free (&catalog);
      continue;
    }

    switch (options.mode) {
      case ADDSTAR_MODE_IMAGE:
	Nsubset = Nstars;
	if (options.closest) {
	  Nmatch += find_matches_closest (skylist[0].regions[i], stars, Nstars, &catalog, options);
	} else {
	  Nmatch += find_matches (skylist[0].regions[i], stars, Nstars, &catalog, options);
	}
	break;
      case ADDSTAR_MODE_REFCAT:
	stars = greference (argv[1], skylist[0].regions[i], options.photcode, &Nstars);
      case ADDSTAR_MODE_REFLIST:
	subset = find_subset (skylist[0].regions[i], stars, Nstars, &Nsubset);
	if (options.closest) {
	  Nmatch += find_matches_closest_refstars (skylist[0].regions[i], subset, Nsubset, &catalog, options);
	} else {
	  Nmatch += find_matches_refstars (skylist[0].regions[i], subset, Nsubset, &catalog, options);
	}
	if (Nsubset) free (subset);
	break;
    }
    if (VERBOSE) MARKTIME ("match stars: %f sec\n", dtime); RESETTIME; 

    /* report total updated values */
    Naverage += catalog.Naverage;
    Nmeasure += catalog.Nmeasure;
    Nlensing += catalog.Nlensing;

    // write out catalog, if appropriate
    if (Nsubset && !options.only_images) {
      SetProtect (TRUE);
      if (options.update) {
	catalog.catflags  = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF | LOAD_LENSING;
	dvo_catalog_update (&catalog, VERBOSE);
      } else {
	dvo_catalog_save (&catalog, VERBOSE);
      }
      SetProtect (FALSE);
    }
    dvo_catalog_unlock (&catalog);
    dvo_catalog_free (&catalog);
    if (VERBOSE) MARKTIME ("save cpt: %f sec\n", dtime); RESETTIME; 

    if (options.mode == ADDSTAR_MODE_REFCAT) free (stars);
  }

  // We only measure a single value for the entire mosaic (add all images to this function)
  if (options.calibrate) { FindCalibration (&images[0]); }

  /*** update the image table ***/
  /* setup image table format and lock */
  db.mode   = dvo_catalog_catmode (CATMODE);
  db.format = dvo_catalog_catformat (CATFORMAT);
  status    = dvo_image_lock (&db, ImageCat, 3600.0, LCK_XCLD);  // shorter timeout?
  if (!status) Shutdown ("ERROR: failure to lock image catalog %s", db.filename);

  /* load or create the image table */
  if (db.dbstate == LCK_EMPTY) {
    if (VERBOSE) fprintf (stderr, "can't find %s, creating a new one\n", ImageCat);
    dvo_image_create (&db, GetZeroPoint());
  } else {
    if (!dvo_image_load (&db, VERBOSE, FORCE_READ)) {
      Shutdown ("can't read image catalog %s", db.filename);
    }
  }

  /* add the new images and save */
  if (options.mode == ADDSTAR_MODE_IMAGE) {
    dvo_image_addrows (&db, images, Nimages);
    SetProtect (TRUE);
    dvo_image_update (&db, VERBOSE);
    SetProtect (FALSE);
  }
  dvo_image_unlock (&db); /* unlock? */

  gettimeofday (&stopAddstar, (void *) NULL);
  float dtime = DTIME (stopAddstar, startAddstar);
  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec for %5d stars (%5d matches), "OFF_T_FMT" average, "OFF_T_FMT" measure, "OFF_T_FMT" lensing\n", dtime, Nstars, Nmatch,  Naverage,  Nmeasure, Nlensing);

  exit (0);
}

/* names:
   catalog - existing object db table
   regions - sky area which may or may not contain data
   patch   - RA,DEC bounded portion of sky
*/

// add in case of failures:
// ohana_memcheck (FALSE);
