/*
 *  LaRC_visst_unpacker.c
 *
 *  Written by Greg Nowicki, AS&M under contract to
 *  Langley Research Center, 2003-2004
 *
 *  unpackCP - unpack bit format data to float
 */

#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "vintrt.h"

static int check_sizes(const int *, int, int, int, const char *);
void change_endian(unsigned int *, int);

char VERSION[] = "LaRC_visst_unpacker: V1.0 Langley Research Center";

#ifndef DEBUG
#define DEBUG 0
#endif
#define ERROR_RETURN -1

/*
 *  this code is designed to unpack the data that is packed using the LaRC Minnis cloud
 *  group's VISST data packer. the reason the data is packed because of the huge data sets
 *  created by the processing of cloud data. reductions of over 60% are being achieved,
 *  justifing the overhead of supporting this code.
 *
 *  Danger, Will Robinson!!!
 *  _YOU_ are required to make sure that the bounds of the array are not exceeded.
 *
 *  parameters:
 *   char *fn - pointer to NULL (0) terminated string containing the filename of the packed
 *              data to be unpacked
 *   int *array_size - pointer to a 4 byte integer array with 3 (or more) elements which
 *                     contain the actual physical size of the data array so array indexing
 *                     can be done correctly. this should be XxYxZ where in a Fortran array
 *                     (column-major) the X indicates the number of pixels, Y states the
 *                     number of lines, and Z denotes the number of parameters. for a C
 *                     array (row-major) the definitions of X and Z are reversed. this is
 *                     done for the efficency reasons when cycling through the array, one
 *                     always wants to access the elements with the least amount of "stride"
 *                     between them. so you want to access Fortran arrays (1,1,1), (2,1,1),
 *                     etc. and C arrays as [0][0][0], [0][0][1], etc. this routine doesn't
 *                     care WHY it's packed that way, but it needs to know HOW it is aligned.
 *   int *selection - an array of integers, number of parameters in size. allows the caller to
 *                    selectively unpack and return only the data they require. if the array
 *                    contains [ 0, 0, 1, 0, 1, 0, ..., 0 ], the caller would be returned only
 *                    the data values from the third and fifth line by pixel parameter.
 *   int *mcidas_navarr - pointer to int array large enough to hold the mcidas navigation
 *                        array (usually 640 4 byte integers)
 *   int *mcidas_head - pointer to int array large enough to hold the mcidas header array
 *                      (usually 64 4 byte integers)
 *   int *valid_size - pointer to a 4 byte integer array with 3 (or more) elements which will
 *                     have the actual nparams x nlines x npixels numbers put into the first
 *                     three elements. this is the actual number of data points which have
 *                     been read from the data file and modified by this routine.
 *   float *data - pointer to float array where data is to be unpacked into. this must be
 *                 large enough to hold all of the data which is unpacked! see array_size.
 *
 *  this routine will _NOT_ touch the data in the first three parameters, but it will overwrite
 *  data contained in the last four
 *
 *  returns ERROR_RETURN upon routine failure, 0 for success.
 *
 *  written by GDN starting 24-Oct-2002
 *
 */
/*
 * wrapper for IDL calls
 */
int unpackcp_idl(int argc, void *argv[])
{
  const char *fn;
  const int *array_size;
  const int *selection;
  int *mcidas_navarr;
  int *mcidas_head;
  int *valid_size;
  float *data;
  float *lat_lon;
  char *comment;

  if (argc != 9) {
    fprintf(stderr, "Bad number of parameters to unpackcp_idl: %d\n", argc);
    return(-1);
  }
  fn = (char *) argv[0];
  array_size = (int *) argv[1];
  selection = (int *) argv[2];
  mcidas_navarr = (int *) argv[3];
  mcidas_head = (int *) argv[4];
  valid_size = (int *) argv[5];
  data = (float *) argv[6];
  lat_lon = (float *) argv[7];
  comment = (char *) argv[8];

  return (unpackCP(fn, array_size, selection, mcidas_navarr,
                 mcidas_head, valid_size, data, lat_lon, comment));
}


/*
 * wrappers for Fortran calls
 */
int unpackcp_(const char *fn, const int *array_size, const int *selection, int *mcidas_navarr,
             int *mcidas_head, int *valid_size, float *data, float *lat_lon, char *comment)
{
  return (unpackCP(fn, array_size, selection, mcidas_navarr,
                 mcidas_head, valid_size, data, lat_lon, comment));
}

int unpackCP_(const char *fn, const int *array_size, const int *selection, int *mcidas_navarr,
             int *mcidas_head, int *valid_size, float *data, float *lat_lon, char *comment)
{
  return (unpackCP(fn, array_size, selection, mcidas_navarr,
                 mcidas_head, valid_size, data, lat_lon, comment));
}

