#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "PBXroutines.h"
#include "hash.h"

#define hashsize(n) ((ub4)1<<(n))
#define hashmask(n) (hashsize(n)-1)

#define XFIND_ERROR -1
#define FSEEK_ERROR -2
#define FREAD_ERROR -2
#define INDEX_TOO_BIG -1
#define BUFF_TOO_SMALL -3

/*
--------------------------------------------------------------------
mix -- mix 3 32-bit values reversibly.
For every delta with one or two bits set, and the deltas of all three
  high bits or all three low bits, whether the original value of a,b,c
  is almost all zero or is uniformly distributed,
* If mix() is run forward or backward, at least 32 bits in a,b,c
  have at least 1/4 probability of changing.
* If mix() is run forward, every bit of c will change between 1/3 and
  2/3 of the time.  (Well, 22/100 and 78/100 for some 2-bit deltas.)
mix() was built out of 36 single-cycle latency instructions in a 
  structure that could supported 2x parallelism, like so:
      a -= b; 
      a -= c; x = (c>>13);
      b -= c; a ^= x;
      b -= a; x = (a<<8);
      c -= a; b ^= x;
      c -= b; x = (b>>13);
      ...
  Unfortunately, superscalar Pentiums and Sparcs can't take advantage 
  of that parallelism.  They've also turned some of those single-cycle
  latency instructions into multi-cycle latency instructions.  Still,
  this is the fastest good hash I could find.  There were about 2^^68
  to choose from.  I only looked at a billion or so.
--------------------------------------------------------------------
*/
#define mix(a,b,c) \
{ \
  a -= b; a -= c; a ^= (c>>13); \
  b -= c; b -= a; b ^= (a<<8); \
  c -= a; c -= b; c ^= (b>>13); \
  a -= b; a -= c; a ^= (c>>12);  \
  b -= c; b -= a; b ^= (a<<16); \
  c -= a; c -= b; c ^= (b>>5); \
  a -= b; a -= c; a ^= (c>>3);  \
  b -= c; b -= a; b ^= (a<<10); \
  c -= a; c -= b; c ^= (b>>15); \
}

/*
--------------------------------------------------------------------
hash() -- hash a variable-length key into a 32-bit value
  k       : the key (the unaligned variable-length array of bytes)
  len     : the length of the key, counting by bytes
  initval : can be any 4-byte value
Returns a 32-bit value.  Every bit of the key affects every bit of
the return value.  Every 1-bit and 2-bit delta achieves avalanche.
About 6*len+35 instructions.

The best hash table sizes are powers of 2.  There is no need to do
mod a prime (mod is sooo slow!).  If you need less than 32 bits,
use a bitmask.  For example, if you need only 10 bits, do
  h = (h & hashmask(10));
In which case, the hash table should have hashsize(10) elements.

If you are hashing n strings (ub1 **)k, do it like this:
  for (i=0, h=0; i<n; ++i) h = hash( k[i], len[i], h);

By Bob Jenkins, 1996.  bob_jenkins@burtleburtle.net.  You may use this
code any way you wish, private, educational, or commercial.  It's free.

See http://burtleburtle.net/bob/hash/evahash.html
Use for hash table lookup, or anything where one collision in 2^^32 is
acceptable.  Do NOT use for cryptographic purposes.
--------------------------------------------------------------------
*/

ub4 hash( k, length, initval)
register ub1* k;        /* the key */
register ub4  length;   /* the length of the key */
register ub4  initval;  /* the previous hash, or an arbitrary value */
{
   register ub4 a,b,c,len;

   /* Set up the internal state */
   len = length;
   a = b = 0x9e3779b9;  /* the golden ratio; an arbitrary value */
   c = initval;         /* the previous hash value */

   /*---------------------------------------- handle most of the key */
   while (len >= 12)
   {
      a += (k[0] +((ub4)k[1]<<8) +((ub4)k[2]<<16) +((ub4)k[3]<<24));
      b += (k[4] +((ub4)k[5]<<8) +((ub4)k[6]<<16) +((ub4)k[7]<<24));
      c += (k[8] +((ub4)k[9]<<8) +((ub4)k[10]<<16)+((ub4)k[11]<<24));
      mix(a,b,c);
      k += 12; len -= 12;
   }

   /*------------------------------------- handle the last 11 bytes */
   c += length;
   switch(len)              /* all the case statements fall through */
   {
   case 11: c+=((ub4)k[10]<<24);
   case 10: c+=((ub4)k[9]<<16);
   case 9 : c+=((ub4)k[8]<<8);
      /* the first byte of c is reserved for the length */
   case 8 : b+=((ub4)k[7]<<24);
   case 7 : b+=((ub4)k[6]<<16);
   case 6 : b+=((ub4)k[5]<<8);
   case 5 : b+=k[4];
   case 4 : a+=((ub4)k[3]<<24);
   case 3 : a+=((ub4)k[2]<<16);
   case 2 : a+=((ub4)k[1]<<8);
   case 1 : a+=k[0];
     /* case 0: nothing left to add */
   }
   mix(a,b,c);
   /*-------------------------------------------- report the result */
   return c;
}

