# include "data.h"

FILE *f = (FILE *) NULL;
char filename[2048];

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

  if (argc < 2) {
    gprint (GP_ERR, "USAGE: datafile (filename)\n");
    return (FALSE);
  }
  
  strcpy (filename, argv[1]);
  if (f != (FILE *) NULL) { fclose (f); }
  f = fopen (filename, "r");
  if (f == (FILE *) NULL) {
    gprint (GP_ERR, "failed to open file %s\n", argv[1]);
    return (FALSE);
  }
  return (TRUE);
}

// vector types
enum {COLTYPE_NONE, COLTYPE_FLT, COLTYPE_INT, COLTYPE_TIME};

int read_vectors (int argc, char **argv) {
  
  int TimeFormat;
  time_t TimeReference;
  int i, j, Nskip, Narg, Nvec, *col, IsCSV, VERBOSE;
  int Nbytes, Nstart, NELEM, Nelem, nread, *coltype;
  char *colstr, *c0, *c1, *buffer, *extname;
  Vector **vec;

  /* auto-sense table type */
  if ((Narg = get_argument (argc, argv, "-fits"))) {
    remove_argument (Narg, &argc, argv);
    extname = strcreate (argv[Narg]);
    if (extname == (char *) NULL) return (FALSE);
    remove_argument (Narg, &argc, argv);
    int status = read_table_vectors (argc, argv, extname);
    free (extname);
    return (status);
  }

  Nskip = 0;
  if ((Narg = get_argument (argc, argv, "-skip"))) {
    remove_argument (Narg, &argc, argv);
    Nskip = atof (argv[Narg]);
    remove_argument (Narg, &argc, argv);
  }

  IsCSV = FALSE;
  if ((Narg = get_argument (argc, argv, "-csv"))) {
    remove_argument (Narg, &argc, argv);
    IsCSV = TRUE;
  }

  VERBOSE = FALSE;
  if ((Narg = get_argument (argc, argv, "-v"))) {
    remove_argument (Narg, &argc, argv);
    VERBOSE = TRUE;
  }

  if ((argc < 3) || !(argc % 2)) {
    gprint (GP_ERR, "USAGE: read name N name N ...\n");
    return (FALSE);
  }
  /* read name N name N  */

  // do this only optionally?
  GetTimeFormat (&TimeReference, &TimeFormat);

  if (f == (FILE *) NULL) {
    gprint (GP_ERR, "no open file for read\n");
    return (FALSE);
  }
  fseeko (f, 0LL, SEEK_SET);

  Nvec = (argc - 1) / 2;
  ALLOCATE (vec, Vector *, Nvec);
  ALLOCATE (col, int, Nvec);
  ALLOCATE (coltype, int, Nvec);

  for (i = 0; i < Nvec; i++) {

    // interpret the column names including type flags
    // XXX review the grammar before releasing this : is foo:type best, or is something else needed?
    // Note the conflict wrt list entries
    // the name may be of the form foo:type, where type may be one of : int, float, time
    
    coltype[i] = COLTYPE_FLT;
    char *colname = argv[2*i + 1];
    char *ptr = strchr (colname, ':');
    if (ptr) {
      // split out colname and type
      *ptr = 0;
      ptr ++;
      if (!ptr) goto bad_colname;
      coltype[i] = COLTYPE_NONE;
      if (!strcasecmp(ptr, "float")) { coltype[i] = COLTYPE_FLT; }
      if (!strcasecmp(ptr, "int"))   { coltype[i] = COLTYPE_INT; }
      if (!strcasecmp(ptr, "time"))  { coltype[i] = COLTYPE_TIME; }
      if (!coltype[i]) goto bad_colname;
    }

    if ((vec[i] = SelectVector (argv[2*i + 1], ANYVECTOR, TRUE)) == NULL) {
      gprint (GP_ERR, "USAGE: read name N name N ...\n");
      free (vec);
      free (col);
      return (FALSE);    
    }

    colstr = argv[2*i+2];
    for (j = 0; j < strlen (colstr); j++) {
      if (isdigit(colstr[j])) {
	col[i] = atof (colstr);
	continue;
      }
      // allow 'excel' columns names of the form A-Z, AA-AZ, BA-BZ, .. ZA-ZZ
      if (strlen(colstr) >= 3) goto bad_colname;
      if (colstr[0] < 'A') goto bad_colname;
      if (colstr[0] > 'Z') goto bad_colname;
      if (colstr[1] && colstr[1] < 'A') goto bad_colname;
      if (colstr[1] && colstr[1] > 'Z') goto bad_colname;

      col[i] = colstr[0] - 'A' + 1;
      if (colstr[1]) {
	col[i] *= 26;
	col[i] += colstr[1] - 'A' + 1;
      }
      continue;

    bad_colname:
      gprint (GP_ERR, "USAGE: read name N name N ...\n");
      free (vec);
      free (col);
      return (FALSE);    
    }
  }

  // currently, all read vectors are forced to be type FLT
  NELEM = 1000;
  for (i = 0; i < Nvec; i++) {
    if (coltype[i] == COLTYPE_INT) {
      ResetVector (vec[i], OPIHI_INT, NELEM);
    } else {
      // note that COLTYPE_TIME is a type of float
      ResetVector (vec[i], OPIHI_FLT, NELEM);
    }
  }
  
  ALLOCATE (buffer, char, 0x10001);
  bzero (buffer, 0x10001);
  for (i = 0; i < Nskip; i++) {
    scan_line (f, buffer);
  }

  // we have a working buffer read from the file. we parse the lines in the working buffer
  // until we reach the last chunk without an EOL char.  at that point, we shift the start
  // of the last (partial) line to the start of the buffer and re-fill.

  Nstart = 0; // location of the last valid byte in the buffer (start filling here)
  Nelem = 0; // number of valid rows read (vector elements)
  while (TRUE) {
    Nbytes = 0x10000 - Nstart;
    bzero (&buffer[Nstart], Nbytes);
    nread = fread (&buffer[Nstart], 1, Nbytes, f);
    if (ferror (f)) {
      perror ("error reading data file");
      break;
    }
    if (nread == 0) break; // end of the file
    // nbytes = nread + Nstart;
    
    int bufferStatus = TRUE; 
    c0 = buffer; // c0 always marks the start of a line
    while (bufferStatus) {
      c1 = strchr (c0, '\n'); // find the end of this current line
      if (c1 == (char *) NULL) {
	Nstart = strlen (c0);
	memmove (buffer, c0, Nstart);
	bufferStatus = FALSE;
	continue;
      }
      *c1 = 0; // mark the end of the line 

      if (*c0 == '#') {	c0 = c1 + 1; continue; }
      if (*c0 == '!') {	c0 = c1 + 1; continue; }

      // parse the vectors in this line.  this code is a bit inefficient: each column
      // requires a separate pass through the line.

      int lineStatus = TRUE;
      for (i = 0; i < Nvec; i++) {
	int ivalue;
	double dvalue;
	time_t tvalue;
	int readStatus = FALSE;
	// need to make the if cases for coltype[i]
	switch (coltype[i]) {
	  case COLTYPE_INT:
	    readStatus = IsCSV ? iparse_csv (&ivalue, col[i], c0) : iparse (&ivalue, col[i], c0);
	    vec[i][0].elements.Int[Nelem] = readStatus ? ivalue : 0;
	    break;
	  case COLTYPE_FLT:
	    readStatus = IsCSV ? dparse_csv (&dvalue, col[i], c0) : dparse (&dvalue, col[i], c0);
	    vec[i][0].elements.Flt[Nelem] = readStatus ? dvalue : NAN;
	    break;
	  case COLTYPE_TIME:
	    readStatus = IsCSV ? tparse_csv (&tvalue, col[i], c0) : tparse (&tvalue, col[i], c0);
	    dvalue = TimeValue (tvalue, TimeReference, TimeFormat);
	    vec[i][0].elements.Flt[Nelem] = readStatus ? dvalue : NAN;
	    break;
	}
	if (!readStatus && VERBOSE) {
	  if (IsCSV) {
	    gprint (GP_ERR, "suspect field: %d (%s) in %s\n", col[i], argv[2*i+2], c0);
	  } else {
	    gprint (GP_ERR, "suspect field: %d in %s\n", col[i], c0);
	  }
	}
	lineStatus &= readStatus;
      }
      if (!lineStatus && VERBOSE) {
	char temp[32];
	strncpy (temp, c0, 32);
	temp[31] = 0;
	gprint (GP_ERR, "skip line %s\n\n", temp);
      }
      Nelem ++;
      if (Nelem == NELEM) {
	NELEM += 1000;
	for (i = 0; i < Nvec; i++) {
	  if (coltype[i] == COLTYPE_INT) {
	    REALLOCATE (vec[i][0].elements.Int, opihi_int, NELEM);
	  } else {
	    REALLOCATE (vec[i][0].elements.Flt, opihi_flt, NELEM);
	  }
	}
      }
      c0 = c1 + 1;
    }
  }
  for (i = 0; i < Nvec; i++) {
    if (coltype[i] == COLTYPE_INT) {
      REALLOCATE (vec[i][0].elements.Int, opihi_int, MAX (Nelem,1));
    } else {
      REALLOCATE (vec[i][0].elements.Flt, opihi_flt, MAX (Nelem,1));
    }
    vec[i][0].Nelements = Nelem;
  }
  
  free (vec);
  free (col);
  free (buffer);
  return (TRUE);

}

