# include "dvomerge.h"

/* things we can verify easily:
   table sizes: (NAXIS1 vs EXTTYPE; NAXIS2 vs data size)
   sum of catalog.average.Nmeasure == catalog.Nmeasure
   averef, obj_id consistent between average and measure
   do we need a checksum?
*/

int VerifyTableFile (char *filename);
int CheckCatalogIndexes (char *catdir, char *filename,  SkyRegion *region);

# define DEBUG 0

int VERBOSE = FALSE;
int NNotSorted = 0;

int main (int argc, char **argv) {

  char filename[1024];

  int N, Nbad;
  off_t i;
  SkyTable *insky;
  SkyList *inlist;
  SkyRegion UserPatch;
  // Catalog catalog;

  int CHECKSORTED;

  if ((N = get_argument (argc, argv, "-v"))) {
    VERBOSE = TRUE;
    remove_argument (N, &argc, argv);
  }
  if ((N = get_argument (argc, argv, "-verbose"))) {
    VERBOSE = TRUE;
    remove_argument (N, &argc, argv);
  }
  if ((N = get_argument (argc, argv, "-s"))) {
    CHECKSORTED = TRUE;
    remove_argument (N, &argc, argv);
  }
  if ((N = get_argument (argc, argv, "-sorted"))) {
    CHECKSORTED = TRUE;
    remove_argument (N, &argc, argv);
  }

  if ((N = get_argument (argc, argv, "-cpt"))) {
    remove_argument (N, &argc, argv);
    char *filename = strcreate (argv[N]);
    remove_argument (N, &argc, argv);

    int isBad = FALSE;

    if (!VerifyTableFile (filename)) {
      fprintf (stderr, "bad average table %s\n", filename);
      isBad = TRUE;
    }

    // change last 't' to 's':
    int Nlast;
    Nlast = strlen(filename) - 1;
    filename[Nlast] = 's';
    if (!VerifyTableFile (filename)) {
      fprintf (stderr, "bad secfilt table %s\n", filename);
      isBad = TRUE;
    }

    // change last 't' to 's':
    Nlast = strlen(filename) - 1;
    filename[Nlast] = 'm';
    if (!VerifyTableFile (filename)) {
      fprintf (stderr, "bad measure table %s\n", filename);
      isBad = TRUE;
    }

    if (isBad) exit (1);
    exit (0);
  }

  // restrict to a portion of the sky
  UserPatch.Rmin = 0;
  UserPatch.Rmax = 360;
  UserPatch.Dmin = -90;
  UserPatch.Dmax = +90;
  if ((N = get_argument (argc, argv, "-region"))) {
    remove_argument (N, &argc, argv);
    UserPatch.Rmin = atof (argv[N]);
    remove_argument (N, &argc, argv);
    UserPatch.Rmax = atof (argv[N]);
    remove_argument (N, &argc, argv);
    UserPatch.Dmin = atof (argv[N]);
    remove_argument (N, &argc, argv);
    UserPatch.Dmax = atof (argv[N]);
    remove_argument (N, &argc, argv);
  }

  if (argc != 2) {
    fprintf (stderr, "USAGE: dvoverify (catdir) [-region Rmin Rmax Dmin Dmax] [-v] [-s]\n\n");
    fprintf (stderr, "  -v : VERBOSE\n");
    fprintf (stderr, "  -s : checks if sorted, return error if not\n");
    fprintf (stderr, "  (catdir) : database of interest\n");
    exit (2);
  }

  char *catdir = argv[1];

  Nbad = 0;

  // XXX make this step optional
  if (1) {
    // check the photcode table
    sprintf (filename, "%s/Photcodes.dat", catdir);
    if (!VerifyTableFile (filename)) {
      Nbad ++;
    }

    // check the skytable
    char *skyfile = SkyTableFilename (catdir);
    if (!VerifyTableFile (skyfile)) {
      Nbad ++;
    }

    // check the image table
    sprintf (filename, "%s/Images.dat", catdir);
    if (!VerifyTableFile (filename)) {
      Nbad ++;
    }
  }

  // load the sky table for the existing database
  insky = SkyTableLoadOptimal (catdir, NULL, NULL, FALSE, SKY_DEPTH_HST, VERBOSE);
  myAssert(insky, "can't read SkyTable");
  SkyTableSetFilenames (insky, catdir, "cpt");
  inlist = SkyListByPatch (insky, -1, &UserPatch);
  
  // loop over all catalogs, save to output catalogs
  for (i = 0; i < inlist[0].Nregions; i++) {
    if (!inlist[0].regions[i][0].table) continue;
    if (i % 1000 == 0) fprintf (stderr, ".");

    // sprintf (filename, "%s/%s.cpt", catdir, inlist[0].regions[i][0].name);
    if (!VerifyTableFile (inlist[0].filename[i])) {
      Nbad ++;
    }

    sprintf (filename, "%s/%s.cps", catdir, inlist[0].regions[i][0].name);
    if (!VerifyTableFile (filename)) {
      Nbad ++;
    }

    sprintf (filename, "%s/%s.cpm", catdir, inlist[0].regions[i][0].name);
    if (!VerifyTableFile (filename)) {
      Nbad ++;
    }

    if (!CheckCatalogIndexes(catdir, inlist[0].filename[i], inlist[0].regions[i])){
      Nbad ++;
    }

    // exit immediately if any file are unsorted and we require sorted tables
    if (CHECKSORTED && NNotSorted) {
      fprintf (stderr, "ERROR: files are not sorted\n");
      exit (1);
    }
  }

  if (Nbad > 0) {
    fprintf (stderr, "ERROR: %d files are bad\n", Nbad);
    exit (1);
  }

  fprintf (stderr, "SUCCESS: no files are bad\n");
  if (NNotSorted) {
    fprintf (stderr, "NOTE: %d files are not sorted\n", NNotSorted);
  }
  exit (0);
}

