# include <ohana.h>
# include <loneos.h>

SecFilt *FixOldSecFilt (OldSecFilt *in, int Nvalues);
Average *FixOldAverage (OldAverage *in, int Nvalues);
Measure *FixOldMeasure (OldMeasure *in, int Nvalues);

/* possible exit status for lock_catalog: 
   0 - failure (including lock failure)
   1 - success
   2 - empty file (file may be open or closed!) 
*/

int lock_catalog (Catalog *catalog, int lockmode) {

  int dbstate;

  /* set lock on database, create stream f */
  catalog[0].f  = (FILE *) NULL;
  catalog[0].f = fsetlockfile (catalog[0].filename, 3600.0, lockmode, &dbstate);
  if (dbstate == LCK_EMPTY) return (2);
  if (catalog[0].f == (FILE *) NULL) return (0);

  fseek (catalog[0].f, 0, SEEK_SET);
  catalog[0].lockmode = lockmode;

  return (1);
}

int unlock_catalog (Catalog *catalog) {

  int dbstate;

  if (catalog[0].f == (FILE *) NULL) return (2);
  fclearlockfile (catalog[0].filename, catalog[0].f, catalog[0].lockmode, &dbstate);
  return (1);
}

/* this is (a bit) dangerous: the load function will automatically
   convert from the old (Loneos) format to the new (CFHT) format.
   But, if you load an old-format catalog, then save the new-format
   version, the new file will be corrupted unless you load all four
   data components.  Actually, 'save_catalog' assumes you've loaded
   all four anyway, so it is not such a problem... */

