# include "opihi.h"
void gfits_compress_timing ();
  
// write a set of vectors to a FITS FTable structure (vectors names become fits column names)
static int WriteVectorTable (FTable *ftable, char *extname, Vector **vec, int Nvec, char *format, char nativeOrder) {
  
  int j;

  char *tformat = NULL;

  Header *theader = ftable->header;
  gfits_create_table_header (theader, "BINTABLE", extname);

  ALLOCATE (tformat, char, 2*Nvec);
  if (format) {
    // the bintable format string can only define the byte-width of each field.  valid output columns are currently:
    // B (char), I (16 bit short), J (32 bit int), E (32 bit float), D (64 bit double).
    // the format string is just the sequence of types, eg: LIIJEED
    // validate the format string
    char *ptr = format;
    for (j = 0; j < Nvec; j++) {
      while (*ptr && OHANA_WHITESPACE (*ptr)) ptr++;
      if (*ptr == 0) {
	gprint (GP_ERR, "error in binary table format %s (insufficient format chars)\n", format);
	goto escape;
      }
      if ((*ptr != 'B') && (*ptr != 'I') && (*ptr != 'J') && (*ptr != 'K') && (*ptr != 'D') && (*ptr != 'E')) {
	gprint (GP_ERR, "error in binary table format %s: invalid character %c\n", format, *ptr);
	goto escape;
      }
      tformat[2*j + 0] = *ptr;
      tformat[2*j + 1] = 0; // a bit sleazy : use a 2xN string to store N 1-byte strings
      ptr ++;
    }
    while (*ptr && OHANA_WHITESPACE (*ptr)) ptr++;
    if (*ptr) {
      gprint (GP_ERR, "error in binary table format %s (extra characters in format)\n", format);
      goto escape;
    }
  } else {
    for (j = 0; j < Nvec; j++) {
      // if the format is not defined, just use the native byte-widths
      tformat[2*j + 0] = (vec[j][0].type == OPIHI_FLT) ? 'D' : 'J';
      tformat[2*j + 1] = 0;
    }
  }

  // define the columns of the table.  XXX NOTE: we cannot have duplicate names in
  // output table (because the data goes to the named column below).  need to enforce
  // this somehow
  for (j = 0; j < Nvec; j++) {
    gfits_define_bintable_column (theader, &tformat[2*j], vec[j][0].name, NULL, NULL, 1.0, 0.0);
  }
  free (tformat);

  // generate the output array that carries the data
  gfits_create_table (theader, ftable);

  // add the vectors to the output array
  for (j = 0; j < Nvec; j++) {
    if (vec[j][0].type == OPIHI_FLT) {
      gfits_set_bintable_column_reformat (theader, ftable, vec[j][0].name, "double", vec[j][0].elements.Flt, vec[j][0].Nelements, nativeOrder);
    } else {
      gfits_set_bintable_column_reformat (theader, ftable, vec[j][0].name, "int", vec[j][0].elements.Int, vec[j][0].Nelements, nativeOrder);
    }
  }
  return (TRUE);

 escape:
  if (tformat) free (tformat);
  return (FALSE);
}
  
# define VERBOSE 0
int dump_raw_table (FTable *table, char *message) {
# if (VERBOSE)
  int i;
  fprintf (stderr, "%s data: ", message);
  for (i = 0; i < table->header->Naxis[0]*table->header->Naxis[1]; i++) {
    fprintf (stderr, "0x%02hhx ", table->buffer[i]);
  }
  fprintf (stderr, "\n");
# else
  OHANA_UNUSED_PARAM(table);
  OHANA_UNUSED_PARAM(message);
# endif
  return TRUE;
}

int dump_cmp_table (FTable *table, char *message) {
# if (VERBOSE)
  int i;
  fprintf (stderr, "%s pntr: ", message);
  for (i = 0; i < table->header->Naxis[0]*table->header->Naxis[1]; i++) {
    fprintf (stderr, "0x%02hhx ", table->buffer[i]);
  }
  fprintf (stderr, "\n");

  fprintf (stderr, "%s data: ", message);
  for (i = 0; i < table->header->pcount; i++) {
    fprintf (stderr, "0x%02hhx ", table->buffer[table->heap_start + i]);
  }
  fprintf (stderr, "\n");
# else
  OHANA_UNUSED_PARAM(table);
  OHANA_UNUSED_PARAM(message);
# endif
  return TRUE;
}