int unpackCP(const char *fn, const int *array_size, const int *selection, int *mcidas_navarr,
             int *mcidas_head, int *valid_size, float *data, float *lat_lon, char *comment)
{
  int i, j, k, pos, index, offset, extra, status, curr_pos, curr_arr_pos;
  unsigned int uvalue, nbits, scale, ints2read, *values;
  unsigned int start_pos[MAX_PARAMETERS], num2read[MAX_PARAMETERS];
  int nparams, nlines, npixels, mcidas_head_size, mcidas_navigation_size;
  float *p, neg;
  FILE *f;
  int data_format_version, ascii_header_size, adj_nparams;
  char data_format[DATA_FORMAT_SIZE + 1], *str1, *str2, *tt;
  unsigned char elems[MAX_PARAMETERS];
  static char ename[] = "visst_unpacker error:";
  static char dname[] = "visst_unpacker debug:";

  if ((f = fopen(fn, "rb")) == NULL) {
    fprintf(stderr, "%s Opening data file: %s\n", ename, fn);
    return ERROR_RETURN;
  }
/*
 *  to make this routine as generic as possible, we'll read juat enough data to determine
 *  the format of the data contained within this data file, then read the data.
 */
  if ((status = fread(data_format, sizeof(char), DATA_FORMAT_SIZE, f)) != DATA_FORMAT_SIZE) {
    fprintf(stderr, "%s Reading data_format from file: %s  number: %d\n", ename, fn, status);
    return ERROR_RETURN;
  }
  data_format_version = atoi(data_format);
  switch (data_format_version) {
    case 0:
    case 1:
      ascii_header_size = MAX_ASCII_SIZE - DATA_FORMAT_SIZE;
      break;
    default:
      fprintf(stderr, "%s Unknown data format version: %d\n", ename, data_format_version);
      return ERROR_RETURN;
  }
  if ((str1 = calloc(ascii_header_size + DATA_FORMAT_SIZE, sizeof(char))) == NULL) {
    fprintf(stderr, "%s callocing str1, size: %d\n", ename, ascii_header_size + DATA_FORMAT_SIZE);
    return ERROR_RETURN;
  }
  if ((status = fread(str1 + DATA_FORMAT_SIZE, sizeof(char), ascii_header_size, f)) != ascii_header_size) {
    fprintf(stderr, "%s Reading char_info from file: %s  number: %d\n", ename, fn, status);
    return ERROR_RETURN;
  }
  memcpy(str1, data_format, DATA_FORMAT_SIZE);
/*
 *  we don't _need_ a second copy, but since strtok hoses the string as it parses it,
 *  let's keep a 'virgin' copy around.
 */
  if ((str2 = calloc(ascii_header_size + DATA_FORMAT_SIZE, sizeof(char))) == NULL) {
    fprintf(stderr, "%s callocing str2, size: %d\n", ename, ascii_header_size + DATA_FORMAT_SIZE);
    return ERROR_RETURN;
  }
  memcpy(str2, str1, ascii_header_size + DATA_FORMAT_SIZE);
/*
 *  pick off the data format string.
 */
  tt = strtok(str1, " \t\0");
/*
 *  pick off the date string.
 */
  tt = strtok(NULL, " \t\0");
/*
 *  pick off the time string.
 */
  tt = strtok(NULL, " \t\0");
/*
 *  pick off the number of parameters and convert it to an int.
 */
  tt = strtok(NULL, " \t\0");
  nparams = atoi(tt);
/*
 *  pick off the number of lines and convert it to an int.
 */
  tt = strtok(NULL, " \t\0");
  nlines = atoi(tt);
/*
 *  pick off the number of pixels and convert it to an int.
 */
  tt = strtok(NULL, " \t\0");
  npixels = atoi(tt);
/*
 *  pick off the number of mcidas navigation block integers.
 */
  tt = strtok(NULL, " \t\0");
  mcidas_navigation_size = atoi(tt);
/*
 *  pick off the number of mcidas head block integers.
 */
  tt = strtok(NULL, " \t\0");
  mcidas_head_size = atoi(tt);
/*
 *  pick off the date and time of file creation, discard ;-(
 */
  tt = strtok(NULL, " \t\0");
  tt = strtok(NULL, " \t\0");
/*
 *  pick off the latitude and longitude values
 */
  for (i = 0; i < 6; i++) {
    tt = strtok(NULL, " \t\0");
    lat_lon[i] = atof(tt);
  }

  strncpy(comment, &str2[DEDICATED_ASCII_SIZE], COMMENT_SIZE);
/*
 *  let's do a little error checking here. never assume that the user gives good input.
 *
 *  check to make sure the user has provided enough space in which to place the data.
 *  use adj_nparams to compare because the whole idea is to allow the caller to
 *  request a subset of the data and only provide enough space for that subset.
 */
  for (i = 0, adj_nparams = 0; i < array_size[0]; i++) {
    if (selection[i] == 1) {
      adj_nparams++;
    }
  }
  j = i;
  if (check_sizes(array_size, adj_nparams, nlines, npixels, "array_size") == ERROR_RETURN) {
    fprintf(stderr, "%s Unrecoverable error.\n", ename);
    return ERROR_RETURN;
  }
/*
 *  now we know how much data to read. tell the caller how much of his/her data array is filled.
 */
  valid_size[0] = nparams;
  valid_size[1] = nlines;
  valid_size[2] = npixels;
  if (nparams > MAX_PARAMETERS) {
    fprintf(stderr, "%s MAX_PARAMETERS not set big enough: %d\n", ename, nparams);
    return ERROR_RETURN;
  }
  if ((values = calloc(nlines * npixels, sizeof(int))) == NULL) {
    fprintf(stderr, "%s callocing values, size: %d\n", ename, nlines * npixels);
    return ERROR_RETURN;
  }
/*
 *  read in the char array which contains parameter index.
 */
  if ((status = fread(elems, sizeof(elems), 1, f)) != 1) {
    fprintf(stderr, "%s Reading char array from file: %s  number: %d\n", ename, fn, status);
    return ERROR_RETURN;
  }
/*
 *  read in the mcidas navigation array.
 */
  if ((status = fread(mcidas_navarr, sizeof(int), mcidas_navigation_size, f)) != mcidas_navigation_size) {
    fprintf(stderr, "%s Reading mcidas navarr from file: %s  number: %d\n", ename, fn, status);
    return ERROR_RETURN;
  }
/*
 *  read in the mcidas header array.
 */
  if ((status = fread(mcidas_head, sizeof(int), mcidas_head_size, f)) != mcidas_head_size) {
    fprintf(stderr, "%s Reading mcidas head from file: %s  number: %d\n", ename, fn, status);
    return ERROR_RETURN;
  }
  change_endian((unsigned int *) mcidas_navarr, mcidas_navigation_size);
  change_endian((unsigned int *) mcidas_head, mcidas_head_size);

#if DEBUG
  printf("%s  mcidas_navigation_size: %d mcidas_head_size: %d\n",
         dname, mcidas_navigation_size, mcidas_head_size);
  printf("%s  nparams: %d nlines: %d npixels: %d\n", dname, nparams, nlines, npixels);
#endif
/*
 *  now, we unpack the data, nbits at a time and put it into the correct float array location.
 */
  curr_pos = ftell(f);
  for (i = 0; i < nparams; i++) {
    nbits = full_list[elems[i]].bits;
    ints2read = (nbits * nlines * npixels) / BITS_PER_INT;
    if ((nbits * nlines * npixels) % BITS_PER_INT != 0) {
      ints2read++;
    }
    start_pos[i] = curr_pos;
    num2read[i] = ints2read;
    curr_pos += (ints2read * sizeof(int));
  }
  j = i;
#if DEBUG
  printf("%s  Max array size in integers: %d\n", dname, array_size[0] * array_size[1] * array_size[2]);
#endif
  curr_arr_pos = 0;
  for (i = 0; i < nparams; i++) {

/*
 *  if we're selecting parameters and the current parameter is not set to 1,
 *  then we skip this parameter.
 */
    if (selection[i] != 1) {
      continue;
    }
/*
 *  given the number of bits per data value and number of data values, how many
 *  4 byte integers should I read for each parameter?
 */
    nbits = full_list[elems[i]].bits;
    scale = full_list[elems[i]].scale;
    ints2read = num2read[i];
#if DEBUG
    printf("%s  %d: nbits: %d scale: %d ints2read: %d\n", dname, i, nbits, scale, ints2read);
#endif
/*
 *  read 'em in.
 */
    if (fseek(f, start_pos[i], SEEK_SET) != 0) {
      fprintf(stderr, "%s positioning to byte pos: %d from file: %s\n", ename, start_pos[i], fn);
      return ERROR_RETURN;
    }
    if ((status = fread(values, sizeof(int), ints2read, f)) != ints2read) {
      fprintf(stderr, "%s Reading packed: %d from file: %s  number: %d\n", ename, i, fn, status);
      return ERROR_RETURN;
    }
    change_endian(values, ints2read);
/*
 *  process the data value one line and one pixel at a time.
 */
#if DEBUG
  printf("%s  About to fill array elements: %d-%d\n", dname, i * array_size[1] * array_size[2],
          (i + 1) * array_size[1] * array_size[2] - 1);
#endif
    for (j = 0, pos = 0; j < nlines; j++) {
/*
 *  reset array positioning because the storage array may be larger than the data
 *  being unpacked (or it may be the same size)
 */
      p = data + j * array_size[2] + curr_arr_pos * array_size[1] * array_size[2];
      for (k = 0; k < npixels; k++, p++, pos += nbits) {
        index = pos / BITS_PER_INT;
        offset = pos % BITS_PER_INT;
/*
 *  how many extra bits (outside of this BITS_PER_INT bit int) are there?
 */
        extra = offset + nbits - BITS_PER_INT;
/*
 *  shift and mask the current int array value.
 *  right shift on a signed int brings the sign bit along for the ride, so do the
 *  shift on the unsiged value.
 */
        uvalue = values[index];
        uvalue = (uvalue >> offset) & MASK[nbits];
/*
 *  OK, do we need to go into the next array element to get the rest of the number?
 */
        if (extra > 0) {
          uvalue += ((values[index + 1] & MASK[extra]) << (BITS_PER_INT - offset));
        }
/*
 *  see if the negative flag is set, if so, remove it and negate the value.
 */
        if ((uvalue >> (nbits - 1)) == 1) {
          uvalue &= MASK[nbits - 1];
          neg = -1.;
        } else {
          neg = 1.;
        }
        *p = neg * (float) uvalue / scale;
      }  /* end of k loop */
    }  /* end of j loop */
    curr_arr_pos++;
#if DEBUG
    printf("%s  unpack: i: %d  index: %u\n", dname, i, index);
#endif
  }  /* end of i loop */
#if DEBUG
  printf("%s  Time to free strings\n", dname);
#endif
  free(str1);
  free(str2);
#if DEBUG
  printf("%s  Time to free values\n", dname);
#endif
  free(values);
#if DEBUG
  printf("%s  Time to return\n", dname);
#endif
  fclose(f);
  return 0;
}