long date_to_julian(long ddate);
long julian_to_date(long jdate);

integer pbxinitInput(_fcd filename, fortint filename_len);
integer pbxinitOutput(_fcd filename, fortint filename_len);
void copyName(_fcd* , _fcd , fortint );

fortint readgrib(FILE* file, unsigned char* buffer, fortint* prod_len);

void pbxindx(fortint);

integer gribHashNumber(unsigned char*,integer*);
void addHashNumberToFile(gribfile*,unsigned char*,integer,integer,integer);

static gribfile* latestFile(collection);
static gribfile* currentFile(collection, integer);

/*
// 
*/
#define DEBUGOFF 1
#define DEBUG1 (debugSet > DEBUGOFF )
#define DEBUG2 (debugSet > (DEBUGOFF + 1) )
static char* debugLevel;
static int debugSet = 0;

#define ONEBYTELONG(a)   (long) ( *(a) )
#define TWOBYTELONG(a)   (long) ( (*(a))<<8 | (*((a)+1))<<0 )
#define THREEBYTELONG(a) (long) (TWOBYTELONG((a))<<8 | (*((a)+2))<<0 )
#define FOURBYTELONG(a)  (long) (THREEBYTELONG((a))<<8 | (*((a)+3))<<0 )

#define MISSING -1
#define NOTGIVEN(x) ((x) == MISSING )
#define MATCH(a,b)  (NOTGIVEN((a)) || ((a) == (b)))

#define HEADLEN 10000

integer exists(_fcd , fortint );
integer addRead(_fcd , fortint );
integer addWrite(_fcd , fortint );
void removeFile(_fcd , fortint );

collection openFiles = {exists,addRead,addWrite,removeFile,-1,-1,NULL};

void pbxindx(fortint thisFile) {
/*
// Fills in details of current file
*/
unsigned char header[HEADLEN];
fortint status;
fortint headerlen;
OFF_T space;
integer number, headerLength, number_of_fields = 0;
gribfile* file = currentFile(openFiles,thisFile);

/*
// Loop through products in the file
// Accept product if OK or if only problem is 'buffer too small' or on EOF
*/
  file->offset[0] = 0;
  do {
    headerlen = HEADLEN;
    status = readgrib(file->fp,header,&headerlen);
    if( (status!=0) && (status!=-3) && (status!=-1) ) exit(1);
    if( status == -1 ) break;
/*
// Note the product byte offset in the file
*/
    file->length[number_of_fields] = (OFF_T) headerlen;
#ifdef FOPEN64
    file->offset[number_of_fields] =
      ftello64(file->fp) - file->length[number_of_fields];
#else
    file->offset[number_of_fields] =
      (OFF_T) ftell(file->fp) - file->length[number_of_fields];
#endif

/*
// If it's a GRIB edition 1 product, pick up more information
// (parameter,level,..)
*/
    if( strncmp((char*)header,"GRIB", 4)==0 ) {
/*
//    Eliminate GRIB editions 0 and -1
*/
      if( (header[ 7] != '\1') ) break;
      if( (header[19] == '\0') && (header[20] == '\0') &&
          (header[21] == '\0') && (header[22] == '\0') &&
          (header[23] == '\0') ) break;

      number = gribHashNumber(header,&headerLength);
      addHashNumberToFile(file,header,headerLength,number,number_of_fields);

#define REALLOC(a) (a)  =  realloc((a) , (size_t) space)

      number_of_fields++;
      if( number_of_fields == file->max ) {
        integer newSize = (file->max)*2;
        file->max = newSize;

        space = (OFF_T) (sizeof(OFF_T)*newSize);
        REALLOC(file->offset);

        space = (OFF_T) (sizeof(integer)*newSize);
        REALLOC(file->length);
/*
JDC      REALLOC(file->hashNumber);
*/
      }
    }
        
  } while( (!feof(file->fp)) &&
           (number_of_fields < file->max)
         );

  file->count = number_of_fields;

  return;
}

fortint soffset012_(unsigned char*,fortint*,fortint*,fortint*);

integer gribHashNumber( unsigned char* header, integer* headerLength)
/*
// Returns hashNumber for a GRIB product.
*/
{
fortint status, is0, is1, is2;

  status = soffset012_(header, &is0, &is1, &is2);

  *headerLength = (is1 - is0);
  *headerLength += THREEBYTELONG(header+is1);
  if( is2 != 0 ) *headerLength += THREEBYTELONG(header+is2);

  return hash((header+is0),*headerLength,0);
}

