# include "dvoshell.h"

// to add a new field to the extractions:
// 1) add the selection for the field below
// 2) add the field to the ParseMeasureField in dbFields.c
// 3) add the field to the measure enum list in dvoshell.h

/* time concepts */
static time_t TimeReference;
static int TimeFormat;

static time_t TimeRefPM;

static CoordTransform *celestial_to_galactic = NULL;
static CoordTransform *celestial_to_ecliptic = NULL;

static int haveGalacticAve = FALSE;
static double GLON_AVE = 0.0;
static double GLAT_AVE = 0.0;

static int haveEclipticAve = FALSE;
static double ELON_AVE = 0.0;
static double ELAT_AVE = 0.0;

static int haveGalacticMeas = FALSE;
static double GLON_MEAS = 0.0;
static double GLAT_MEAS = 0.0;

static int haveEclipticMeas = FALSE;
static double ELON_MEAS = 0.0;
static double ELAT_MEAS = 0.0;

int dbExtractMeasuresInit () {
  TimeRefPM = ohana_date_to_sec ("2000/01/01");
  GetTimeFormat (&TimeReference, &TimeFormat);
  return (TRUE);
}

// define a locally-static transform
int dbExtractMeasuresInitTransform (CoordTransformSystem target) {

  // galactic transform is kept forever
  if (target == COORD_GALACTIC) {
    if (celestial_to_galactic != NULL) return (TRUE);
    celestial_to_galactic = InitTransform (COORD_CELESTIAL, target);
    return (TRUE);
  }

  // ecliptic transform must be updated (is weakly time-dependent)
  if (target == COORD_ECLIPTIC) {
    if (celestial_to_ecliptic != NULL) {
      free (celestial_to_ecliptic);
    }
    celestial_to_ecliptic = InitTransform (COORD_CELESTIAL, target);
    return (TRUE);
  }
  return (FALSE);
}

int dbExtractMeasuresInitAve () {
  haveGalacticAve = FALSE;
  haveEclipticAve = FALSE;
  return (TRUE);
}

int dbExtractMeasuresInitMeas () {
  haveGalacticMeas = FALSE;
  haveEclipticMeas = FALSE;
  return (TRUE);
}