void change_endian(unsigned int *data, int num)
{
  unsigned char *p = (unsigned char *) data;
  int i;

  for (i = 0; i < num; i++, p += 4) {
    data[i] = (*p << 24) + (*(p+1) << 16) + (*(p+2) << 8) + (*(p+3));
  }
}

int check_sizes(const int *valid, int x, int y, int z, const char *var_name)
{
  static char ename[] = "check_sizes error:";
  static char dname[] = "check_sizes debug:";
  int i;

  for (i = 0; i < 3; i++) {
    if (valid[i] < 1) {
      fprintf(stderr, "%s Invalid value for %s: %d\n", ename, var_name, valid[i]);
      return ERROR_RETURN;
    }
  }
  if ((x > valid[0]) || (y > valid[1]) || (z > valid[2])) {
    fprintf(stderr, "%s Array sizes incorrect, data is : %dx%dx%d\n", ename, x, y, z);
    fprintf(stderr, "Provided array size is: ");
    for (i = 0; i < 3; i++) {
      fprintf(stderr, "%d", valid[i]);
      if (i < 2) {
        fprintf(stderr, "x");
      } else {
        fprintf(stderr, "\n");
      }
    }
    return ERROR_RETURN;
  }
  return 0;
}

/*
 *  utilities
 */