int load_catalog (Catalog *catalog, char mode, int VERBOSE) {
  
  int Nitems, nitems;
  int i, Nmeas, Nmiss, size, ssize;
  int NewMeasure, NewAverage, NewSecFilt;
  FILE *f;
  OldMeasure *tmpmeasure;
  OldAverage *tmpaverage;
  OldSecFilt *tmpsecfilt;
  struct stat filestatus;

  f = catalog[0].f;
  fseek (f, 0, SEEK_SET);

  /* read catalog header */
  if (!fits_load_header (f, &catalog[0].header)) {
    if (VERBOSE) fprintf (stderr, "catalog file does not exist: %s\n", catalog[0].filename);
    return (FALSE);
  }

  /* this is done by the load anyway... */
  fseek (f, catalog[0].header.size, SEEK_SET); 

  /* get the components from the header */
  catalog[0].Naverage = catalog[0].Nmeasure = catalog[0].Nmissing = catalog[0].Nsecfilt = 0;
  if (!fits_scan (&catalog[0].header, "NSTARS",   "%d", 1, &catalog[0].Naverage)) return (FALSE);
  if (!fits_scan (&catalog[0].header, "NMEAS",    "%d", 1, &catalog[0].Nmeasure)) return (FALSE);
  if (!fits_scan (&catalog[0].header, "NMISS",    "%d", 1, &catalog[0].Nmissing)) return (FALSE);
  if (!fits_scan (&catalog[0].header, "NSECFILT", "%d", 1, &catalog[0].Nsecfilt)) catalog[0].Nsecfilt = 0;
  if (!fits_scan (&catalog[0].header, "NEWMEAS",  "%t", 1, &NewMeasure)) NewMeasure = FALSE;
  if (!fits_scan (&catalog[0].header, "NEWAVES",  "%t", 1, &NewAverage)) NewAverage = FALSE;
  if (!fits_scan (&catalog[0].header, "NEWSECF",  "%t", 1, &NewSecFilt)) NewSecFilt = FALSE;

  /* predicted file size - for double checking data validity */
  size = catalog[0].header.size;
  ssize = NewAverage ? sizeof (Average) : sizeof (OldAverage);
  size += ssize * catalog[0].Naverage;
  ssize = NewMeasure ? sizeof (Measure) : sizeof (OldMeasure);
  size += ssize * catalog[0].Nmeasure;
  ssize = sizeof (Missing);
  size += ssize * catalog[0].Nmissing;
  ssize = NewSecFilt ? sizeof (SecFilt) : sizeof (OldSecFilt);
  size += ssize * catalog[0].Nsecfilt * catalog[0].Naverage;

  /* check that file size makes sense */
  if (stat (catalog[0].filename, &filestatus) == -1) {
    if (VERBOSE) fprintf (stderr, "failed to get status of catalog\n");
    return (FALSE);
  }
  if (size > filestatus.st_size) {
    if (VERBOSE) {
      fprintf (stderr, "star catalog has inconsistent size\n");
      ssize = NewAverage ? sizeof (Average) : sizeof (OldAverage);
      fprintf (stderr, "average: %d = %d bytes\n", catalog[0].Naverage, catalog[0].Naverage*ssize);
      ssize = NewMeasure ? sizeof (Measure) : sizeof (OldMeasure);
      fprintf (stderr, "measure: %d = %d bytes\n", catalog[0].Nmeasure, catalog[0].Nmeasure*ssize);
      ssize = sizeof (Missing);
      fprintf (stderr, "missing: %d = %d bytes\n", catalog[0].Nmissing, catalog[0].Nmissing*ssize);
      ssize = NewSecFilt ? sizeof (SecFilt) : sizeof (OldSecFilt);
      fprintf (stderr, "secfilt: %d = %d bytes\n", catalog[0].Nsecfilt, catalog[0].Nsecfilt*catalog[0].Naverage*ssize);
      fprintf (stderr, "expect: %d, found: %d\n", size, (int)filestatus.st_size);
    }
    return (FALSE);
  } 
  if (size < filestatus.st_size) {
    if (VERBOSE) fprintf (stderr, "warning: file larger than expected\n");
  } 

  if (catalog[0].Naverage == 0) {
    if (VERBOSE) fprintf (stderr, "no stars yet in catalog %s\n", catalog[0].filename);
    return (TRUE);
  }

  if (mode & LOAD_AVES) {
    /* read average values */
    if (NewAverage) {
      ALLOCATE (catalog[0].average, Average, MAX (catalog[0].Naverage, 1));
      Nitems = catalog[0].Naverage;
      nitems = Fread (catalog[0].average, sizeof(Average), Nitems, f, "average");
    } else {
      ALLOCATE (tmpaverage, OldAverage, MAX (catalog[0].Naverage, 1));
      Nitems = catalog[0].Naverage;
      nitems = Fread (tmpaverage, sizeof(OldAverage), Nitems, f, "oldaverage");
      catalog[0].average = FixOldAverage (tmpaverage, Nitems);
    }
    if (nitems != Nitems) {
      if (VERBOSE) fprintf (stderr, "failed to read averages from catalog file %s (%d vs %d)\n", catalog[0].filename, nitems, Nitems);
      return (FALSE);
    }
  } else {
    /* skip over averages */
    fseek (f, catalog[0].Naverage * sizeof(Average), SEEK_CUR); 
  }    
  
  if (mode & LOAD_MEAS) {
    /* read measurements */
    if (NewMeasure) {
      ALLOCATE (catalog[0].measure, Measure, MAX (catalog[0].Nmeasure, 1));
      Nitems = catalog[0].Nmeasure;
      nitems = Fread (catalog[0].measure, sizeof(Measure), Nitems, f, "measure");
    } else {
      ALLOCATE (tmpmeasure, OldMeasure, MAX (catalog[0].Nmeasure, 1));
      Nitems = catalog[0].Nmeasure;
      nitems = Fread (tmpmeasure, sizeof(OldMeasure), Nitems, f, "oldmeasure");
      catalog[0].measure = FixOldMeasure (tmpmeasure, Nitems);
    }
    if (nitems != Nitems) {
      if (VERBOSE) fprintf (stderr, "failed to read measures from catalog file %s (%d vs %d)\n", catalog[0].filename, nitems, Nitems);
      return (FALSE);
    }
  } else {
    /* skip over measures */
    fseek (f, catalog[0].Nmeasure * sizeof(Measure), SEEK_CUR); 
  } 

  if (mode & LOAD_MISS) {
    /* read missing */
    ALLOCATE (catalog[0].missing, Missing, MAX (catalog[0].Nmissing, 1));
    Nitems = catalog[0].Nmissing;
    nitems = Fread (catalog[0].missing, sizeof(Missing), Nitems, f, "missing");
    if (nitems != Nitems) {
      if (VERBOSE) fprintf (stderr, "failed to read missing from catalog file %s (%d vs %d)\n", catalog[0].filename, nitems, Nitems);
      return (FALSE);
    }
  } else {
    /* skip over missings */
    fseek (f, catalog[0].Nmissing * sizeof(Missing), SEEK_CUR); 
  }
  
  if (mode & LOAD_SECF) {
    /* read missing */
    if (NewSecFilt) {
      Nitems = catalog[0].Naverage * catalog[0].Nsecfilt;
      ALLOCATE (catalog[0].secfilt, SecFilt, MAX (Nitems, 1));
      nitems = Fread (catalog[0].secfilt, sizeof(SecFilt), Nitems, f, "secfilt");
    } else {
      Nitems = catalog[0].Naverage * catalog[0].Nsecfilt;
      ALLOCATE (tmpsecfilt, OldSecFilt, MAX (Nitems, 1));
      nitems = Fread (tmpsecfilt, sizeof(OldSecFilt), Nitems, f, "oldsecfilt");
      catalog[0].secfilt = FixOldSecFilt (tmpsecfilt, Nitems);
    } 
    if (nitems != Nitems) {
      if (VERBOSE) fprintf (stderr, "failed to read secondary filters from catalog file %s (%d vs %d)\n", catalog[0].filename, nitems, Nitems);
      return (FALSE);
    }
  } else {
    /* skip over secfilts */
    fseek (f, catalog[0].Naverage * catalog[0].Nsecfilt * sizeof(SecFilt), SEEK_CUR); 
  }

  if (VERBOSE) fprintf (stderr, "read %d stars from catalog file %s (%d measurements, %d missing, %d secondary filters)\n", 
	   catalog[0].Naverage, catalog[0].filename, catalog[0].Nmeasure, catalog[0].Nmissing, catalog[0].Nsecfilt);

  /* check data integrity */
  if (mode & LOAD_AVES) {
    for (i = Nmeas = Nmiss = 0; i < catalog[0].Naverage; i++) {
      Nmeas += catalog[0].average[i].Nm; 
      Nmiss += catalog[0].average[i].Nn; 
    }
    if ((Nmeas != catalog[0].Nmeasure) || (Nmiss != catalog[0].Nmissing)) {
      if (VERBOSE) {
	fprintf (stderr, "****** data in catalog %s is corrupt, sums don't check\n", catalog[0].filename);
	fprintf (stderr, "****** Nmeas: %d, %d\n", Nmeas, catalog[0].Nmeasure);
	fprintf (stderr, "****** Nmiss: %d, %d\n", Nmiss, catalog[0].Nmissing);
      }
      return (FALSE);
    }
  }
  return (TRUE);
}

