# include <dvo.h>
# define DEBUG 1

void dvo_catalog_test (Catalog *catalog, int halt) {

  Catalog *subcat;

  // fprintf (stderr, "catalog: Naverage = %d, average = %zx\n", catalog[0].Naverage, (size_t) catalog[0].average);
  // fprintf (stderr, "catalog: Nmeasure = %d, measure = %zx\n", catalog[0].Nmeasure, (size_t) catalog[0].measure);
  // fprintf (stderr, "catalog: Nmissing = %d, missing = %zx\n", catalog[0].Nmissing, (size_t) catalog[0].missing);
  // fprintf (stderr, "catalog: Nsecfilt = %d, secfilt = %zx\n", catalog[0].Nsecfilt, (size_t) catalog[0].secfilt);

  if (!catalog[0].measure || !catalog[0].secfilt) {
    fprintf (stderr, "error: %s\n", catalog[0].filename);
    if (halt) abort ();
  }

  // XXX test that things are correctly initialized
  if (catalog[0].catmode != DVO_MODE_SPLIT) return;

  subcat = catalog[0].measure_catalog;
  if (subcat) {
    if (subcat[0].measure_catalog || subcat[0].secfilt_catalog || subcat[0].missing_catalog) {
      fprintf (stderr, "error in init\n");
      abort ();
    }
  }
  subcat = catalog[0].missing_catalog;
  if (subcat) {
    if (subcat[0].measure_catalog || subcat[0].secfilt_catalog || subcat[0].missing_catalog) {
      fprintf (stderr, "error in init\n");
      abort ();
    }
  }
  subcat = catalog[0].secfilt_catalog;
  if (subcat) {
    if (subcat[0].measure_catalog || subcat[0].secfilt_catalog || subcat[0].missing_catalog) {
      fprintf (stderr, "error in init\n");
      abort ();
    }
  }
}

int dvo_catalog_catformat (char *catformat) {
  
  /* set the specified CATFORMAT */
  if (!strcasecmp (catformat, "INTERNAL"))        return (DVO_FORMAT_INTERNAL);
  if (!strcasecmp (catformat, "LONEOS"))    	  return (DVO_FORMAT_LONEOS);
  if (!strcasecmp (catformat, "ELIXIR"))    	  return (DVO_FORMAT_ELIXIR);
  if (!strcasecmp (catformat, "PANSTARRS_DEV_0")) return (DVO_FORMAT_PANSTARRS_DEV_0);
  if (!strcasecmp (catformat, "PANSTARRS_DEV_1")) return (DVO_FORMAT_PANSTARRS_DEV_1);
  if (!strcasecmp (catformat, "PS1_DEV_1"))       return (DVO_FORMAT_PS1_DEV_1);
  if (!strcasecmp (catformat, "PS1_DEV_2"))       return (DVO_FORMAT_PS1_DEV_2);
  if (!strcasecmp (catformat, "PS1_V1"))          return (DVO_FORMAT_PS1_V1);
  return (DVO_FORMAT_UNDEF);
}

int dvo_catalog_catmode (char *catmode) {

  /* set the specified CATMODE */
  if (!strcasecmp (catmode, "RAW"))   return (DVO_MODE_RAW);
  if (!strcasecmp (catmode, "MEF"))   return (DVO_MODE_MEF);
  if (!strcasecmp (catmode, "SPLIT")) return (DVO_MODE_SPLIT);
  return (DVO_MODE_UNDEF);
}