/* return measure.field based on the selection */
dbValue dbExtractMeasures (Average *average, SecFilt *secfilt, Measure *measure, dbField *field) {

  int Nsec;
  dbValue value;
  double ra, dec, x, y, dT;

  Coords *mosaic;
  PhotCode *equiv;

  value.Flt = NAN;
  value.Int =   0;

  switch (field->ID) {
    case MEAS_MAG: /* magnitudes are already determined above */
      equiv = GetPhotcodeEquivbyCode (measure[0].photcode);

      // we return the magnitude for this measure if:
      if (field->photcode->type == PHOT_MAG) goto valid_photcode;
      if ((field->photcode->type == PHOT_REF) && (measure[0].photcode == field->photcode->code)) goto valid_photcode;
      if ((field->photcode->type == PHOT_DEP) && (measure[0].photcode == field->photcode->code)) goto valid_photcode;

      if ((equiv != NULL) && (field->photcode->type == PHOT_SEC) && (equiv[0].code == field->photcode->code)) goto valid_photcode;
      break;

  valid_photcode:
      switch (field->magMode) {
	case MAG_INST:
	  value.Flt = PhotInst (measure);  
	  break;
	case MAG_CAT:
	  value.Flt = PhotCat  (measure); 
	  break;
	case MAG_SYS:
	  value.Flt = PhotSys  (measure, average, secfilt); 
	  break;
	case MAG_REL:
	  value.Flt = PhotRel  (measure, average, secfilt); 
	  break;
	case MAG_CAL:
	  value.Flt = PhotCal  (measure, average, secfilt, measure, equiv); 
	  break;
	case MAG_AVE:
	  value.Flt = PhotAve  (equiv, average, secfilt); 
	  break;
	case MAG_REF:
	  value.Flt = PhotRef  (equiv, average, secfilt, measure); 
	  break;
	case MAG_ERR:
	  if (field->photcode->type == PHOT_SEC) {
	    value.Flt = PhotdM  (equiv, average, secfilt); 
	  } else {
	    value.Flt = measure[0].dM;
	  }
	  break;
	case MAG_PHOT_FLAGS:
	  if ((field->photcode->type == PHOT_REF) || (field->photcode->type == PHOT_DEP)) {
	    value.Int = measure[0].photFlags;
	  } else {
	    value.Int = 0;
	  }
	  break;
	case MAG_CHISQ:
	  value.Flt = PhotXm  (equiv, average, secfilt); 
	  break;
	case MAG_NCODE:
	  if (equiv == NULL) {
	    value.Int = 0;
	    break;
	  }
	  Nsec = GetPhotcodeNsec (equiv->code);
	  if (Nsec == -1) break;
	  value.Int = secfilt[Nsec].Ncode;
	  break;
	case MAG_NPHOT:
	  if (equiv == NULL) {
	    value.Int = 0;
	    break;
	  }
	  Nsec = GetPhotcodeNsec (equiv->code);
	  if (Nsec == -1) break;
	  value.Int = secfilt[Nsec].Nused;
	  break;
      }
      break;
    case MEAS_RA: /* OK */
      value.Flt = average[0].R - measure[0].dR / 3600.0;
      break;
    case MEAS_DEC: /* OK */
      value.Flt = average[0].D - measure[0].dD / 3600.0;
      break;
    case MEAS_RA_AVE: /* OK */
      value.Flt = average[0].R;
      break;
    case MEAS_DEC_AVE: /* OK */
      value.Flt = average[0].D;
      break;

    case MEAS_GLON:
      if (!haveGalacticMeas) {
	ApplyTransform (&GLON_MEAS, &GLAT_MEAS, average[0].R - measure[0].dR / 3600.0, average[0].D - measure[0].dD / 3600.0, celestial_to_galactic);
	haveGalacticMeas = TRUE;
      }
      value.Flt = GLON_MEAS;
      break;
    case MEAS_GLAT:
      if (!haveGalacticMeas) {
	ApplyTransform (&GLON_MEAS, &GLAT_MEAS, average[0].R - measure[0].dR / 3600.0, average[0].D - measure[0].dD / 3600.0, celestial_to_galactic);
	haveGalacticMeas = TRUE;
      }
      value.Flt = GLAT_MEAS;
      break;
    case MEAS_ELON:
      if (!haveEclipticMeas) {
	ApplyTransform (&ELON_MEAS, &ELAT_MEAS, average[0].R - measure[0].dR / 3600.0, average[0].D - measure[0].dD / 3600.0, celestial_to_ecliptic);
	haveEclipticMeas = TRUE;
      }
      value.Flt = ELON_MEAS;
      break;
    case MEAS_ELAT:
      if (!haveEclipticMeas) {
	ApplyTransform (&ELON_MEAS, &ELAT_MEAS, average[0].R - measure[0].dR / 3600.0, average[0].D - measure[0].dD / 3600.0, celestial_to_ecliptic);
	haveEclipticMeas = TRUE;
      }
      value.Flt = ELAT_MEAS;
      break;

    case MEAS_GLON_AVE:
      if (!haveGalacticAve) {
	ApplyTransform (&GLON_AVE, &GLAT_AVE, average[0].R, average[0].D, celestial_to_galactic);
	haveGalacticAve = TRUE;
      }
      value.Flt = GLON_AVE;
      break;
    case MEAS_GLAT_AVE:
      if (!haveGalacticAve) {
	ApplyTransform (&GLON_AVE, &GLAT_AVE, average[0].R, average[0].D, celestial_to_galactic);
	haveGalacticAve = TRUE;
      }
      value.Flt = GLAT_AVE;
      break;
    case MEAS_ELON_AVE:
      if (!haveEclipticAve) {
	ApplyTransform (&ELON_AVE, &ELAT_AVE, average[0].R, average[0].D, celestial_to_ecliptic);
	haveEclipticAve = TRUE;
      }
      value.Flt = ELON_AVE;
      break;
    case MEAS_ELAT_AVE:
      if (!haveEclipticAve) {
	ApplyTransform (&ELON_AVE, &ELAT_AVE, average[0].R, average[0].D, celestial_to_ecliptic);
	haveEclipticAve = TRUE;
      }
      value.Flt = ELAT_AVE;
      break;

    case MEAS_RA_AVE_ERR: /* OK */
      value.Flt = average[0].dR;
      break;
    case MEAS_DEC_AVE_ERR: /* OK */
      value.Flt = average[0].dD;
      break;
    case MEAS_U_RA: /* OK */
      value.Flt = average[0].uR;
      break;
    case MEAS_U_DEC: /* OK */
      value.Flt = average[0].uD;
      break;
    case MEAS_U_RA_ERR: /* OK */
      value.Flt = average[0].duR;
      break;
    case MEAS_U_DEC_ERR: /* OK */
      value.Flt = average[0].duD;
      break;
    case MEAS_PAR: /* OK */
      value.Flt = average[0].R;
      break;
    case MEAS_PAR_ERR: /* OK */
      value.Flt = average[0].D;
      break;
    case MEAS_XP: /* OK */
      value.Flt = average[0].Xp;
      break;
    case MEAS_NMEAS: /* OK */
      value.Int = average[0].Nmeasure;
      break;
    case MEAS_NMISS: /* OK */
      value.Int = average[0].Nmissing;
      break;
    case MEAS_OBJFLAGS: /* OK */
      value.Int = average[0].flags;
      break;
    // note that these represent the ra displacement relative to the average, not 
    // the error.
    case MEAS_RA_OFFSET: /* OK */
      value.Flt = measure[0].dR;
      break;
    case MEAS_DEC_OFFSET: /* OK */
      value.Flt = measure[0].dD;
      break;
    case MEAS_RA_FIT_OFFSET: /* OK */
      dT = (measure[0].t - TimeRefPM) / (86400*365.25);
      value.Flt = average[0].uR * dT + measure[0].dR;
      break;
    case MEAS_DEC_FIT_OFFSET: /* OK */
      dT = (measure[0].t - TimeRefPM) / (86400*365.25);
      value.Flt = average[0].uD * dT + measure[0].dD;
      break;
    case MEAS_RA_OFFSET_ERR: /* OK */
      value.Flt = NAN;
      break;
    case MEAS_DEC_OFFSET_ERR: /* OK */
      value.Flt = NAN;
      break;
    case MEAS_AIRMASS: /* OK */
      value.Flt = measure[0].airmass;
      break;
    case MEAS_AZ: /* OK */
      value.Flt = measure[0].az;
      break;
    case MEAS_EXPTIME: /* OK */
      value.Flt = pow (10.0, measure[0].dt * 0.4);
      break;
    case MEAS_PHOTCODE: /* OK */
      value.Int = measure[0].photcode;
      break;
    case MEAS_TIME: /* OK */
      value.Flt = TimeValue (measure[0].t, TimeReference, TimeFormat);
      break;
    case MEAS_FWHM: /* OK */
      value.Flt = 0.01*(measure[0].FWx + measure[0].FWy) / 2.0;
      break;
    case MEAS_FWHM_MAJ: /* OK */
      value.Flt = 0.01*measure[0].FWx;
      break;
    case MEAS_FWHM_MIN: /* OK */
      value.Flt = 0.01*measure[0].FWy;
      break;
    case MEAS_THETA: /* OK */
      value.Flt = measure[0].theta;
      break;
    case MEAS_DOPHOT: /* OK */
      value.Int = (measure[0].photFlags >> 16);
      break;
    case MEAS_DB_FLAGS: /* ? */
      value.Int = measure[0].dbFlags;
      break;
    case MEAS_PHOT_FLAGS: /* ? */
      value.Int = measure[0].photFlags;
      break;
    case MEAS_XCCD: /* OK */
/* I need to perform this conversion for ELIXIR and LONEOS formats on load */      
# if 1
      value.Flt = measure[0].Xccd;
# else
      { 
	Image *image;
	ra  = average[0].R - measure[0].dR / 3600.0;
	dec = average[0].D - measure[0].dD / 3600.0;
	image = MatchImage (measure[0].t, measure[0].photcode);
	if (image == NULL) break;
	RD_to_XY (&x, &y, ra, dec, &image[0].coords);
	value.Flt = x;
      }
# endif
      break;
    case MEAS_YCCD: /* OK */
/* I need to perform this conversion for ELIXIR and LONEOS formats on load */      
# if 1
      value.Flt = measure[0].Yccd;
# else
      {
	Image *image;
	ra  = average[0].R - measure[0].dR / 3600.0;
	dec = average[0].D - measure[0].dD / 3600.0;
	image = MatchImage (measure[0].t, measure[0].photcode);
	if (image == NULL) break;
	RD_to_XY (&x, &y, ra, dec, &image[0].coords);
	value.Flt = y;
      }
# endif
      break;
    case MEAS_XCCD_ERR: /* OK */
      value.Flt = measure[0].dXccd;
      break;
    case MEAS_YCCD_ERR: /* OK */
      value.Flt = measure[0].dYccd;
      break;
    case MEAS_XMOSAIC: /* OK */
      ra  = average[0].R - measure[0].dR / 3600.0;
      dec = average[0].D - measure[0].dD / 3600.0;
      mosaic = MatchMosaic (measure[0].t, measure[0].photcode);
      if (mosaic == NULL) break;
      RD_to_XY (&x, &y, ra, dec, mosaic);
      value.Flt = x;
      break;
    case MEAS_YMOSAIC: /* OK */
      ra  = average[0].R - measure[0].dR / 3600.0;
      dec = average[0].D - measure[0].dD / 3600.0;
      mosaic = MatchMosaic (measure[0].t, measure[0].photcode);
      if (mosaic == NULL) break;
      RD_to_XY (&x, &y, ra, dec, mosaic);
      value.Flt = y;
      break;

    case MEAS_SKY: /* OK */
      value.Flt = measure[0].Sky;
      break;
    case MEAS_dSKY: /* OK */
      value.Flt = measure[0].dSky;
      break;
    case MEAS_DET_ID: /* OK */
      value.Int = measure[0].detID;
      break;
    case MEAS_OBJ_ID: /* OK */
      value.Int = average[0].objID;
      break;
    case MEAS_CAT_ID: /* OK */
      value.Int = average[0].catID;
      break;
    case MEAS_IMAGE_ID: /* OK */
      value.Int = measure[0].imageID;
      break;
    case MEAS_PSF_QF: /* OK */
      value.Flt = measure[0].psfQual;
      break;
    case MEAS_PSF_CHISQ: /* OK */
      value.Flt = measure[0].psfChisq;
      break;
    case MEAS_CR_NSIGMA: /* OK */
      value.Flt = measure[0].crNsigma;
      break;
    case MEAS_EXT_NSIGMA: /* OK */
      value.Flt = measure[0].extNsigma;
      break;

      // add the star/galaxy sep -- 
  }
  return (value);
}

/** the mosaic entries do not use the registered mosaic found 
    by MatchImage (via FindMosaicForImage).  Rather, they use
    a coordinate frame saved by SetImageSelection 
**/