/* this is not robust: an error will not return us to the original
   state.  But, it is faster: only limited data written to disk? *
*/

int save_catalog (Catalog *catalog, char VERBOSE) {

  int Nitems, nitems;
  FILE *f;

  if (catalog[0].Naverage == 0) {
    if (VERBOSE) fprintf (stderr, "no stars in catalog, skipping\n");
    return (TRUE);
  }

  /* make sure header is consistent with data */
  fits_modify (&catalog[0].header, "NSTARS",   "%d", 1, catalog[0].Naverage);
  fits_modify (&catalog[0].header, "NMEAS",    "%d", 1, catalog[0].Nmeasure);
  fits_modify (&catalog[0].header, "NMISS",    "%d", 1, catalog[0].Nmissing);
  fits_modify (&catalog[0].header, "NSECFILT", "%d", 1, catalog[0].Nsecfilt);
  fits_modify (&catalog[0].header, "NEWMEAS",  "%t", 1, TRUE);
  fits_modify (&catalog[0].header, "NEWAVES",  "%t", 1, TRUE);
  fits_modify (&catalog[0].header, "NEWSECF",  "%t", 1, TRUE);

  f = catalog[0].f;
  /* rewind file pointers and truncate */
  fseek (f, 0, SEEK_SET);
  ftruncate (fileno (catalog[0].f), 0);

  nitems = Fwrite (catalog[0].header.buffer, 1, catalog[0].header.size, f, "char");
  if (nitems != catalog[0].header.size) {
    if (VERBOSE) fprintf (stderr, "failed to write header\n");
    return (FALSE);
  }

  Nitems = catalog[0].Naverage;
  nitems = Fwrite (catalog[0].average, sizeof(Average), Nitems, f, "average");
  if (nitems != Nitems) {
    if (VERBOSE) fprintf (stderr, "failed to write catalog file aves %s\n", catalog[0].filename);
    return (FALSE);
  }
  
  Nitems = catalog[0].Nmeasure;
  nitems = Fwrite (catalog[0].measure, sizeof(Measure), Nitems, f, "measure");
  if (nitems != Nitems) {
    if (VERBOSE) fprintf (stderr, "failed to write catalog file meas %s\n", catalog[0].filename);
    return (FALSE);
  }

  Nitems = catalog[0].Nmissing;
  nitems = Fwrite (catalog[0].missing, sizeof(Missing), Nitems, f, "missing");
  if (nitems != Nitems) {
    if (VERBOSE) fprintf (stderr, "failed to write catalog file miss %s\n", catalog[0].filename);
    return (FALSE);
  }

  Nitems = catalog[0].Naverage * catalog[0].Nsecfilt;
  nitems = Fwrite (catalog[0].secfilt, sizeof(SecFilt), Nitems, f, "secfilt");
  if (nitems != Nitems) {
    if (VERBOSE) fprintf (stderr, "failed to write catalog file secondary filters %s\n", catalog[0].filename);
    return (FALSE);
  }

  return (TRUE);

}