/* Global data structures that get set each time a file is opened */

static int fd = -1, data_format = -1;
static int ascii_header_size;
static unsigned char elems[MAX_PARAMETERS];
static struct visst_header vhead;  /* defined in vintrt.h */
static int *mcidas_navarr;
static int *mcidas_head;
static off_t fpos = -1;  /* currently not consistantly used nor fulled implemnted */
static int bytes2skip[MAX_PARAMETERS];
static int ints2read[MAX_PARAMETERS];

/* Internal prototypes */

static int get_data_format();
static struct visst_header *parse_vintrt_head();
static char *num2var_name(int);
static int var_name2num(const char *);
static int mystrncasecmp(const char *, const char *);

/************************************************************************/
/*
 *  open the file whose name is pointed to by fn and return the file descriptor.
 *  fill out the global data structures. return nparams, nlines, and npixels to caller.
 *  calls parse_vintrt_head() (an internal routine).
 *
 *  return -1 on failure, 0 on success.
 */
int vintrt_open_(char *fn, int *nparams, int *nlines, int *npixels)
{
  return (vintrt_open(fn, nparams, nlines, npixels));
}
int vintrt_open(char *fn, int *nparams, int *nlines, int *npixels)
{
  const char ename[] = "vintrt_open error:";
  const char dname[] = "vintrt_open debug:";
  int b2s, i;

#if DEBUG
  printf("%s file name: %s\n", dname, fn);
#endif
  if (fd != -1) {
    fprintf(stderr, "%s Opening file: %s, file descriptor already in use.\n", ename, fn);
    return -1;
  }
  if ((fd = open(fn, O_RDONLY, 0)) == -1) {
    fprintf(stderr, "%s Opening file: %s\n", ename, fn);
    return -1;
  }
/*
 *  set variable fpos
 */
  if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
    fprintf(stderr, "%s lseek position current.\n", ename);
    return -1;
  }
  if (parse_vintrt_head() == NULL) {
    fprintf(stderr, "%s Reading/parsing ascii header.\n", ename);
    return -1;
  }
  *nparams = vhead.nparams;
  *nlines = vhead.nlines;
  *npixels = vhead.npixels;
  if ((fpos = lseek(fd, ascii_header_size, SEEK_SET)) == -1) {
    fprintf(stderr, "%s lseek position %d.\n", ename, ascii_header_size);
    return -1;
  }
  if (read(fd, elems, MAX_PARAMETERS) != MAX_PARAMETERS) {
    fprintf(stderr, "%s Reading parameter (elems) info.\n", ename);
    return -1;
  }
  free(mcidas_navarr);
  if ((mcidas_navarr = malloc(vhead.mcidas_nsize * sizeof(int))) == NULL) {
    fprintf(stderr, "%s Mallocing mcidas navarr data.\n", ename);
    return -1;
  }
  if (read(fd, mcidas_navarr, vhead.mcidas_nsize * sizeof(int)) !=
      vhead.mcidas_nsize * sizeof(int)) {
    fprintf(stderr, "%s Reading mcidas navarr data.\n", ename);
    return -1;
  }
  free(mcidas_head);
  if ((mcidas_head = malloc(vhead.mcidas_hsize * sizeof(int))) == NULL) {
    fprintf(stderr, "%s Mallocing mcidas head data.\n", ename);
    return -1;
  }
  if (read(fd, mcidas_head, vhead.mcidas_hsize * sizeof(int)) !=
      vhead.mcidas_hsize * sizeof(int)) {
    fprintf(stderr, "%s Reading mcidas head data.\n", ename);
    return -1;
  }
  change_endian((unsigned int *) mcidas_navarr, vhead.mcidas_nsize);
  change_endian((unsigned int *) mcidas_head, vhead.mcidas_hsize);