/*
// ****************************************************************
*/
fortint pbxtotl_(_fcd filename, fortint filename_len) {
/*
// Returns number of GRIB products in the file.
*/
integer thisFile;
gribfile* file;

  thisFile = pbxinitInput(filename,filename_len);
  file = currentFile(openFiles, thisFile);

  if( DEBUG1 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBXTOTL: Number of GRIBs in file %s = %d\n", pfile, file->count);
    free(pfile);
  }
  return ( file->count );
}

fortint pbxtotl(_fcd filename, fortint filename_len) {
  return pbxtotl_(filename,filename_len);
}

fortint pbxget_(_fcd filename, fortint* buffer, fortint* bufflen,
               fortint* n, fortint filename_len) {
/*
// Gets the nth GRIB product.
*/
integer index = (*n)-1;
integer length, status;
OFF_T offset;
integer thisFile;
FILE* fp;
gribfile* file;

  if( DEBUG1 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBXGET: getting GRIB number %d in file %s\n",
    (index+1), pfile);
    free(pfile);
  }

  if( index >= 0 ) {
    thisFile = pbxinitInput(filename,filename_len);
    file = currentFile(openFiles, thisFile);
    if( index < file->count ) {

      length = file->length[index];

      if( DEBUG1 ) printf("PBXGET: length of GRIB number %d = %d\n",
                          (index+1), length);

      if( *bufflen < length ) {
        fprintf(stderr,
          "PBXGET: user buffer too small, %d bytes required\n", length);
        return BUFF_TOO_SMALL;
      }

      offset = file->offset[index];

      if( DEBUG1 ) printf("PBXGET: offset of GRIB number %d = %d\n",
                          (index+1), offset);

      fp = file->fp;
#ifdef FOPEN64
      if( fseeko64( fp, offset, 0) ) {
#else
      if( fseek( fp, offset, 0) ) {
#endif
        perror("PBXGET: error in fseek");
        return FSEEK_ERROR;
      }

      status = fread(buffer, 1, length, fp);
      if( status != length ) {
        fprintf(stderr,"PBXGET: error in fread\n");
        return FREAD_ERROR;
      }
      
      return length;
    }
  }

  return INDEX_TOO_BIG;

}

fortint pbxget(_fcd filename, fortint* buffer, fortint* bufflen,
               fortint* n, fortint filename_len) {
  return pbxget_(filename,buffer,bufflen,n,filename_len);
}

/*
// ****************************************************************
*/
integer pbxinitInput(_fcd filename, fortint filename_len) {
/*
// Initialise indices for current file if necessary.
*/
integer thisFile;

  if( DEBUG2 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBX_pbxinitInput: checking if file %s already open\n", pfile);
    free(pfile);
  }

  thisFile = openFiles.exists(filename,filename_len);

  if( thisFile == -1 ) {
    if( DEBUG2 ) printf("PBX_pbxinitInput: file not yet open\n");
    return ( openFiles.addRead(filename, filename_len) );
  }

  if( DEBUG2 ) printf("PBX_pbxinitInput: file has open slot %d\n", thisFile);
  return thisFile;

}

integer pbxinitOutput(_fcd filename, fortint filename_len) {
/*
// Initialise output file if necessary.
*/
integer thisFile;

  if( DEBUG2 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBX_pbxinitOutput: checking if file %s already open\n", pfile);
    free(pfile);
  }

  thisFile = openFiles.exists(filename,filename_len);

  if( thisFile == -1 ) {
    if( DEBUG2 ) printf("PBX_pbxinitOutput: file not yet open\n");
    return ( openFiles.addWrite(filename, filename_len) );
  }

  if( DEBUG2 ) printf("PBX_pbxinitOutput: file has open slot %d\n", thisFile);
  return thisFile;

}

integer exists(_fcd filename , fortint filename_len) {
integer n;
_fcd pfile;
gribfile* file;


/*
// See if DEBUG switched on.
*/
    if( ! debugSet ) {
      debugLevel = getenv("PBX_DEBUG");
      if( debugLevel == NULL )
        debugSet = DEBUGOFF;              /* off */
      else {
        int loop;
        for( loop = 0; loop < strlen(debugLevel) ; loop++ ) {
          if( ! isdigit(debugLevel[loop]) ) {
            printf("Invalid number string in PBX_DEBUG: %s\n", debugLevel);
            printf("PBX_DEBUG must comprise only digits [0-9].\n");
            debugSet = DEBUGOFF;
          }
        }
        debugSet = DEBUGOFF + atol( debugLevel );
      }
      if( DEBUG2 ) printf("PBX_exists: PBX_DEBUG switched on\n");
    }

  if( openFiles.count > -1 ) {

    copyName(&pfile, filename, filename_len);

    if( DEBUG2 ) printf("PBX_exists: looking for filename = %s\n", pfile);

    for( n = 0; n <= openFiles.count; n++ ) {
      file = currentFile(openFiles, n);
      if( file != 0 ) {
        if( strcmp( file->fname, pfile ) == 0 ) {
          free(pfile);
          if( DEBUG2 ) printf("PBX_exists: file found in slot= %d\n", n);
          return n;
        }
      }
    }

    free(pfile);
  }
  return -1;
}

