# include "dvopsps.h"
# define DEBUG 0

# define MARKTIME(MSG,...) {			\
    float dtime;				\
    gettimeofday (&stop, (void *) NULL);	\
    dtime = DTIME (stop, start);		\
    fprintf (stderr, MSG, __VA_ARGS__); }

int Ndetections = 0;
int NDETECTIONS = 0;
Detections *detections = NULL;

int init_detections () {
  NDETECTIONS = 1000;
  ALLOCATE (detections, Detections, NDETECTIONS);
  return TRUE;
}

int append_detections_dvopsps_catalog (Catalog *catalog) {

  off_t i, j;

  int missingID = 0;

  if (Ndetections + catalog[0].Nmeasure >= NDETECTIONS) {
    NDETECTIONS = Ndetections + catalog[0].Nmeasure + 1000;
    REALLOCATE (detections, Detections, NDETECTIONS);
  }

  time_t TIME_START_SEC = 0;
  time_t TIME_END_SEC   = 0;
  if (TIME_START) ohana_str_to_time (TIME_START, &TIME_START_SEC); // we validate this in the args.c
  if (TIME_END)   ohana_str_to_time (TIME_END,   &TIME_END_SEC);   // we validate this in the args.c

  for (i = 0; i < catalog[0].Naverage; i++) {

    if (catalog[0].average[i].extID == 0) {
      missingID ++;
    }

    off_t m = catalog[0].average[i].measureOffset;
    for (j = 0; j < catalog[0].average[i].Nmeasure; j++) {

      Average *average = &catalog->average[i];
      Measure *measure = &catalog->measure[m + j];

      // some filters -- these are the detections we skip
      if (TIME_START && (measure->t < TIME_START_SEC)) continue;
      if (TIME_END   && (measure->t >=  TIME_END_SEC)) continue;

      if (measure->photcode < PHOTCODE_START) continue;
      if (measure->photcode >=  PHOTCODE_END) continue;

      PhotCode *code = GetPhotcodebyCode(measure->photcode);

      detections[Ndetections].imageID     = measure->imageID;    // imageID
      detections[Ndetections].ippDetectID = measure->detID;   // ippDetectID
      detections[Ndetections].detectID    = measure->extID;   // detectID
      detections[Ndetections].ippObjID    = ((uint64_t)average->catID * 1000000000) + (uint64_t)average->objID; // ippObjID
      // NOTE: this is better, but the above is the current ippToPsps value
      // detections[Ndetections]. ((uint64_t)average->catID << 32) + (uint64_t)average->objID; // ippObjID
      detections[Ndetections].objID	   = average->extID;   // objID
      detections[Ndetections].photcode	   = measure->photcode;   // photcode
      detections[Ndetections].flags 	   = measure->dbFlags;  // flags
      detections[Ndetections].zp 	   = code->C * 0.001 + code->K * (measure->airmass - 1) - measure->Mcal;   // zp
      detections[Ndetections].zpErr 	   = measure->dMcal;    // zpErr
      detections[Ndetections].airMass      = measure->airmass;
      detections[Ndetections].expTime      = pow(10.0, 0.4 * measure->dt);    // expTime
      detections[Ndetections].ra 	   = average->R - measure->dR / 3600.;    // ra
      detections[Ndetections].dec 	   = average->D - measure->dD / 3600.;    // dec
      detections[Ndetections].raErr 	   = measure->dXccd * 0.01 * fabs(measure->pltscale); // estimate of raErr
      detections[Ndetections].decErr	   = measure->dYccd * 0.01 * fabs(measure->pltscale); // estimate of decErr

      Ndetections ++;
      myAssert (Ndetections <= NDETECTIONS, "programming error");
    }
  }

  fprintf (stderr, "write %d detections, %d missing extID\n", Ndetections, missingID);
  return TRUE;
}