/**** tools to convert between formats - this happens automatically in load_catalog */

Measure *FixOldMeasure (OldMeasure *in, int Nvalues) {

  int i;
  Measure *out;

  ALLOCATE (out, Measure, Nvalues);

  for (i = 0; i < Nvalues; i++) {
    out[i].dR = in[i].dR;
    out[i].dD = in[i].dD;
    out[i].M  = in[i].M;
    out[i].dM = in[i].dM;
    out[i].Mcal = in[i].Mcal;
    out[i].dophot = in[i].dophot;
    out[i].source = in[i].source;
    out[i].t = in[i].t;

    out[i].dt = 0xffff;
    out[i].averef = in[i].average & 0x00ffffff;
    out[i].flags  = (in[i].average & 0xff000000) >> 24;
  }

  free (in);
  
  return (out);
}

Average *FixOldAverage (OldAverage *in, int Nvalues) {

  int i;
  Average *out;

  ALLOCATE (out, Average, Nvalues);

  for (i = 0; i < Nvalues; i++) {
    out[i].R       = in[i].R;      
    out[i].D       = in[i].D;      
    out[i].M       = in[i].M;      
    out[i].Xp      = in[i].Xp;     
    out[i].Xm      = in[i].Xm;     
    out[i].Nm      = in[i].Nm;     
    out[i].Nn      = in[i].Nn;     
    out[i].code    = in[i].code;   
    out[i].offset  = in[i].offset; 
    out[i].missing = in[i].missing;

    out[i].dM = 0xffff;
  }

  free (in);
  
  return (out);
}

SecFilt *FixOldSecFilt (OldSecFilt *in, int Nvalues) {

  int i;
  SecFilt *out;

  ALLOCATE (out, SecFilt, Nvalues);

  for (i = 0; i < Nvalues; i++) {
    out[i].M    = in[i].M;      
    out[i].Xm   = in[i].Xm;      

    out[i].dM   = 0xffff;
  }

  free (in);
  
  return (out);
}