#if DEBUG
  printf("%s file descriptor: %d\n", dname, fd);
#endif
/*
 *  figure out how many bytes reside in each parameter of the data. and how many
 *  bytes to skip for each one. store it in global arrays.
 */
  bytes2skip[0] = ascii_header_size + MAX_PARAMETERS + (vhead.mcidas_nsize + vhead.mcidas_hsize) *
               sizeof(int);
  for (i = 0; i < vhead.nparams; i++) {
    ints2read[i] = (full_list[elems[i]].bits * vhead.nlines * vhead.npixels) / BITS_PER_INT;
    if ((full_list[elems[i]].bits * vhead.nlines * vhead.npixels) % BITS_PER_INT != 0) {
      ints2read[i]++;
    }
    if (i > 0) {
      bytes2skip[i] = bytes2skip[i - 1] + (ints2read[i - 1] * sizeof(int));
    }
  }
  return 0;
}

/************************************************************************/
/*
 *  read the vintrt header from open file pointed to by file descriptor fd.
 *  copy it into the area pointed to by header (provided by the caller).
 *
 *  return -1 on failure, 0 on success.
 */
int vintrt_get_header_(char *header)
{
  return (vintrt_get_header(header));
}
int vintrt_get_header(char *header)
{
  const char ename[] = "vintrt_print_head error:";
  char str[MAX_ASCII_SIZE];
  int status;

  if (lseek(fd, 0, SEEK_SET) == -1) {
    fprintf(stderr, "%s lseek position BOF.\n", ename);
    return -1;
  }
  if ((status = read(fd, str, ascii_header_size)) != ascii_header_size) {
    fprintf(stderr, "%s Reading ascii header from file. number: %d\n", ename, status);
    return -1;
  }
  if (lseek(fd, fpos, SEEK_SET) == -1) {
    fprintf(stderr, "%s lseek position fpos.\n", ename);
    return -1;
  }
  strncpy(header, str, MAX_ASCII_SIZE);
  return 0;
}

/************************************************************************/
/*
 *  find and return to the caller the parameters for var_name.
 *  space for those parameters must be provided by the caller.
 *
 *  return -1 upon failure, 0 for success.
 */
int vintrt_var_attributes_(const char *var_name, char *units, 
                          float range[], float fill[], int *valid)
{
  return (vintrt_var_attributes(var_name, units, range, fill, valid));
}
int vintrt_var_attributes(const char *var_name, char *units, 
                          float range[], float fill[], int *valid)
{
  int vnum;
  char ename[] = "vintrt_var_attributes error:";
  char dname[] = "vintrt_var_attributes debug:";

  if (fd == -1) {
    fprintf(stderr, "%s No file open\n", ename);
    return -1;
  }
  if ((vnum = var_name2num(var_name)) == -1) {
    fprintf(stderr, "%s Unknown variable name: %s\n", ename, var_name);
    return -1;
  }
  strncpy(units, full_list[elems[vnum]].unit_name, MAX_UNIT_NAME_SIZE);
  range[0] = full_list[elems[vnum]].min;
  range[1] = full_list[elems[vnum]].max;
  fill[0] = -7.;
  fill[1] = -8.;
  fill[2] = -9.;
  *valid = 3;
  return 0;
}

/************************************************************************/
/*
 *  return to the caller the data for variable name var_name. the caller is
 *  responsible for telling this routine the size of the array (in variable
 *  asize) provided in [nlines, npixels] format and making that array
 *  available to the routine.
 *
 *  return -1 upon failure, 0 for success.
 */