// XXX write a single table for all detections or a series of tables?
// if I write a single table, then I have to store all data in memory
// if I write multiple tables, then I need to (a) iterate over the tables and (b) 
// incur seek hits on the file
int save_detections_dvopsps () {

  int status = TRUE;

  if (!DetectionsSave (RESULT_FILE, detections, Ndetections)) {
    fprintf (stderr, "failed to save detection file %s\n", RESULT_FILE);
    status = FALSE;
  }

  free (detections);
  Ndetections = 0;
  return (status);
}

time_t TIME_START_SEC = 0;
time_t TIME_END_SEC   = INT_MAX;

int insert_detections_dvopsps_catalog (Catalog *catalog, MYSQL *mysql) {

  off_t i, j;
  int missingID = 0;
  struct timeval start, stop;

  IOBuffer buffer;
  buffer.Nalloc = 0;

  Average *average = catalog->average;
  Measure *measure = catalog->measure;

  off_t found = 0;

  gettimeofday (&start, (void *) NULL);
  insert_detections_mysql_init (&buffer);
  int Ninsert = 0;
  int Ntotal = 0;

  int status = TRUE;

  if (TIME_START) ohana_str_to_time (TIME_START, &TIME_START_SEC); // we validate this in the args.c
  if (TIME_END)   ohana_str_to_time (TIME_END,   &TIME_END_SEC);   // we validate this in the args.c

  // NOTE for testing, just do a few objects
  if (!mysql) {
    catalog[0].Naverage = 5;
  }
  for (i = 0; i < catalog[0].Naverage; i++) {

    if (average[i].extID == 0) {
      missingID ++;
    }

    off_t m = average[i].measureOffset;
    for (j = 0; j < average[i].Nmeasure; j++) {

      off_t Nmeas = m + j;

      // some filters -- these are the detections we skip (not an error, just skipped)
      if (TIME_START && (measure[Nmeas].t < TIME_START_SEC)) continue;
      if (TIME_END   && (measure[Nmeas].t >=  TIME_END_SEC)) continue;
  
      if (measure[Nmeas].photcode < PHOTCODE_START) continue;
      if (measure[Nmeas].photcode >=  PHOTCODE_END) continue;

      // XXX check return status
      if (!insert_detections_mysql_value (&buffer, &average[i], &measure[Nmeas])) {
	fprintf (stderr, "failure to insert detections in mysql\n");
	status = FALSE;
      }
      Ninsert ++;
      Ntotal ++;

      if (buffer.Nbuffer > MAX_BUFFER) {
	if (!insert_detections_mysql_commit (&buffer, mysql)) {
	  fprintf (stderr, "failure to insert detections in mysql (commit)\n");
	  status = FALSE;
	}
	if (DEBUG) fprintf (stderr, "inserted %d rows\n", Ninsert);
	Ninsert = 0;
	if (0) {
	  FlushIOBuffer (&buffer);
	} else {
	  buffer.Nbuffer = 0;
	  bzero (buffer.buffer, buffer.Nalloc);
	}
	insert_detections_mysql_init (&buffer);
      }
      found ++;
    }
  }
  if (Ninsert > 0) {
    // insert the remaining detections loaded in the buffer
    if (!insert_detections_mysql_commit (&buffer, mysql)) {
      fprintf (stderr, "failure to insert detections in mysql (commit)\n");
      status = FALSE;
    }
  }
  
  if (VERBOSE) fprintf (stderr, "inserted %d rows\n", Ntotal);
  FreeIOBuffer (&buffer);

  if (!status) {
    MARKTIME("-- failed to insert "OFF_T_FMT" rows in %f sec\n", found, dtime);
    return (FALSE);
  }

  MARKTIME("-- inserted "OFF_T_FMT" rows in %f sec, skipped %d without IDs\n", found, dtime, missingID);
  return (TRUE);
}

int insert_detections_mysql_init (IOBuffer *buffer) {

  if (buffer->Nalloc == 0) {
    InitIOBuffer (buffer, 1024);
  }

  // PrintIOBuffer (buffer, "INSERT INTO dvoDetectionFull (imageID, ippDetectID, detectID, ippObjID, objID, photcode, flags, zp, zpErr, airMass, expTime, ra, dec_, raErr, decErr) VALUES \n");
  PrintIOBuffer (buffer, "INSERT INTO dvoDetectionFull (imageID, ippDetectID, detectID, ippObjID, objID, flags, zp, zpErr, airMass, expTime, ra, dec_, raErr, decErr) VALUES \n");

  return TRUE;
}