// is this file a consistent FITS file?
int VerifyTableFile (char *filename) {

  int status, Next;
  off_t Nbytes;
  Header header;

  struct stat fileStats;
  FILE *file;

  // does the file exist?
  status = stat (filename, &fileStats);
  if (status) {
    // some error accessing the file.  there is only one acceptable error: file not found
    switch (errno) {
      case ENOENT:
	if (DEBUG) fprintf (stderr, "file does not exist, skipping %s\n", filename);
	return TRUE;
      case ENOMEM:
	fprintf (stderr, "Out of memory: %s\n", filename);
	return TRUE;
      case EACCES:
	fprintf (stderr, "Permission error on %s\n", filename);
	return FALSE;
      case EFAULT:
	fprintf (stderr, "Bad address: %s\n", filename);
	return FALSE;
      case ELOOP:
	fprintf (stderr, "Too many symbolic links encountered while traversing the path: %s\n", filename);
	return FALSE;
      case ENAMETOOLONG:
	fprintf (stderr, "File name too long: %s\n", filename);
	return FALSE;
      case ENOTDIR:
	fprintf (stderr, "A component of the path is not a directory: %s\n", filename);
	return FALSE;
      case EOVERFLOW:
	fprintf (stderr, "file too large for program version: %s\n", filename);
	return FALSE;
      default:
	fprintf (stderr, "unknown error: %s\n", filename);
	return FALSE;
    }
  }

  // does it have any data?
  if (fileStats.st_size == 0) {
    fprintf (stderr, "file is empty: %s\n", filename);
    return FALSE;
  }

  // can we open it?
  file = fopen(filename, "r");
  if (!file) {
    fprintf (stderr, "unable to open valid file: %s\n", filename);
    return FALSE;
  }

  // scan all extentions
  Nbytes = 0;
  Next = -1;
  if (DEBUG) fprintf (stderr, "sizes: ("OFF_T_FMT" vs "OFF_T_FMT")\n", Nbytes, fileStats.st_size);
  while (Nbytes < fileStats.st_size) {

    // Check on the PHU
    if (!gfits_fread_header (file, &header)) {
      if (Next == -1) {
	fprintf (stderr, "unable to read PHU header for %s\n", filename);
      } else {
	fprintf (stderr, "unable to read header for %s, extension %d (or file has excess bytes)\n", filename, Next);
      }
      fclose(file);
      return (FALSE);
    }

    // move to TBL header
    Nbytes += header.datasize + gfits_data_size (&header);
    if (DEBUG) fprintf (stderr, "sizes: ("OFF_T_FMT" vs "OFF_T_FMT")\n", Nbytes, fileStats.st_size);
    if (Nbytes > fileStats.st_size) {
      fprintf (stderr, "file is short ("OFF_T_FMT" vs "OFF_T_FMT"): %s\n", Nbytes, fileStats.st_size, filename);
      gfits_free_header(&header);
      fclose (file);
      return FALSE;
    }
    gfits_free_header(&header);

    status = fseeko (file, Nbytes, SEEK_SET);
    if (status) {
      switch (errno) {
	case EBADF:
	  fprintf (stderr, "something wrong with file handle: %s\n", filename);
	  fclose (file);
	  return FALSE;
	case EINVAL:
	  fprintf (stderr, "invalid offset: %s\n", filename);
	  fclose (file);
	  return FALSE;
	default:
	  fprintf (stderr, "other error in fseeko: %s\n", filename);
	  fclose (file);
	  return FALSE;
      }
    }
    Next ++;
  }
  if (DEBUG) fprintf (stderr, "file is good: %s\n", filename);
  fclose (file);
  return TRUE;
}

// CheckCatalogIndexes(catdir, inlist[0].regions[i][0].name);