// init all data, or just catalog data
void dvo_catalog_init (Catalog *catalog, int complete) {

  // the following are used to guide open/create/load
  if (complete) {
    catalog[0].f = NULL;
    catalog[0].filename = NULL;

    catalog[0].lockmode = 0;
    catalog[0].catmode  = 0;
    catalog[0].catformat = 0;
    catalog[0].catflags = 0;
    catalog[0].sorted = 0;
    catalog[0].Nsecfilt = 0;
  }

  gfits_init_header (&catalog[0].header);

  // the following describe the catalog files on disk
  catalog[0].average = NULL;
  catalog[0].measure = NULL; 
  catalog[0].missing = NULL; 
  catalog[0].secfilt = NULL;
  
  catalog[0].objID = 0;
  catalog[0].catID = 0;

  catalog[0].Naverage = 0;
  catalog[0].Nmeasure = 0;
  catalog[0].Nmissing = 0;
  catalog[0].Nsecf_mem = 0;

  catalog[0].Naves_disk = 0;
  catalog[0].Nmeas_disk = 0;
  catalog[0].Nmiss_disk = 0;
  catalog[0].Nsecf_disk = 0;

  catalog[0].Naves_off  = 0;
  catalog[0].Nmeas_off  = 0;
  catalog[0].Nmiss_off  = 0;
  catalog[0].Nsecf_off  = 0;

  /* pointers to SPLIT data files */
  catalog[0].measure_catalog = NULL;
  catalog[0].missing_catalog = NULL;
  catalog[0].secfilt_catalog = NULL;

  /* pointers for data manipulation */
  catalog[0].found = NULL;
  catalog[0].image = NULL;
  catalog[0].mosaic = NULL;
  catalog[0].X = NULL;
  catalog[0].Y = NULL;
}

/* possible exit status for lock_catalog: 
   DVO_CAT_OPEN_FAIL - failure (including lock failure)
   DVO_CAT_OPEN_OK - success
   DVO_CAT_OPEN_EMPTY - empty file (file may be open or closed!) 
*/
int dvo_catalog_lock (Catalog *catalog, int lockmode) {

  int dbstate;

  /* record lockmode choice */
  catalog[0].lockmode = lockmode;

  /* set lock on database, create stream f */
  // fprintf (stderr, "locking: %s\n", catalog[0].filename);
  catalog[0].f = fsetlockfile (catalog[0].filename, 3600.0, catalog[0].lockmode, &dbstate);

  if (dbstate == LCK_MISSING) return (DVO_CAT_OPEN_EMPTY);
  if (dbstate == LCK_EMPTY)   return (DVO_CAT_OPEN_EMPTY);
  if (catalog[0].f == NULL)   return (DVO_CAT_OPEN_FAIL);

  fseek (catalog[0].f, 0, SEEK_SET);
  return (DVO_CAT_OPEN_OK);
}

int dvo_catalog_unlock (Catalog *catalog) {

  int fd, dbstate;
  struct stat filestat;

  if (catalog[0].f == (FILE *) NULL) return (2);
  fflush (catalog[0].f);
  // fprintf (stderr, "unlocking: %s\n", catalog[0].filename);

  // attempt to unlink an empty file
  fd = fileno (catalog[0].f);
  if (!fstat (fd, &filestat)) {
    if (filestat.st_size == 0) {
      unlink (catalog[0].filename);
    }
  }

  fclearlockfile (catalog[0].filename, catalog[0].f, catalog[0].lockmode, &dbstate);

  if (catalog[0].catmode == DVO_MODE_SPLIT) {
    if (catalog[0].measure_catalog != NULL) dvo_catalog_unlock (catalog[0].measure_catalog);
    if (catalog[0].missing_catalog != NULL) dvo_catalog_unlock (catalog[0].missing_catalog);
    if (catalog[0].secfilt_catalog != NULL) dvo_catalog_unlock (catalog[0].secfilt_catalog);
  }
  return (1);
}