integer addFile(_fcd filename , fortint filename_len, char readwriteflag) {
_fcd pfile;
char mode[2] = " ";
FILE* in;
gribfile* previousLatest, * latest;

  openFiles.count++;

  copyName(&pfile, filename, filename_len);

  if( DEBUG2 ) printf("PBX_addFile: adding filename = %s\n", pfile);

  mode[0] = readwriteflag;
#ifdef FOPEN64
  in = fopen64(pfile,mode);
#else
  in = fopen(pfile,mode);
#endif
  if( in == NULL ) {
    perror("Error opening file");
    exit(1);
  }

  if( openFiles.count == 0 ) {
    openFiles.files = (gribfile*)malloc(sizeof(gribfile));
    latest = openFiles.files;
  }
  else {
    previousLatest = latestFile(openFiles);
    previousLatest->next = (gribfile*)malloc(sizeof(gribfile));
    latest = previousLatest->next;
  }

  latest->fp = in;
  copyName(&(latest->fname),filename,filename_len);
  latest->readwriteflag = readwriteflag;
  latest->max = MAX_NUMBER_OF_GRIBS;
  latest->count = 0;

#define MALLOC(a) (a) = (void*) malloc(space)

  if( readwriteflag == 'r' ) {
    integer space;

    space = sizeof(OFF_T)*MAX_NUMBER_OF_GRIBS;
    MALLOC(latest->offset);

    space = sizeof(integer)*MAX_NUMBER_OF_GRIBS;
    MALLOC(latest->length);
/*
JDC  MALLOC(latest->hashTable);
*/
     latest->fileHashTable = NULL;
  }

  latest->next = 0;

  if( DEBUG2 ) printf("PBX_addFile: adding file %s in slot = %d\n",
                       pfile, openFiles.count);

  free(pfile);
  return openFiles.count;

}

void removeFile(_fcd filename , fortint filename_len) {
integer thisFile;
int status;

  if( DEBUG2 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBX_removeFile: trying to remove filename = %s\n", pfile);
    free(pfile);
  }
 
  thisFile = openFiles.exists(filename,filename_len);

  if( thisFile != -1 ) {
    integer index;
    gribfile* previous = openFiles.files;
    gribfile* current;

    current = previous;

    for( index = 0; index < thisFile; index++ ) {
      previous = current;
      current = current->next;
    }

    status = fclose(current->fp);
    if( status != 0 ) {
      perror("Error closing file");
      exit(1);
    }
    if( DEBUG2 ) printf("PBX_removeFile: removing file %s from slot %d\n",
                         current->fname, thisFile);
    free(current->fname);

/*
//  Input files have arrays of values
*/
    if( current->readwriteflag == 'r' ) {
      free(current->offset);
      free(current->length);
/*
JDC    free(current->hashNumber);
*/
    }

    if( thisFile == 0 ) 
      openFiles.files = (current->next);
    else
       previous->next = (current->next);
    free(current);
    
    openFiles.count--;
  }
}

integer addRead(_fcd filename , fortint filename_len) {
integer thisFile;

  if( DEBUG2 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBX_addRead: add for reading filename = %s\n", pfile);
    free(pfile);
  }

  thisFile = addFile(filename,filename_len,'r');

  pbxindx( thisFile);

  return thisFile;
}

integer addWrite(_fcd filename , fortint filename_len) {
integer thisFile;

  if( DEBUG2 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("PBX_addWrite: add for writing filename = %s\n", pfile);
    free(pfile);
  }

  thisFile = addFile(filename,filename_len,'w');

  return thisFile;
}

void copyName(_fcd* pfile, _fcd filename, fortint filename_len) {
/*
// Copies FORTRAN filename to C string.
*/
char* blankCharacterPointer;
integer space;

  *pfile = (char*) malloc((size_t) (filename_len+1) );
  memcpy(*pfile, filename, (size_t) filename_len);
  space = (integer) filename_len;
  (*pfile)[space] = '\0';

  blankCharacterPointer = strchr(filename, ' ');
  if( blankCharacterPointer != NULL ) {
    space = (integer) (blankCharacterPointer - filename);
    (*pfile)[space] = '\0';
  }

  return;
}