int vintrt_read_var_(const char *var_name, const int asize[], float data[])
{
  return (vintrt_read_var(var_name, asize, data));
}
int vintrt_read_var(const char *var_name, const int asize[], float data[])
{
  char ename[] = "vintrt_read_var error:";
  char dname[] = "vintrt_read_var debug:";
  int vnum, i, j;
  int *values;
  int pos, index, offset, extra, neg, bits, bytes2read;
  unsigned uvalue;
  float *p, scale;

  if (fd == -1) {
    fprintf(stderr, "%s No file open.\n", ename);
    return -1;
  }
  if ((vnum = var_name2num(var_name)) == -1) {
    fprintf(stderr, "%s Unknown variable name: %s\n", ename, var_name);
    return -1;
  }
  if ((vhead.npixels > asize[1]) || (vhead.nlines > asize[0])) {
    fprintf(stderr, "%s Array sizes incorrect, data is : %dx%d\n", ename, vhead.nlines, vhead.npixels);
    fprintf(stderr, "Provided array size is: %dx%d\n", asize[0], asize[1]);
    return -1;
  }
/*
 *  now we know how much data to read.
 */
  if ((values = calloc(vhead.nlines * vhead.npixels, sizeof(int))) == NULL) {
    fprintf(stderr, "%s callocing values, size: %d\n", ename, vhead.nlines * vhead.npixels);
    return -1;
  }
#if DEBUG
  printf("%s  vnum: %d  bytes2skip: %d  ints2read: %d  nbits: %d\n", dname, vnum,
         bytes2skip[vnum], ints2read[vnum], full_list[elems[vnum]].bits);
#endif
  if (lseek(fd, bytes2skip[vnum], SEEK_SET) == -1) {
    free(values);
    fprintf(stderr, "%s lseek position bytes2skip: %d\n", ename, bytes2skip[vnum]);
    return -1;
  }
  bytes2read = ints2read[vnum] * sizeof(int);
  if (read(fd, values, bytes2read) != bytes2read) {
    free(values);
    fprintf(stderr, "%s  parameter: %d  read values: %d\n", ename, vnum, bytes2read);
    return -1;
  }
  change_endian((unsigned int *) values, ints2read[vnum]);
  bits = full_list[elems[vnum]].bits;
  scale = full_list[elems[vnum]].scale;
/*
 *  insert the data in the array pixel by pixel, leading to a layout as follows:
 *  memory location
 *  1                 2                 3    .  .  .  .     n
 *  nline=1,npixel=1  nline=1,npixel=2  nline=1,npixel=3 .. nline=nline,npixel=npixel
 */
  for (i = 0, pos = 0; i < vhead.nlines; i++) {
    p = data + i * asize[1];
    for (j = 0; j < vhead.npixels; j++, p++, pos += bits) {
      index = pos / BITS_PER_INT;
      offset = pos % BITS_PER_INT;
/*
 *  how many extra bits (outside of this BITS_PER_INT bit int) are there?
 */
      extra = offset + bits - BITS_PER_INT;
/*
 *  shift and mask the current int array value.
 *  right shift on a signed int brings the sign bit along for the ride, so do the
 *  shift on the unsiged value.
 */
      uvalue = values[index];
      uvalue = (uvalue >> offset) & MASK[bits];
/*
 *  OK, do we need to go into the next array element to get the rest of the number?
 */
      if (extra > 0) {
        uvalue += ((values[index + 1] & MASK[extra]) << (BITS_PER_INT - offset));
      }
/*
 *  see if the negative flag is set, if so, remove it and negate the value.
 */
      if ((uvalue >> (bits - 1)) == 1) {
        uvalue &= MASK[bits - 1];
        neg = -1.;
      } else {
        neg = 1.;
      }
      *p = neg * (float) uvalue / scale;
    }  /* end of j loop */
#if DEBUG
    printf("%s unpack: i: %d  index: %u\n", dname, i, index);
#endif
  }  /* end of i loop */
  free(values);
  if (lseek(fd, fpos, SEEK_SET) == -1) {
    return -1;
  }
  return 0;
}

/************************************************************************/
/*
 *  front-end for num2var_name which will take the integer provided by the caller
 *  and copy that variable name into space provided by the caller.
 *
 *  return -1 upon failure, 0 for success.
 */
int vintrt_get_vname_(int *num, char *var_name)
{
  return (vintrt_get_vname(num, var_name));
}
int vintrt_get_vname(int *num, char *var_name)
{
  char ename[] = "vintrt_get_vnames error:";
  char *c;

  if (fd == -1) {
    fprintf(stderr, "%s No file open.\n", ename);
    return -1;
  }
  if ((c = num2var_name(*num)) == NULL) {
    fprintf(stderr, "%s Variable number %d not found.\n", ename, *num);
    return -1;
  }
  strncpy(var_name, c, MAX_VAR_NAME_SIZE);
  return 0;
}

/************************************************************************/
/*
 *  return to the caller the date and time contained in the header.
 *  copy into space provided by the caller.
 *  assumes that the header has been read into the data structure.
 *  date contains: dd/mm/yyyy
 *  time contains: hh:mm:ss
 *
 *  return -1 upon failure, 0 for success.
 */
int vintrt_get_datetime_(int *date, int *time)
{
  return (vintrt_get_datetime(date, time));
}
int vintrt_get_datetime(int *date, int *time)
{
  char ename[] = "vintrt_get_datetime error:";

  if (fd == -1) {
    fprintf(stderr, "%s No file open.\n", ename);
    return -1;
  }
  date[0] = vhead.day;
  date[1] = vhead.month;
  date[2] = vhead.year;
  time[0] = vhead.hour;
  time[1] = vhead.minute;
  time[2] = vhead.second;
  return 0;
}

/************************************************************************/
/*
 *  return to the caller the sizes of the mcidas nav and header block.
 *  copy into space provided by the caller.
 *  assumes that the header has been read into the data structure.
 *
 *  return -1 upon failure, 0 for success.
 */
int vintrt_get_mcidas_size_(int *nav_size, int *head_size)
{
  return (vintrt_get_mcidas_size(nav_size, head_size));
}
int vintrt_get_mcidas_size(int *nav_size, int *head_size)
{
  char ename[] = "vintrt_get_mcidas_size error:";

  if (fd == -1) {
    fprintf(stderr, "%s No file open.\n", ename);
    return -1;
  }
  *nav_size = vhead.mcidas_nsize;
  *head_size = vhead.mcidas_hsize;
  return 0;
}

/************************************************************************/
/*
 *  close the file pointed to by the file descriptor. reset global fd to -1.
 *
 *  return -1 upon failure, 0 for success.
 */