// open an existing catalog file or or create a new one as needed (iomode == "w")
// returns FALSE if a catalog could not be opened
// returns TRUE if the catalog was empty
// XXX allow stdout as destination (don't lock)
enum {DVO_OPEN_NONE, DVO_OPEN_READ, DVO_OPEN_WRITE, DVO_OPEN_UPDATE};
int dvo_catalog_open (Catalog *catalog, SkyRegion *region, int VERBOSE, char *iomode) {

  int Nsecfilt, mode;

  mode = DVO_OPEN_NONE;
  if (!strcasecmp (iomode, "r")) mode = DVO_OPEN_READ;
  if (!strcasecmp (iomode, "w")) mode = DVO_OPEN_WRITE;
  if (!strcasecmp (iomode, "a")) mode = DVO_OPEN_UPDATE;
  if (mode == DVO_OPEN_NONE) return (FALSE);

  // save the expected Nsecfilt value for testing
  Nsecfilt = catalog[0].Nsecfilt;

  dvo_catalog_init (catalog, FALSE);

  catalog[0].lockmode  = LCK_XCLD;
  if (mode == DVO_OPEN_READ) catalog[0].lockmode  = LCK_SOFT;
  
  // XXX make a backup?  always?
  if (!check_file_access (catalog[0].filename, TRUE, VERBOSE)) {
    if (VERBOSE) fprintf (stderr, "no permission to access %s\n", catalog[0].filename);
    return (FALSE);
  }

  if (VERBOSE) fprintf (stderr, "opening %s\n", catalog[0].filename);
  
  switch (dvo_catalog_lock (catalog, catalog[0].lockmode)) {
  case DVO_CAT_OPEN_FAIL:
    if (VERBOSE) fprintf (stderr, "can't lock file %s\n", catalog[0].filename);
    return (FALSE);
  case DVO_CAT_OPEN_OK:
    if (!dvo_catalog_load (catalog, VERBOSE)) {
      if (VERBOSE) fprintf (stderr, "failure loading catalog\n");
      return (FALSE);
    }
    if (!dvo_catalog_check (catalog, Nsecfilt, TRUE)) {
      if (VERBOSE) fprintf (stderr, "can't reduce number of secondary filters\n");
      return (FALSE);
    }
    if (VERBOSE) fprintf (stderr, "loaded existing file %s\n", catalog[0].filename);
    break;
  case DVO_CAT_OPEN_EMPTY:
    if ((mode == DVO_OPEN_READ) || (mode == DVO_OPEN_UPDATE)) return (TRUE);
    catalog[0].Nsecfilt = Nsecfilt;
    dvo_catalog_create (region, catalog); /* fills in new header info */
    if (VERBOSE) fprintf (stderr, "creating new file %s\n", catalog[0].filename);
    break;
  }
  return (TRUE);
}

// load an existing dvo_catalog currenly, the catmode information is carried per
// catalog.  the user-set value of catmode will set the output mode for new
// tables an old table will keep its mode.
int dvo_catalog_load (Catalog *catalog, int VERBOSE) {
  
  int Naxis, split, status;
  char measure[80];

  dvo_catalog_init (catalog, FALSE);

  // load the main catalog header, determine characteristics
  if (!gfits_fread_header (catalog[0].f, &catalog[0].header)) {
    if (VERBOSE) fprintf (stderr, "failure to read main catalog header\n");
    return (FALSE);
  }
  // check if the table has been sorted or not 
  status = gfits_scan (&catalog[0].header, "SORTED", "%t", 1, &catalog[0].sorted);
  if (!status) catalog[0].sorted = TRUE;

  // determine catmode
  if (!gfits_scan (&catalog[0].header, "NAXIS", "%d", 1, &Naxis)) {
    if (VERBOSE) fprintf (stderr, "can't determine catalog db mode\n");
    return (FALSE);
  }
  catalog[0].catmode = DVO_MODE_MEF;
  split = gfits_scan (&catalog[0].header, "MEASURE", "%s", 1, measure);
  if (split) catalog[0].catmode = DVO_MODE_SPLIT;
  if (Naxis == 2) catalog[0].catmode = DVO_MODE_RAW;
  /* don't use Naxis == 2 and split mode! */

  switch (catalog[0].catmode) {
    case DVO_MODE_RAW:
      if (VERBOSE) fprintf (stderr, "reading catalog (mode DVO_MODE_RAW)\n");
      if (!dvo_catalog_load_raw (catalog, VERBOSE)) {
	if (VERBOSE) fprintf (stderr, "failed to load RAW catalog file %s\n", catalog[0].filename);
	return (FALSE);
      }
      break;
    case DVO_MODE_MEF:
      if (VERBOSE) fprintf (stderr, "reading catalog (mode DVO_MODE_MEF)\n");
      if (!dvo_catalog_load_mef (catalog, VERBOSE)) {
	if (VERBOSE) fprintf (stderr, "failed to load MEF catalog file %s\n", catalog[0].filename);
	return (FALSE);
      }
      break;
    case DVO_MODE_SPLIT:
      if (VERBOSE) fprintf (stderr, "reading catalog (mode DVO_MODE_SPLIT)\n");
      if (!dvo_catalog_load_split (catalog, VERBOSE)) {
	if (VERBOSE) fprintf (stderr, "failed to load SPLIT catalog file %s\n", catalog[0].filename);
	return (FALSE);
      }
      break;
    default:
      fprintf (stderr, "error getting catalog mode\n");
      exit (2);
  }
  return (TRUE);
}