int CheckCatalogIndexes (char *catdir, char *filename,  SkyRegion *region) {

  Catalog catalog;
  int i, j, m, status;

  status = TRUE;

  // set the parameters which guide catalog open/load/create
  catalog.filename  = filename;
  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;
  catalog.Nsecfilt  = 0;
  
  // an error exit status here is a significant error (disk I/O or file access)
  if (!dvo_catalog_open (&catalog, region, VERBOSE, "r")) {
    fprintf (stderr, "ERROR: failure to open catalog file %s\n", catalog.filename);
    return FALSE;
  }

  // Naves_disk == 0 implies an empty catalog file, skip empty catalogs
  if (catalog.Naves_disk == 0) {
    dvo_catalog_unlock (&catalog);
    dvo_catalog_free (&catalog);
    return TRUE;
  }

  // if the table is SORTED, then the following can be checked
  // check the following:
  // measure[j].averef -> average[averef]
  // measure[j].objID = average[averef].objID
  // measure[j].catID = average[averef].catID
  // measure[j].measureOffset < Nmeasure
  // \sum average[].Nmeasure = Nmeasure

  // if the table is NOT SORTED, we have a subset of checks we can make
  if (!catalog.sorted) {
    fprintf (stderr, "!");
    dvo_catalog_unlock (&catalog);
    dvo_catalog_free (&catalog);
    NNotSorted++;
    if (VERBOSE) fprintf (stderr, "file is not sorted: %s\n", filename);
    return TRUE;
  }

  int NmeasureTotal = 0;
  int measureOffsetOK = TRUE;
  for (i = 0; i < catalog.Naverage; i++) {
    NmeasureTotal += catalog.average[i].Nmeasure;
    if (VERBOSE && !(NmeasureTotal <= catalog.Nmeasure)) {
      fprintf (stderr, "NmeasureTotal > catalog.Nmeasure: %d %d %d\n", i, catalog.average[i].Nmeasure, (int) catalog.Nmeasure);
    }
    measureOffsetOK &= (catalog.average[i].measureOffset < catalog.Nmeasure);
    if (VERBOSE && !(catalog.average[i].measureOffset < catalog.Nmeasure)) {
      fprintf (stderr, "measureOffset >= catalog.Nmeasure: %d %d %d\n", i, catalog.average[i].measureOffset, (int) catalog.Nmeasure);
    }
    measureOffsetOK &= (catalog.average[i].measureOffset + catalog.average[i].Nmeasure <= catalog.Nmeasure);
    if (VERBOSE && !(catalog.average[i].measureOffset + catalog.average[i].Nmeasure <= catalog.Nmeasure)) {
      fprintf (stderr, "measureOffset + Nmeasure > catalog.Nmeasure : %d %d %d\n", i, catalog.average[i].Nmeasure, (int) catalog.Nmeasure);
    }
  }

  if (!measureOffsetOK) {
    fprintf (stderr, "ERROR: catalog %s has an invalid measureOffset\n", catalog.filename);
    status = FALSE;
  }

  if (NmeasureTotal != catalog.Nmeasure) {
    fprintf (stderr, "ERROR: catalog %s has an invalid Nmeasure\n", catalog.filename);
    status = FALSE;
  }

  if (!status) {
    dvo_catalog_unlock (&catalog);
    dvo_catalog_free (&catalog);
    return (status);
  }

  int objIDsOK = TRUE;
  int catIDsOK = TRUE;
  int averefOK = TRUE;

  for (i = 0; i < catalog.Naverage; i++) {
    m = catalog.average[i].measureOffset;
    for (j = 0; j < catalog.average[i].Nmeasure; j++) {
      objIDsOK &= (catalog.average[i].objID == catalog.measure[m+j].objID);
      catIDsOK &= (catalog.average[i].catID == catalog.measure[m+j].catID);
      averefOK &= (catalog.measure[m+j].averef == i);
    }
  }
    
  if (!objIDsOK) {
    fprintf (stderr, "ERROR: catalog %s has invalid obj IDs\n", catalog.filename);
    status = FALSE;
  }
  if (!catIDsOK) {
    fprintf (stderr, "ERROR: catalog %s has invalid cat IDs\n", catalog.filename);
    status = FALSE;
  }
  if (!averefOK) {
    fprintf (stderr, "ERROR: catalog %s has invalid averef values\n", catalog.filename);
    status = FALSE;
  }

//  for (i = 0; i < catalog.Naverage; i++) {
//    m = catalog.average[i].measureOffset;
//    for (j = 0; i < catalog.Nmeasure; i++) {
//      objIDsOK &= (catalog.average[i].objID == catalog.measure[m+j].objID);
//      catIDsOK &= (catalog.average[i].catID == catalog.measure[m+j].catID);
//      averefOK &= (catalog.measure[m+j].averef = i);
//    }
//  }

  dvo_catalog_unlock (&catalog);
  dvo_catalog_free (&catalog);

  return status;
}

// gfits_scan(&cpmHeaderTBL, "NAXIS1", "%d", 1, &NbytesPerRow);
// gfits_scan(&cpmHeaderTBL, "NAXIS2", "%d", 1, &Nrows);
    
  