int vintrt_close_()
{
  return (vintrt_close());
}
int vintrt_close()
{

  if (close(fd) == 0) {
    fd = -1;
    return 0;
  } else {
    return -1;
  }
}

/************************************************************************/
/*
 *  just printout the VERSION and return.
 *
 *  void return.
 */
void vintrt_utils_version_()
{
  vintrt_utils_version();
  return;
}
void vintrt_utils_version()
{

  printf("%s\n", VERSION);
  return;
}

/*
 *  do not call any of the following code from outside of this module. any of
 *  these structures can change at any time!
 */
/************************************************************************/
/*
 *  given a file descriptor, return the information from the packed pixel files
 *  header, such as number of parameters, lines, and pixels, comment, etc.
 *
 *  Only for use within the utils code since it can only be easily integrated
 *  into C calling routines.
 *  the structure it returns is flexible and could be changed at any time.
 *
 *  return NULL upon failure, pointer to global vhead structure for success.
 *
 *  Internal use only!
 */
static struct visst_header *parse_vintrt_head()
{
  const char ename[] = "parse_vintrt_head error:";
  const char dname[] = "parse_vintrt_head debug:";
  int status;
  char str[MAX_ASCII_SIZE + 1], form[DATA_FORMAT_SIZE + 1];

  if (fd == -1) {
    fprintf(stderr, "%s No file open.\n", ename);
    return NULL;
  }
  form[DATA_FORMAT_SIZE] = '\0';
  if (lseek(fd, 0, SEEK_SET) == -1) {
    fprintf(stderr, "%s lseek.\n", ename);
    return NULL;
  }
  if ((status = read(fd, form, DATA_FORMAT_SIZE)) != DATA_FORMAT_SIZE) {
    fprintf(stderr, "%s Reading data format from file. number: %d\n", ename, status);
    return NULL;
  }
  switch (atoi(form)) {
    case 0:
    case 1:
      ascii_header_size = MAX_ASCII_SIZE;
      break;
    default:
      fprintf(stderr, "%s  Unknown data format: %d\n", ename, form);
      return NULL;
      break;
  }
  if (lseek(fd, 0, SEEK_SET) == -1) {
    fprintf(stderr, "%s lseek.\n", ename);
    return NULL;
  }
  str[ascii_header_size] = '\0';
  if ((status = read(fd, str, ascii_header_size)) != ascii_header_size) {
    fprintf(stderr, "%s Reading char_info from file. number: %d\n", ename, status);
    return NULL;
  }
#if DEBUG
  printf("%s header string: %s\n", dname, str);
#endif
/*
 *  pick off the format number
 */
  vhead.data_format = atoi(strtok(str, ":/ \t\0"));
/*
 *  pick off the date string elements, month, day, year.
 */
  vhead.month = atoi(strtok(NULL, ":/ \t\0"));
  vhead.day = atoi(strtok(NULL, ":/ \t\0"));
  vhead.year = atoi(strtok(NULL, ":/ \t\0"));
#if DEBUG
  printf("%s month-day-year: %d-%d-%d\n", dname, vhead.month, vhead.day, vhead.year);
#endif
/*
 *  pick off the time string elements, hour, minute, second.
 */
  vhead.hour = atoi(strtok(NULL, ":/ \t\0"));
  vhead.minute = atoi(strtok(NULL, ":/ \t\0"));
  vhead.second = atoi(strtok(NULL, ":/ \t\0"));
#if DEBUG
  printf("%s hour:minute:second: %d:%d:%d\n", dname, vhead.hour, vhead.minute, vhead.second);
#endif
/*
 *  pick off the number of parameters, lines, and pixels.
 */
  vhead.nparams = atoi(strtok(NULL, ":/ \t\0"));
  vhead.nlines = atoi(strtok(NULL, ":/ \t\0"));
  vhead.npixels = atoi(strtok(NULL, ":/ \t\0"));
#if DEBUG
  printf("%s nparams nlines npixels: %d %d %d\n", dname, vhead.nparams, vhead.nlines, vhead.npixels);
#endif
/*
 *  pick off the number of mcidas navigation and header block integers.
 */
  vhead.mcidas_nsize = atoi(strtok(NULL, ":/ \t\0"));
  vhead.mcidas_hsize = atoi(strtok(NULL, ":/ \t\0"));
#if DEBUG
  printf("%s mcidas head size, nav size: %d %d\n", dname, vhead.mcidas_hsize, vhead.mcidas_nsize);
#endif
/*
 *  pick off the pixel level file creation date string elements, month, day, year.
 */
  vhead.cmonth = atoi(strtok(NULL, ":/ \t\0"));
  vhead.cday = atoi(strtok(NULL, ":/ \t\0"));
  vhead.cyear = atoi(strtok(NULL, ":/ \t\0"));
#if DEBUG
  printf("%s data collection month-day-year: %d-%d-%d\n", dname, vhead.cmonth, vhead.cday, vhead.cyear);
#endif
/*
 *  pick off the pixel level file creation time string elements, hour, minute, second.
 */
  vhead.chour = atoi(strtok(NULL, ":/ \t\0"));
  vhead.cminute = atoi(strtok(NULL, ":/ \t\0"));
  vhead.csecond = atoi(strtok(NULL, ":/ \t\0"));
#if DEBUG
  printf("%s data collection hour:minute:second: %d:%d:%d\n", dname, vhead.chour, vhead.cminute, vhead.csecond);
#endif
  vhead.ul_lat = atof(strtok(NULL, ":/ \t\0"));
  vhead.ul_lon = atof(strtok(NULL, ":/ \t\0"));
  vhead.lr_lat = atof(strtok(NULL, ":/ \t\0"));
  vhead.lr_lon = atof(strtok(NULL, ":/ \t\0"));