// write out the data, unlink if empty?  'save' means: write out all data currently in
// memory.  NOTE: this is currently not always possible: for non-SPLIT mode files, this
// operation may require expanding the file size, which does not automatically happen
int dvo_catalog_save (Catalog *catalog, char VERBOSE) {

  // set the 'sorted' header keyword
  gfits_modify (&catalog[0].header, "SORTED",  "%t", 1, catalog[0].sorted);

  // XXX handle return status
  switch (catalog[0].catmode) {
    case DVO_MODE_RAW:
      dvo_catalog_save_raw (catalog, VERBOSE);
      break;
    case DVO_MODE_MEF:
      dvo_catalog_save_mef (catalog, VERBOSE);
      break;
    case DVO_MODE_SPLIT:
      dvo_catalog_save_split (catalog, VERBOSE);
      break;
    default:
      fprintf (stderr, "invalid catalog mode\n");
      exit (2);
  }
  return (TRUE);
}

// write out the data, assuming the in-memory data represents all data for the table.
// NOTE: This operation may truncate the file and delete entries.
int dvo_catalog_save_complete (Catalog *catalog, char VERBOSE) {

  // set the 'sorted' header keyword
  gfits_modify (&catalog[0].header, "SORTED",  "%t", 1, catalog[0].sorted);

  // XXX handle return status
  switch (catalog[0].catmode) {
    case DVO_MODE_RAW:
      dvo_catalog_save_raw (catalog, VERBOSE);
      break;
    case DVO_MODE_MEF:
      dvo_catalog_save_mef (catalog, VERBOSE);
      break;
    case DVO_MODE_SPLIT:
      dvo_catalog_save_split_complete (catalog, VERBOSE);
      break;
    default:
      fprintf (stderr, "invalid catalog mode\n");
      exit (2);
  }
  return (TRUE);
}

// write out the in-memory data which extends beyond the data on disk.  NOTE: this is
// currently only possible for the SPLIT mode.
int dvo_catalog_update (Catalog *catalog, char VERBOSE) {

  // set the 'sorted' header keyword
  catalog[0].sorted = FALSE;
  gfits_modify (&catalog[0].header, "SORTED",  "%t", 1, catalog[0].sorted);

  /* update is only valid for catmode SPLIT */
  switch (catalog[0].catmode) {
    case DVO_MODE_RAW:
      fprintf (stderr, "not allowed for RAW mode\n");
      break;
    case DVO_MODE_MEF:
      fprintf (stderr, "not allowed for MEF mode\n");
      break;
    case DVO_MODE_SPLIT:
      dvo_catalog_update_split (catalog, VERBOSE);
      break;
    default:
      fprintf (stderr, "failure updating catalog\n");
      fprintf (stderr, "invalid catalog mode\n");
      exit (2);
  }
  return (TRUE);
}