# define ESCAPE(MSG) { \
  gprint (GP_ERR, "%s\n", MSG); \
  if (CCDKeyword != NULL) free (CCDKeyword); \
  gfits_free_table  (&table); \
  gfits_free_header (&header); \
  return (FALSE);  }

int read_table_vectors (int argc, char **argv, char *extname) {

  off_t Nbytes;
  int i, j, k, N, Nextend, Ny, Binary, vecType, padIfShort;
  char type[16], ID[80], *CCDKeyword;
  FTable table;
  Header header;
  Vector **vec;

  table.buffer = NULL;
  header.buffer = NULL;

  CCDKeyword = NULL;
  if ((N = get_argument (argc, argv, "-keyword"))) {
    remove_argument (N, &argc, argv);
    CCDKeyword = strcreate (argv[N]);
    remove_argument (N, &argc, argv);
  }

  padIfShort = FALSE;
  if ((N = get_argument (argc, argv, "-pad-if-short"))) {
    remove_argument (N, &argc, argv);
    padIfShort = TRUE;
  }

  Nextend = -1;
  if ((N = get_argument (argc, argv, "-extnum"))) {
    remove_argument (N, &argc, argv);
    Nextend = atoi (extname);
  }

  // XXX ReadAll needs: deal with Extnum vs Extname, save vectors, etc
  // ReadAll = FALSE;
  // if ((N = get_argument (argc, argv, "-all"))) {
  //   remove_argument (N, &argc, argv);
  //   ReadAll = atoi (extname);
  //   if (argc != 1) ESCAPE ("-all option cannot be mixed with selected field");
  // }

  if (argc < 2) ESCAPE ("USAGE: read -fits extension [-extnum] [-keyword key] name name ...");

  if (f == NULL) ESCAPE ("file not found");
  fseeko (f, 0LL, SEEK_SET);
  table.header = &header;

  /* load appropriate extension (if extname is a number, use count) */
  if (Nextend > -1) {
    // first extension is PHU, cannot be a table. 
    // Nextend counts from 0 for first extension
    if (!gfits_load_header (f, &header)) ESCAPE ("error reading primary header for file");
    Nbytes = gfits_data_size (&header);
    fseeko (f, Nbytes, SEEK_CUR);
    gfits_free_header (&header);

    for (i = 0; i < Nextend; i++) {
      if (!gfits_load_header (f, &header)) ESCAPE ("extension not found");
      Nbytes = gfits_data_size (&header);
      /* skip the prior data buffers */
      fseeko (f, Nbytes, SEEK_CUR);
      gfits_free_header (&header);
    }
    if (!gfits_load_header (f, &header)) ESCAPE ("error reading header for extension");
    if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension");

  } else {
    if (CCDKeyword == NULL) {
      CCDKeyword = get_variable ("CCDKEYWORD");
    }
    if (CCDKeyword == NULL) {
      CCDKeyword = strcreate ("EXTNAME");
    }

    while (1) {
      if (!gfits_load_header (f, &header)) {
	gprint (GP_ERR, "extension %s not found in file\n", extname);
	if (CCDKeyword != NULL) free (CCDKeyword); 
	gfits_free_table  (&table); 
	gfits_free_header (&header); 
	return (TRUE);  
      }

      Nbytes = gfits_data_size (&header);

      if (!gfits_scan (&header, CCDKeyword, "%s", 1, ID)) {
	fseeko (f, Nbytes, SEEK_CUR);
	gfits_free_header (&header);
	continue;
      }
      if (strcmp (ID, extname)) {
	fseeko (f, Nbytes, SEEK_CUR);
	gfits_free_header (&header);
	continue;
      }
      if (!gfits_fread_ftable_data (f, &table, padIfShort)) ESCAPE ("error reading table for extension");
      break;
    }
  }

  /* identify table type (ascii / binary) */
  Binary = FALSE;
  gfits_scan (&header, "XTENSION", "%s", 1, type);
  if (strcmp (type, "BINTABLE") && strcmp (type, "TABLE")) {
    ESCAPE ("specified extension is not a table\n");
  }
  Binary = !strcmp (type, "BINTABLE");
  Ny = header.Naxis[1];

  /* find columns which match requested vectors */
  for (i = 1; i < argc; i++) {
    void   *data;
    int Nval;
    char name[80];
      
    Nval = 0;
    if (Binary) {
      if (!gfits_get_bintable_column_type (&header, argv[i], type, &Nval)) ESCAPE ("requested field not found");
      if (!gfits_get_bintable_column (&header, &table, argv[i], &data)) ESCAPE ("error reading data from specified field");
    } else {
      if (!gfits_get_table_column_type (&header, argv[i], type)) ESCAPE ("requested field not found");
      if (!gfits_get_table_column (&header, &table, argv[i], &data)) ESCAPE ("error reading data from specified field");
    }
    if (Nval == 0) ESCAPE ("no data for field in table");
    
    vecType = OPIHI_INT;
    if (!strcmp (type, "double") || !strcmp (type, "float")) {
      vecType = OPIHI_FLT;
    }
	
    // define the multifield vector names
    ALLOCATE (vec, Vector *, Nval);
    for (j = 0; j < Nval; j++) {
      if (Nval == 1) 
	sprintf (name, "%s", argv[i]);
      else
	sprintf (name, "%s:%d", argv[i], j);
      if ((vec[j] = SelectVector (name, ANYVECTOR, TRUE)) == NULL) ESCAPE ("bad vector name");
      ResetVector (vec[j], vecType, Ny);
    }

    if (!strcmp (type, "char")) {
      char *Ptr = data;
      for (j = 0; j < Ny; j++) {
	for (k = 0; k < Nval; k++, Ptr++) {
	  vec[k][0].elements.Int[j] = *Ptr;
	}
      }
    }
    if (!strcmp (type, "short")) {
      short *Ptr = data;
      for (j = 0; j < Ny; j++) {
	for (k = 0; k < Nval; k++, Ptr++) {
	  vec[k][0].elements.Int[j] = *Ptr;
	}
      }
    }
    if (!strcmp (type, "int")) {
      int *Ptr = data;
      for (j = 0; j < Ny; j++) {
	for (k = 0; k < Nval; k++, Ptr++) {
	  vec[k][0].elements.Int[j] = *Ptr;
	}
      }
    }
    if (!strcmp (type, "int64_t")) {
      int64_t *Ptr = data;
      for (j = 0; j < Ny; j++) {
	for (k = 0; k < Nval; k++, Ptr++) {
	  vec[k][0].elements.Int[j] = *Ptr;
	}
      }
    }
    if (!strcmp (type, "float")) {
      float *Ptr = data;
      for (j = 0; j < Ny; j++) {
	for (k = 0; k < Nval; k++, Ptr++) {
	  vec[k][0].elements.Flt[j] = *Ptr;
	}
      }
    }
    if (!strcmp (type, "double")) {
      double *Ptr = data;
      for (j = 0; j < Ny; j++) {
	for (k = 0; k < Nval; k++, Ptr++) {
	  vec[k][0].elements.Flt[j] = *Ptr;
	}
      }
    }
    free (data);
    free (vec);
  }
  if (CCDKeyword != NULL) free (CCDKeyword);
  gfits_free_table (&table);
  gfits_free_header (&header);
  return (TRUE);
}