gribfile* latestFile(collection openFiles) {
integer index;
gribfile* last = openFiles.files;

  for( index = 1; index < openFiles.count; index++ )
    last =  last->next;

  return last;

}

gribfile* currentFile(collection openFiles, integer thisFile) {
integer index;
gribfile* current = openFiles.files;

  for( index = 0; index < thisFile; index++ )
    current = current->next;

  return current;
}

long julian_to_date(long jdate) {
long x,y,d,m,e;
long day,month,year;

    x = 4 * jdate - 6884477;
    y = (x / 146097) * 100;
    e = x % 146097;
    d = e / 4;

    x = 4 * d + 3;
    y = (x / 1461) + y;
    e = x % 1461;
    d = e / 4 + 1;

    x = 5 * d - 3;
    m = x / 153 + 1;
    e = x % 153;
    d = e / 5 + 1;

    if( m < 11 )
    	month = m + 2;
    else
    	month = m - 10;

    day = d;
    year = y + m / 11;

    return year * 10000 + month * 100 + day;
}

long date_to_julian(long ddate) {
long  m1,y1,a,b,c,d,j1;
long month,day,year;

    year = ddate / 10000;
    ddate %= 10000;
    month  = ddate / 100;
    ddate %= 100;
    day = ddate;


    if (year < 100) year = year + 1900;

    if (month > 2)
    {
    	m1 = month - 3;
    	y1 = year;
    }
    else
    {
    	m1 = month + 9;
    	y1 = year - 1;
    }

    a = 146097*(y1/100)/4;
    d = y1 % 100;
    b = 1461*d/4;
    c = (153*m1+2)/5+day+1721119;
    j1 = a+b+c;

    return(j1);
}

fortint soffset012_(unsigned char*,fortint*,fortint*,fortint*);

integer findHashEntry(gribfile*,integer,unsigned char*,integer);

fortint pbxxfind_(fortint* grib1, _fcd filename, fortint filename_len)
{
unsigned char* buffer1 = (unsigned char*) grib1;
static fortint* ibuffer2 = NULL;
static buffer2Length = 0;
fortint status, totalLength, headerLength, numberOfGribs, loop;
fortint is0, is1, is2;
fortint js0, js1, js2;
ub4 hash1, hash2;
integer thisFile;
gribfile* file;
fortint next;

  if( DEBUG1 ) {
    _fcd pfile;
    integer loop;

    copyName(&pfile, filename, filename_len);
    printf("PBXXFIND: searching file %s\n", pfile);
    free(pfile);
  }

  thisFile = pbxinitInput(filename,filename_len);
  file = currentFile(openFiles, thisFile);

  numberOfGribs = pbxtotl_(filename,filename_len);
  if( numberOfGribs < 1 ) {
    if( DEBUG1 ) {
      _fcd pfile;
      copyName(&pfile, filename, filename_len);
      printf("PBXFIND: No GRIBs in file %s\n", pfile);
      free(pfile);
    }
    return XFIND_ERROR;
  }

  status = soffset012_(buffer1, &is0, &is1, &is2);
  if( status ) return XFIND_ERROR;

  totalLength = THREEBYTELONG(buffer1+is0+4);
  headerLength = (is1 - is0);
  headerLength += THREEBYTELONG(buffer1+is1);
  if( is2 != 0 ) headerLength += THREEBYTELONG(buffer1+is2);
  hash1 = hash((buffer1+is0),headerLength,0);

  if( (next = findHashEntry(file,hash1,(buffer1+is0),headerLength) ) >= 0 )
    return (next+1);
  else 
    return XFIND_ERROR;
}

fortint pbxxfind(fortint* grib1, _fcd filename, fortint filename_len) {
  return pbxxfind_(grib1,filename,filename_len);
}

#define GRIB_TOO_SMALL -4