# define PRINT_FLOAT(BUFFER,FIELD,FORMAT)		  \
  if (isinf(FIELD) || isnan(FIELD)) PrintIOBuffer (BUFFER, "NULL, ");	\
  else PrintIOBuffer (BUFFER, FORMAT, FIELD); 

int insert_detections_mysql_value (IOBuffer *buffer, Average *average, Measure *measure) {

  PhotCode *code = GetPhotcodebyCode(measure->photcode);
  PrintIOBuffer (buffer, "(%d, ", measure->imageID);	  // imageID
  PrintIOBuffer (buffer, "%u, ", measure->detID); // ippDetectID
  PrintIOBuffer (buffer, "%lu, ", measure->extID);	  // detectID
  PrintIOBuffer (buffer, "%lu, ", ((uint64_t)average->catID * 1000000000) + (uint64_t)average->objID);	  // ippObjID
  // NOTE: the following is better, but the above is the current ippToPsps value
  // ((uint64_t)average->catID << 32) + (uint64_t)average->objID, // ippObjID
  PrintIOBuffer (buffer, "%lu, ", average->extID);	  // objID
  // XXX PrintIOBuffer (buffer, "%d, ", measure->photcode);	  // photcode
  PrintIOBuffer (buffer, "%u, ", measure->dbFlags);	  // flags

  float zp = code->C * 0.001 + code->K * (measure->airmass - 1) - measure->Mcal;
  float exptime = pow(10.0, 0.4 * measure->dt);
  double ra  = average->R - measure->dR / 3600.0;
  double dec = average->D - measure->dD / 3600.0;
  double dR = measure->dXccd * 0.01 * fabs(measure->pltscale);
  double dD = measure->dYccd * 0.01 * fabs(measure->pltscale);

  PRINT_FLOAT(buffer, zp,               "%.6f, ");
  PRINT_FLOAT(buffer, measure->dMcal,   "%.6f, ");
  PRINT_FLOAT(buffer, measure->airmass, "%.6f, ");
  PRINT_FLOAT(buffer, exptime,          "%.6f, ");
  PRINT_FLOAT(buffer, ra,               "%.8f, ");
  PRINT_FLOAT(buffer, dec,              "%.8f, ");
  PRINT_FLOAT(buffer, dR,               "%.6f, ");
  PRINT_FLOAT(buffer, dD,               "%.6f),\n");
  
  return TRUE;
}
    
int insert_detections_mysql_commit (IOBuffer *buffer, MYSQL *mysql) {

  MYSQL_RES *result;

  int status = TRUE;

  // check that the last two chars are ,\n and replace with ;\n
  if (!strcmp(&buffer->buffer[buffer->Nbuffer-2], ",\n")) {
    buffer->buffer[buffer->Nbuffer-2] = ';';
  } else {
    fprintf (stderr, "invalid sql?\n");
    int Nstart = MAX(0, buffer->Nbuffer - 3000);
    fprintf (stderr, "buffer: ...%s...\n", &buffer->buffer[Nstart]);
    return FALSE;
  }

  // XXX check return status
  if (mysql) {
    if (DEBUG) fprintf (stderr, "%s\n", buffer->buffer);
    int mysqlStatus = mysql_query(mysql, buffer->buffer); 
    if (mysqlStatus) {
      fprintf (stderr, "error with insert:\n");
      fprintf (stderr, "%s\n", mysql_error(mysql));
      fprintf (stderr, "Nbuffer: %d\n", buffer->Nbuffer);
      status = FALSE;
    }
    result = mysql_store_result (mysql);
    mysql_free_result (result);
  } else {
    fprintf (stderr, "%s\n", buffer->buffer);
  }

  return status;
}