// write a set of vectors to a FITS file (vectors names become fits column names)
int WriteVectorTableFITS (char *filename, char *extname, Vector **vec, int Nvec, int append, char *compress, char *format, int Ntile) {
  
  Header rawheader;
  Header cmpheader;
  FTable rawtable;
  FTable cmptable;

  FILE *f = NULL;

  /* open file for outuput */
  if (append) {
    f = fopen (filename, "a");
  } else {
    f = fopen (filename, "w");
  }
  if (f == (FILE *) NULL) {
    gprint (GP_ERR, "can't open file for write : %s\n", filename);
    return (FALSE);
  }

  // init so free below does not fail if table is not created
  gfits_init_header (&rawheader);
  gfits_init_header (&cmpheader);
  gfits_init_table (&rawtable);
  gfits_init_table (&cmptable);

  rawtable.header = &rawheader;
  if (!WriteVectorTable (&rawtable, extname, vec, Nvec, format, (compress != NULL))) goto escape;
  // NOTE : for compression, the table is constructed in native by order 

  FTable *outtable = &rawtable;
  Header *outheader = &rawheader;

  if (compress) {
    cmptable.header = &cmpheader;

    dump_raw_table (&rawtable, "wd raw");
    if (!gfits_compress_table (&rawtable, &cmptable, Ntile, compress)) goto escape;
    gfits_compress_timing ();

    int i, Nfields;
    if (!gfits_scan (&cmpheader, "TFIELDS", "%d", 1, &Nfields)) goto escape;
    for (i = 0; i < Nfields; i++) {
      gfits_byteswap_varlength_column (&cmptable, i+1);
    }
    dump_cmp_table (&cmptable, "wd cmp");

    outtable = &cmptable;
    outheader = &cmpheader;
  }

  if (!append) {
    Header header;
    Matrix matrix;
    gfits_init_header (&header);
    gfits_init_matrix (&matrix);
    header.extend = TRUE;
    gfits_create_header (&header);
    gfits_create_matrix (&header, &matrix);
    gfits_fwrite_header  (f, &header);
    gfits_fwrite_matrix  (f, &matrix);
    gfits_free_header (&header);
    gfits_free_matrix (&matrix);
  }

  // write the actual table data
  gfits_fwrite_Theader (f, outheader);
  gfits_fwrite_table  (f, outtable);

  gfits_free_header (&rawheader);
  gfits_free_table (&rawtable);

  if (compress) {
    gfits_free_header (&cmpheader);
    gfits_free_table (&cmptable);
  }

  fclose (f);
  fflush (f);
  return (TRUE);

 escape:
  gfits_free_header (&rawheader);
  gfits_free_table (&rawtable);

  if (compress) {
    gfits_free_header (&cmpheader);
    gfits_free_table (&cmptable);
  }

  fclose (f);
  fflush (f);
  return (FALSE);
}
  