int dvo_catalog_check (Catalog *catalog, int Nsecfilt, int extend) {

  int i, j, Nextra, in, out;
  SecFilt *insec, *outsec;

  if (Nsecfilt == 0) return (TRUE); // if Nsecfilt is not set, don't do this check
  if (catalog[0].Nsecfilt == Nsecfilt) return (TRUE);
  if (catalog[0].Nsecfilt > Nsecfilt) return (FALSE);
  if ((catalog[0].Nsecfilt < Nsecfilt) && !extend) return (FALSE);

  if ((catalog[0].Nsecfilt < Nsecfilt) && extend) {
    Nextra = Nsecfilt - catalog[0].Nsecfilt;
    insec = catalog[0].secfilt;
    ALLOCATE (outsec, SecFilt, catalog[0].Naverage * Nsecfilt);
    for (in = out = i = 0; i < catalog[0].Naverage; i++) {
      for (j = 0; j < catalog[0].Nsecfilt; j++, in++, out++) {
	outsec[out].M  = insec[in].M;
	outsec[out].dM = insec[in].dM;
	outsec[out].Xm = insec[in].Xm;
      }
      for (j = 0; j < Nextra; j++, out++) {
	outsec[out].M  = NAN;
	outsec[out].dM = NAN;
	outsec[out].Xm = NAN_S_SHORT;
      }
    }
    free (catalog[0].secfilt);
    catalog[0].secfilt = outsec;
    catalog[0].Nsecfilt = Nsecfilt;
    catalog[0].Nsecf_mem = Nsecfilt * catalog[0].Naverage;
  }
  return (TRUE);
}

void dvo_catalog_free (Catalog *catalog) {
  dvo_catalog_free_data (catalog);
  gfits_free_header (&catalog[0].header);
}

void dvo_catalog_free_data (Catalog *catalog) {

  /* free, initialize data structures */
  if (catalog[0].average != NULL) {
    free (catalog[0].average); 
    catalog[0].Naverage = 0;
    catalog[0].average = NULL;
  }
  if (catalog[0].measure != NULL) {
    free (catalog[0].measure); 
    catalog[0].Nmeasure = 0;
    catalog[0].measure = NULL;
  }
  if (catalog[0].missing != NULL) {
    free (catalog[0].missing); 
    catalog[0].Nmissing = 0;
    catalog[0].missing = NULL;
  }
  if (catalog[0].secfilt != NULL) {
    free (catalog[0].secfilt); 
    catalog[0].Nsecf_mem = 0;
    catalog[0].secfilt = NULL;
  }
}

/*
  mode   : items to read (LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF)
  format : what table structure on disk (INTERNAL, LONEOS, etc, )
  style  : raw, mef, split, mysql
*/

int dvo_catalog_load_segment (Catalog *catalog, int VERBOSE, int start, int Nrows) {

  switch (catalog[0].catmode) {
    case DVO_MODE_RAW:
      if (VERBOSE) fprintf (stderr, "reading catalog (mode DVO_MODE_RAW)\n");
      fprintf (stderr, "cannot do this in raw mode\n");
      // dvo_catalog_load_segment_raw (catalog, VERBOSE, start, Nrows);
      break;
    case DVO_MODE_MEF:
      if (VERBOSE) fprintf (stderr, "reading catalog (mode DVO_MODE_MEF)\n");
      fprintf (stderr, "cannot do this in mef mode\n");
      // dvo_catalog_load_segment_mef (catalog, VERBOSE, start, Nrows);
      break;
    case DVO_MODE_SPLIT:
      if (VERBOSE) fprintf (stderr, "reading catalog (mode DVO_MODE_SPLIT)\n");
      dvo_catalog_load_segment_split (catalog, VERBOSE, start, Nrows);
      break;
    default:
      fprintf (stderr, "error getting catalog mode\n");
      exit (2);
  }
  return (TRUE);
}