fortint pbxgeth012_(_fcd filename, fortint* buffer, fortint bufflen,
                    fortint n, fortint filename_len) {
/*
// Gets section 0,1 and 2 for the nth GRIB product.
*/
integer index = n - 1;
integer length, status;
OFF_T offset;
integer thisFile;
FILE* fp;
gribfile* file;

  if( DEBUG1 ) {
    _fcd pfile;
    copyName(&pfile, filename, filename_len);
    printf("pbxgeth012: getting GRIB number %d in file %s\n",
    (index+1), pfile);
    free(pfile);
  }

  if( index >= 0 ) {
    thisFile = pbxinitInput(filename,filename_len);
    file = currentFile(openFiles, thisFile);
    if( index < file->count ) {

      length = file->length[index];

      if( DEBUG1 ) printf("pbxgeth012: length of GRIB number %d = %d\n",
                          (index+1), length);

      if( length < bufflen ) return GRIB_TOO_SMALL;

      offset = file->offset[index];

      if( DEBUG1 ) printf("pbxgeth012: offset of GRIB number %d = %d\n",
                          (index+1), offset);

      fp = file->fp;
#ifdef FOPEN64
      if( fseeko64( fp, offset, 0) ) {
#else
      if( fseek( fp, offset, 0) ) {
#endif
        perror("pbxgeth012: error in fseek");
        return FSEEK_ERROR;
      }

      status = fread(buffer, 1, bufflen, fp);
      if( status != bufflen ) {
        fprintf(stderr,"pbxgeth012: error in fread\n");
        return FREAD_ERROR;
      }
      
      return length;
    }
  }

  return INDEX_TOO_BIG;

}

#define ERROR(a,b) {perror(a);return b;}
#define GRIB 0x47524942
#define len3oct(p) ((((long)*(p))<<16) + (((long)*(p+1))<<8) + (long)*(p+2))
#define BIT1 0x80
#define BIT2 0x40
#define BIT3 0x20
#define BIT4 0x10
#define BIT5 0x08
#define BIT6 0x04
#define BIT7 0x02
#define BIT8 0x01

static int grab(unsigned char* , unsigned char* , long ,long ,long* );

fortint soffset012_(
  unsigned char* buffer,
  fortint* is0,
  fortint* is1,
  fortint* is2 ) {
long s0, s1, s2, edition;
int large = 0;
int found = 0;
int code = 0;
long bytes_read = 0, advance;
unsigned char p, edit_num, flag23;
unsigned char size[3];
int section0 = 8, section1, section2;
long total;

/*
// Read bytes until "GRIB" found
*/
    do {
      if( grab(buffer, &p, 1, 1, &bytes_read) != 0) return 1;
      code = ( (code << 8) + p ) & 0xFFFFFFFF;
      if (code == GRIB ) found = 1;
    } while ( ! found );
    s0 = bytes_read - 4;
    bytes_read = 4;
/*
// Now find out which edition of GRIB is present (default is 1)
*/
    edition = 1;
    s1 = s0 + 8;
    if( (*(buffer+21-s0) == '\0') && (*(buffer+22-s0) == '\0') ) {
      edition = -1;                            /* GRIB edition -1 */
      s1 = s0;
      section1 = 20;
      section0 = 4;
    }
    else {
      if( grab(buffer, size, 3, 1, &bytes_read) != 0) return 1;
      total = len3oct(size);
      if( total == 24 ) {
/*
// Move past the edition number
*/
        if( grab(buffer, &edit_num, 1, 1, &bytes_read) != 0) return 1;
        edition = 0;                         /* GRIB edition 0 */
        section1 = 24;
        s1 = s0 + 4;
        section0 = 4;
      }
    }

    if( edition == 1 ) {
/*
// See if it is an extra large (wave) product
*/
      if( total > 0x800000 ) {
        total = (total&0x7fffff) * 120;
        large = 1;
      }
/*
// Move past the edition number
*/
      if( grab(buffer, &edit_num, 1, 1, &bytes_read) != 0) return 1;
/*
// Read length of section 1
*/
      if( grab(buffer, size, 3, 1, &bytes_read) != 0) return 1;
      section1 = len3oct(size);
    }
/*
// Now figure out if section 2 is present
*/
    advance = 4;
    bytes_read += advance;
    if( grab(buffer, &flag23, 1, 1, &bytes_read) != 0) return 1;
    section2 = flag23 & BIT1;

/*
// Advance to end of section 1
*/
    advance = section1 - (bytes_read - section0);
    bytes_read += advance;
/*
// Read section 2 length if it is given
*/
    if( section2 ) {
      s2 = s0 + bytes_read;
      if( grab(buffer, size, 3, 1, &bytes_read) != 0) return 1;
      section2 = len3oct(size);
      advance = section2 - (bytes_read - section0 - section1);
      bytes_read += advance;
    }
    else {
      section2 = 0;
      s2 = 0;
    }
/*
// Success!
*/
    *is0 = (fortint) s0;
    *is1 = (fortint) s1;
    *is2 = (fortint) s2;
    return 0;
}

static int grab(unsigned char* buffer, unsigned char* where, long size,long cnt,long* num_bytes_read)
{
long number = size*cnt;

    memcpy(where, (buffer+(*num_bytes_read)), number);
    *num_bytes_read += number;

    return 0;
}

#define BASIC_HASH_TABLE_SIZE 1000
#define BASIC_HIT_LIST_SIZE 2

#define FILETABLE(a) ((a)->fileHashTable)
#define FILEENTRIES(a) (*(*FILETABLE(a)).entries)
#define ENTRY(a,b) ((a)->fileHashTable)->entries[(b)]
#define HIT(a,b,c) (ENTRY((a),(b)))->hitList[(c)]

void addHashEntry(
  gribfile* file,
  unsigned char* header,
  integer headerLength,
  integer number,
  integer index)
{
integer nextEntry = (*FILETABLE(file)).numberOfEntries;
integer maximum = (*FILETABLE(file)).maximumNumberOfEntries;
integer loop;

  if( DEBUG1 ) printf("addHashEntry: file = %0x\n",file);
/*
// See if hash number already in the hit list
*/
  for( loop = 0; loop < nextEntry; loop++ ) {

    if( ENTRY(file,loop)->value == number ) {
      integer nextHitEntry = ENTRY(file,loop)->numberOfHits;
      integer maximumHits  = ENTRY(file,loop)->maximumNumberOfHits;

      if( DEBUG1 ) {
        printf("addHashEntry: %0x already in hash table in position %d\n",
               number,loop);
        printf("addHashEntry: numberOfHits = %d\n",
               ENTRY(file,loop)->numberOfHits);
      }
      if( nextHitEntry < maximumHits ) {
        HIT(file,loop,nextHitEntry)->index = index;
        HIT(file,loop,nextHitEntry)->length = headerLength;
        HIT(file,loop,nextHitEntry)->bytes =
          (unsigned char*) malloc(headerLength);
        memcpy(HIT(file,loop,nextHitEntry)->bytes,header,headerLength);
        ENTRY(file,loop)->numberOfHits++;
        if( DEBUG1) printf("addHashEntry: numberOfHits -> %d\n",
                           ENTRY(file,loop)->numberOfHits);
      }
      else {
        integer hitLoop, newMaximum = maximumHits * 2;
        hit** newList = (hit**) malloc(sizeof(hit*)*newMaximum);

        if( DEBUG1 ) {
          printf("addHashEntry: nextHitEntry = %d\n",nextHitEntry);
          printf("addHashEntry: old hit limit = %d\n",maximumHits);
          printf("addHashEntry: expand to %d\n",newMaximum);
        }
        
        for( hitLoop = 0; hitLoop < maximumHits; hitLoop++ )
          newList[hitLoop] = HIT(file,loop,hitLoop);

        for( hitLoop = maximumHits; hitLoop < newMaximum; hitLoop++ ) {
          newList[hitLoop] = (hit*) malloc(sizeof(hit));
          newList[hitLoop]->index = -1; 
          newList[hitLoop]->length = 0; 
          newList[hitLoop]->bytes = NULL; 
        }

        free(ENTRY(file,loop)->hitList);
        ENTRY(file,loop)->hitList = newList;
        ENTRY(file,loop)->maximumNumberOfHits = newMaximum;

        HIT(file,loop,nextHitEntry)->index = index;
        HIT(file,loop,nextHitEntry)->length = headerLength;
        HIT(file,loop,nextHitEntry)->bytes =
          (unsigned char*) malloc(headerLength);
        memcpy(HIT(file,loop,nextHitEntry)->bytes,header,headerLength);
        ENTRY(file,loop)->numberOfHits++;
      }

      return;
    }
  }

  if( nextEntry < maximum ) {
    ENTRY(file,nextEntry)->value = number;
    if( DEBUG1 )
      printf("addHashEntry: ENTRY(file,%d) = %0x\n",
             nextEntry,ENTRY(file,nextEntry)->value);
    HIT(file,nextEntry,0)->index = index;
    HIT(file,nextEntry,0)->length = headerLength;
    HIT(file,nextEntry,0)->bytes = (unsigned char*) malloc(headerLength);
    memcpy(HIT(file,nextEntry,0)->bytes,header,headerLength);
    ENTRY(file,nextEntry)->numberOfHits = 1;

    (*FILETABLE(file)).numberOfEntries++;
  }
  else {
    integer loop, newSize= maximum * 2;
    integer entryLoop, hitLoop;
    hashValue** newEntries = (hashValue**) malloc(sizeof(hashValue*)*newSize);

    if( DEBUG1 ) {
      printf("addHashEntry: Hash table full\n");
      printf("addHashEntry: nextEntry = %d\n",nextEntry);
      printf("addHashEntry: current maximum = %d\n",maximum);
      printf("addHashEntry: increase maximum to %d\n",newSize);
    }

    for( entryLoop = 0; entryLoop < maximum; entryLoop++ )
      newEntries[entryLoop] = (file->fileHashTable)->entries[entryLoop];

    for( entryLoop = maximum; entryLoop < newSize; entryLoop++ ) {
      newEntries[entryLoop] = (hashValue*) malloc(sizeof(hashValue));
      newEntries[entryLoop]->hitList =
        (hit**) malloc(sizeof(hit)*BASIC_HIT_LIST_SIZE);
      newEntries[entryLoop]->maximumNumberOfHits = BASIC_HIT_LIST_SIZE;
      newEntries[entryLoop]->value = -1;

      for( hitLoop = 0; hitLoop < BASIC_HIT_LIST_SIZE; hitLoop++ ) {
        newEntries[entryLoop]->hitList[hitLoop] = (hit*) malloc(sizeof(hit));
        newEntries[entryLoop]->hitList[hitLoop]->index = 0;
        newEntries[entryLoop]->hitList[hitLoop]->length = 0;
        newEntries[entryLoop]->hitList[hitLoop]->bytes = NULL;
      }
    }

    free((file->fileHashTable)->entries);
    (file->fileHashTable)->entries = newEntries;
    (*FILETABLE(file)).maximumNumberOfEntries = newSize;

    ENTRY(file,nextEntry)->value = number;
    if( DEBUG1 )
      printf("addHashEntry: ENTRY(file,%d) = %0x\n",
             nextEntry,ENTRY(file,nextEntry)->value);
    HIT(file,nextEntry,0)->index = index;
    HIT(file,nextEntry,0)->length = headerLength;
    HIT(file,nextEntry,0)->bytes = (unsigned char*) malloc(headerLength);
    memcpy(HIT(file,nextEntry,0)->bytes,header,headerLength);
    ENTRY(file,nextEntry)->numberOfHits = 1;

    (*FILETABLE(file)).numberOfEntries++;
  }

  return;
}

void addHashNumberToFile(
  gribfile* file,
  unsigned char* header,
  integer headerLength,
  integer number,
  integer index)
{
integer entryLoop, hitLoop;

  if( FILETABLE(file) == NULL ) {

    FILETABLE(file) = (hashTable*) malloc(sizeof(hashTable));

    (*FILETABLE(file)).entries =
      (hashValue**) malloc(sizeof(hashValue*)*BASIC_HASH_TABLE_SIZE);
    (*FILETABLE(file)).maximumNumberOfEntries = BASIC_HASH_TABLE_SIZE;
    (*FILETABLE(file)).numberOfEntries = 0;

    for( entryLoop = 0; entryLoop < BASIC_HASH_TABLE_SIZE; entryLoop++ ) {

      (*FILETABLE(file)).entries[entryLoop] =
        (hashValue*) malloc(sizeof(hashValue));

      ENTRY(file,entryLoop)->hitList =
        (hit**) malloc(sizeof(hit)*BASIC_HIT_LIST_SIZE);
      ENTRY(file,entryLoop)->maximumNumberOfHits = BASIC_HIT_LIST_SIZE;
      ENTRY(file,entryLoop)->value = -1;

      for( hitLoop = 0; hitLoop < BASIC_HIT_LIST_SIZE; hitLoop++ ) {
        ENTRY(file,entryLoop)->hitList[hitLoop] = (hit*) malloc(sizeof(hit));
        HIT(file,entryLoop,hitLoop)->index = 0;
        HIT(file,entryLoop,hitLoop)->length = 0;
        HIT(file,entryLoop,hitLoop)->bytes = NULL;
      }
    }
  }

  addHashEntry(file,header,headerLength,number,index);

  return;
}

#define HASH_NOT_FOUND -1

integer correctMatchingHit(hashValue*,unsigned char*,integer);

integer findHashEntry(
  gribfile* file,
  integer number,
  unsigned char* header,
  integer headerLength)
{
/*
// See if hash number in the hit list
*/
integer numberInList = (*FILETABLE(file)).numberOfEntries;
integer loop;

  if( DEBUG1 )
    printf("findHashEntry: file = %0x, number = %0x\n",file, number);
  for( loop = 0; loop < numberInList; loop++ ) {
    if( ENTRY(file,loop)->value == number ) {
      if( DEBUG2 ) {
        printf("findHashEntry: %0x in hash table in position %d\n",
               number,loop);
        printf("findHashEntry: numberOfHits = %d\n",
               ENTRY(file,loop)->numberOfHits);
      }
      return correctMatchingHit(ENTRY(file,loop), header, headerLength);
    }
  }
  return HASH_NOT_FOUND;
}

integer correctMatchingHit(
  hashValue* entry,
  unsigned char* header,
  integer headerLength)
{
/*
// See if key details have a match in the hit list
*/
integer loop;
hit* nextHit;

  for( loop = 0; loop < entry->numberOfHits; loop++ ) {
    nextHit = entry->hitList[loop];
    if( nextHit->length == headerLength )
      if( memcmp(nextHit->bytes,header,headerLength) == 0 )
        return nextHit->index;
  }

  return HASH_NOT_FOUND;
}