  vhead.x_res = atof(strtok(NULL, ":/ \t\0"));
  vhead.y_res = atof(strtok(NULL, ":/ \t\0"));

  strncpy(vhead.comment, &str[DEDICATED_ASCII_SIZE], COMMENT_SIZE);
#if DEBUG
  printf("%s comment: %s\n", dname, vhead.comment);
  printf("%s file descriptor: %d\n", dname, fd);
  printf("%s &vhead: %p\n", dname, &vhead);
#endif
  if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) {
    fprintf(stderr, "%s  lseek\n", ename);
    return NULL;
  }
  return &vhead;
}

/************************************************************************/
/*
 *  just return the data format version as an integer
 *  no side effects as the file stream pointer is reset to initial location.
 *
 *  return -1 upon failure, 0 for success.
 *
 *  Internal use only!
 */
static int get_data_format()
{
  const char ename[] = "get_data_format error:";
  const char dname[] = "get_data_format debug:";
  char data_format_str[DATA_FORMAT_SIZE + 1];

/*
 *  to make this routine as generic as possible, we'll read just enough data to determine
 *  the format of the data contained within this data file.
 *  we may come into this routine at any time, so now make sure that the characters read
 *  are the first 4 and reset the file pointer to the previous position.
 */
  if (lseek(fd, 0, SEEK_SET) == -1) {
    return -1;
  }
  if (read(fd, data_format_str, DATA_FORMAT_SIZE) != DATA_FORMAT_SIZE) {
    return -1;
  }
  if (lseek(fd, fpos, SEEK_SET) == -1) {
    return -1;
  }
#if DEBUG
  printf("%s data_format_string: %s\n", dname, data_format_str);
#endif
  return atoi(data_format_str);
}

/************************************************************************/
/*
 *  given a file descriptor and parameter number, locate the variable name and use
 *  the provided cahracter pointer to return the variable name.
 *  return pointer to var_name if successful. overwritten after each call.
 *  return NULL if the parameter number is not in range or file cannot be read from.
 *
 *  Internal use only!
 */
static char *num2var_name(int num)
{
  static char var_name[MAX_VAR_NAME_SIZE + 1];

  if (fd == -1) {
    return NULL;
  }
  if ((num < 0) || (num >= vhead.nparams)) {
    return NULL;
  }
  strncpy(var_name, full_list[elems[num]].var_name, MAX_VAR_NAME_SIZE + 1);
  return var_name;
}

/************************************************************************/
/*
 *  given a file descriptor and variable name, return the parameter number
 *  of this variable name. -1 if file problem or not a valid variable name.
 *
 *  Internal use only!
 */
static int var_name2num(const char *var_name)
{
  int i;

  if (fd == -1) {
    return -1;
  }
  for (i = 0; i < vhead.nparams; i++) {
    if (mystrncasecmp(var_name, full_list[elems[i]].var_name) == 0) {
      return i;
    }
  }
  return -1;
}

/************************************************************************/
/*
 *  given a file descriptor and variable name, return the attributes of this
 *  variable such as range, dimensions, and fill value(s).
 *  return NULL if variable not found.
 *
 *  return -1 upon failure, 0 for success.
 *
 *  Internal use only!
 */
struct var_attributes *vintrt_var_attributes_internal(const char *var_name)
{
  static struct var_attributes attrib;
  int i, max_vals;

  if ((attrib.data_format = get_data_format()) == -1) {
    return NULL;
  }
  attrib.fill[0] = -7;
  attrib.fill[1] = -8;
  attrib.fill[2] = -9;
  attrib.num_fill = 3;
  for (i = 0; i < vhead.nparams; i++) {
    if (mystrncasecmp(var_name, full_list[elems[i]].var_name) == 0) {
      strncpy(attrib.var_name, full_list[elems[i]].var_name, MAX_VAR_NAME_SIZE);
      strncpy(attrib.unit_name, full_list[elems[i]].unit_name, MAX_UNIT_NAME_SIZE);
      attrib.min = full_list[elems[i]].min;
      attrib.max = full_list[elems[i]].max;
      attrib.var_num = i;
      return &attrib;
    }
  }
  return NULL;
}

/************************************************************************/
/*
 *  compare two string, case insensitive
 *
 *  return 0 if the same, else -1.
 *
 *  Internal use only!
 */
static int mystrncasecmp(const char *a, const char *b)
{

  while (*a && *b) {
    if (tolower(*a) != tolower(*b)) {
      return -1;
    }
    a++;
    b++;
  }
  if ((*a == '\0') && (*b == '\0')) {
    return 0;
  }
  return -1;
}