// read the complete set of vectors from the given FITS file & extension
// XXX not quite right : I need to merge multiple vectors together:
// do not associate with an Opihi vector until later in mextract
Vector **ReadVectorTableFITS (char *filename, char *extname, int *nvec) {
  
  Header header;
  FTable ftable;

  int i, j;
  FILE *f = NULL;

  /* open file for input */
  f = fopen (filename, "r");
  if (f == (FILE *) NULL) {
    gprint (GP_ERR, "can't open file for read\n");
    return NULL;
  }

  ftable.header = &header;

  // read the full table data into a buffer
  if (!gfits_fread_ftable (f, &ftable, extname)) {
    fclose (f);
    return NULL;
  }

  // XXX handle binary and ascii tables (see read_vectors.c)
  // find the columns in the table
  int Nfields;
  gfits_scan (&header, "TFIELDS", "%d", 1, &Nfields);
  int Nrows = header.Naxis[1];

  // how many output vectors do we need?  depends on number of columns per field, but min is Nfields
  int Nvec = 0;
  int NVEC = Nfields;
  Vector **vec = NULL;
  ALLOCATE (vec, Vector *, NVEC);
  
  for (i = 0; i < Nfields; i++) {
    int Nval;
    char type[16], label[16], name[80];

    // determine the column name, data type, and number of sub-fields
    sprintf (label, "TTYPE%d", i + 1);
    int status = gfits_scan (&header, label, "%s", 1, name);
    assert (status);

    status = gfits_get_bintable_column_type_by_N (&header, i + 1, type, &Nval);
    assert (status);

    if (Nvec + Nval >= NVEC) {
      NVEC = Nvec + Nval + 16;
      REALLOCATE (vec, Vector *, NVEC);
    }

    int vecType = OPIHI_INT;
    if (!strcmp (type, "double") || !strcmp (type, "float")) {
      vecType = OPIHI_FLT;
    }

    // generate the needed vectors
    for (j = 0; j < Nval; j++) {
      vec[Nvec + j] = InitVector();
      if (Nval == 1) {
	strcpy (vec[Nvec + j]->name, name);
      } else {
	snprintf (vec[Nvec + j]->name, OPIHI_NAME_SIZE, "%s:%d", name, j);
      }
      ResetVector (vec[Nvec + j], vecType, Nrows);
    }

    // read the actual table data into a column
    void *data;
    status = gfits_get_bintable_column (&header, &ftable, name, &data);
    assert (status);

    if (!VectorAssignData(&vec[Nvec], type, data, Nrows, Nval)) {
      // free unneeded things
      gprint (GP_ERR, "trouble parsing data block type %s\n", type);
      return (NULL);
    }

    free (data);
    Nvec += Nval;
  }

  gfits_free_header (&header);
  gfits_free_table (&ftable);

  fclose (f);

  *nvec = Nvec;
  return vec;

//escape:
//  gfits_free_header (&header);
//  gfits_free_matrix (&matrix);
//  gfits_free_header (&theader);
//  gfits_free_table (&ftable);
//
//  fclose (f);
//  fflush (f);
//  return (FALSE);
}

# define ASSIGN_DATA(TYPE,DTYPE,OPTYPE)	       \
    /* assign the data to the actual vector */ \
    if (!strcmp (type, #TYPE)) { \
      DTYPE *Ptr = data;		    \
      for (k = 0; k < Nrows; k++) { \
	for (j = 0; j < Nval; j++, Ptr++) { \
	  vec[j][0].elements.OPTYPE[k] = *Ptr; \
	  } } return TRUE; }

int VectorAssignData (Vector **vec, char *type, void *data, int Nrows, int Nval) {

  int j, k;

  // assign the data to the actual vector
  ASSIGN_DATA(byte,    char,    Int);
  ASSIGN_DATA(char,    char,    Int);
  ASSIGN_DATA(short,   short,   Int);
  ASSIGN_DATA(int,     int,     Int);
  ASSIGN_DATA(int64_t, int64_t, Flt); // int64_t has a problem: Int is too small, Flt is wrong precision
  ASSIGN_DATA(float,   float,   Flt);
  ASSIGN_DATA(double,  double,  Flt);

  return FALSE;
}

# define ASSIGN_DATA_TRANSPOSE(TYPE,DTYPE,OPTYPE)	\
    /* assign the data to the actual vector */ \
    if (!strcmp (type, #TYPE)) { \
      DTYPE *Ptr = data;		    \
      for (k = 0; k < Nrows; k++) { \
	for (j = 0; j < Nval; j++, Ptr++) { \
	  vec[k][0].elements.OPTYPE[j] = *Ptr; \
	  } } return TRUE; }

int VectorAssignDataTranspose (Vector **vec, char *type, void *data, int Nrows, int Nval) {

  int j, k;

  // assign the data to the actual vector
  ASSIGN_DATA_TRANSPOSE(byte,    char,    Int);
  ASSIGN_DATA_TRANSPOSE(char,    char,    Int);
  ASSIGN_DATA_TRANSPOSE(short,   short,   Int);
  ASSIGN_DATA_TRANSPOSE(int,     int,     Int);
  ASSIGN_DATA_TRANSPOSE(int64_t, int64_t, Flt);
  ASSIGN_DATA_TRANSPOSE(float,   float,   Flt);
  ASSIGN_DATA_TRANSPOSE(double,  double,  Flt);

  return FALSE;
}
