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

#define FORTINT_H

#ifdef INTEGER_IS_INT
#define fortint int
#else
#ifdef hpR64
#define fortint long long
#else
#define fortint long
#endif
#endif

#endif /* end of  FORTINT_H */

#define SECTION0 8

#define TABLE2 1
#define TABLE2OCTET SECTION0+3
#define CENTRE 2
#define CENTREOCTET SECTION0+4
#define GENPID 3
#define GENPIDOCTET SECTION0+5
#define GRID 4
#define GRIDOCTET SECTION0+6
#define PARAM 6
#define PARAMOCTET SECTION0+8
#define LEVELIND 7
#define LEVELINDOCTET SECTION0+9
#define LEVEL1 8
#define LEVEL1OCTET SECTION0+10
#define LEVEL2 9
#define LEVEL2OCTET SECTION0+11
#define YEAR 10
#define YEAROCTET SECTION0+12
#define MONTH 11
#define MONTHOCTET SECTION0+13
#define DAY 12
#define DAYOCTET SECTION0+14
#define HOUR 13
#define HOUROCTET SECTION0+15
#define MINUTE 14
#define MINUTEOCTET SECTION0+16
#define TIMEUNIT 15
#define TIMEUNITOCTET SECTION0+17
#define TIME1 16
#define TIME1OCTET SECTION0+18
#define TIME2 17
#define TIME2OCTET SECTION0+19
#define TIMERANGE 18
#define TIMERANGEOCTET SECTION0+20
#define NUMINAV 19
#define NUMINAVOCTET SECTION0+21
#define NUMMISSING 20
#define NUMMISSINGOCTET SECTION0+23
#define CENTURY 21
#define CENTURYOCTET SECTION0+24
#define SUBCENTRE 22
#define SUBCENTREOCTET SECTION0+25
#define DECSCALE 23
#define DECSCALEOCTET SECTION0+26
#define LOCALUSE 37
#define LOCALUSEOCTET SECTION0+40
#define CLASS 38
#define CLASSOCTET SECTION0+41
#define TYPE 39
#define TYPEOCTET SECTION0+42
#define STREAM 40
#define STREAMOCTET SECTION0+43
#define EXPVER 41
#define EXPVEROCTET SECTION0+45

#define BUFFLEN 1000000
#define OK 0
#define ENDOFFILE -1
#define BUFFERTOOSMALL -3

#define NAMEBUFFLEN 256

typedef struct Changes {
  int * types;
  int * values;
  int numberOfValues;
} Changes;

void patchHeader(unsigned char *, Changes *, long, long);
int readChanges(FILE * , Changes ** );
fortint readgrib( FILE * , unsigned char * ,fortint * );

#ifdef FORTRAN_NO_UNDERSCORE
#define CHGRIB chgrib
#else
#define CHGRIB chgrib_
#endif

int CHGRIB(
  char * originalChangeListName,
  char * originalSourceFileName,
  char * originalTargetFileName,
  int l1,
  int l2,
  int l3 ) {

char changeListName[NAMEBUFFLEN];
char sourceFileName[NAMEBUFFLEN];
char targetFileName[NAMEBUFFLEN];
FILE * changeList, * sourceFile, * targetFile;
int status;
Changes * changes = NULL;
unsigned char * buffer, * section1Start;
long section1Length, ecmwfLocalUsage;
long size, position, bufsize = BUFFLEN;
fortint buflen;
long productNumber = 0;

/*
//
// Called from FORTRAN:
//
//    IRET = CHGRIB(NLIST, IFILE, OFILE)
//
// NLIST is formattted as a Fortran-style NAMELIST:
//
//   &USHEAD\n");
//     IUSEC1(10)=99,
//     IUSEC1(11)=12,
//     IUSEC1(12)=25,
//     IUSEC1(13)=23,
//     IUSEC1(41)=jdc1,
//  /
//
// IFILE: input file of GRIB products
//
// OFILE: output file of modified GRIB products
//
// Returns 0 if all OK, otherwise 1.
//
*/

/*
// Copy the filenames and put in a null terminator
*/
  {
   int n;
   char * p;

   n = (l1 > NAMEBUFFLEN) ? NAMEBUFFLEN : l1;
   strncpy( changeListName, originalChangeListName, n);
   changeListName[n] = '\0';
   p  = changeListName + strlen(changeListName) - 1 ;
   while(*p == ' ') {
     *p = 0;
     p--;
   }

   n = (l2 > NAMEBUFFLEN) ? NAMEBUFFLEN : l2;
   strncpy( sourceFileName, originalSourceFileName, n);
   sourceFileName[n] = '\0';
   p  = sourceFileName + strlen(sourceFileName) - 1 ;
   while(*p == ' ') {
     *p = 0;
     p--;
   }

   n = (l3 > NAMEBUFFLEN) ? NAMEBUFFLEN : l3;
   strncpy( targetFileName, originalTargetFileName, n);
   targetFileName[n] = '\0';
   p  = targetFileName + strlen(targetFileName) - 1 ;
   while(*p == ' ') {
     *p = 0;
     p--;
   }
  }


  if( (changeList = fopen(changeListName,"r")) == NULL ) {
    printf("Problem opening file %s for reading\n",changeListName);
    return (fortint) 1;
  }
/*
// Read required changes
*/
  status = readChanges(changeList, &changes);
  if( status != 0 ) {
    printf("Problem reading required changes from file %s\n",changeListName);
    return (fortint) 1;
  }

  if( ( sourceFile = fopen(sourceFileName,"r")) == NULL ) {
    printf("Problem opening file %s for reading\n",sourceFileName);
    return (fortint) 1;
  }

  if( (targetFile = fopen(targetFileName,"w")) == NULL ) {
    printf("Problem opening file %s for writing\n",targetFileName);
    return (fortint) 1;
  }
/*
// Allocate buffer for reading GRIB products
*/
   buffer = (unsigned char *) malloc(bufsize);
   if( buffer == NULL ) {
    printf("Problem with malloc for GRIB buffer\n");
    return (fortint) 1;
  }

/*
// Loop through GRIB products:
//  - read GRIB from sourceFile
//  - patch section 1
//  - write GRIB to targetFile
*/

  do {
    buflen = bufsize;
    position = ftell(sourceFile);
    status = (int) readgrib(sourceFile,buffer,&buflen);

    switch( status ) {

      case OK:
        section1Start = buffer+SECTION0;
        section1Length = ((*section1Start)<<16) +
                         (*(section1Start+1)<<8) +
                         (*(section1Start+2));
        ecmwfLocalUsage = ( section1Length > 28 );
        productNumber++;
        patchHeader(buffer,changes,ecmwfLocalUsage,productNumber);
        size = fwrite(buffer, (size_t) 1, (size_t) buflen, targetFile);
        if( size != buflen ) {
          printf("Problem writing to file %s\n", targetFileName);
          return (fortint) 1;
        }
        break;

      case ENDOFFILE:
        break;

      case BUFFERTOOSMALL:
        bufsize *= 10;
        buffer = (unsigned char *) realloc(buffer,(size_t) bufsize);
        buflen = bufsize;
        status = fseek(sourceFile,position,0);
        if( status < 0 ) {
          perror("Error repositioning file");
          return (fortint) 1;
        }
        status = (int) readgrib(sourceFile,buffer,&buflen);
        if( status == BUFFERTOOSMALL ) {
          printf("Buffer size still too small at %d bytes\n",bufsize);
          return (fortint) 1;
        }

        section1Start = buffer+SECTION0;
        section1Length = ((*section1Start)<<16) +
                         (*(section1Start+1)<<8) +
                         (*(section1Start+2));
        ecmwfLocalUsage = ( section1Length > 28 );
        productNumber++;
        patchHeader(buffer,changes,ecmwfLocalUsage,productNumber);
        size = fwrite(buffer, (size_t) 1, (size_t) buflen, targetFile);
        if( size != buflen ) {
          printf("Problem writing to file %s\n", targetFileName);
          return (fortint) 1;
        }
        break;

      default:
        printf("Problem reading GRIB product; status = %d\n", status);
        return (fortint) 1;

    }

  } while( status == 0 );

/*
// That's all
*/
  free(changes);
  fclose(sourceFile);
  fclose(targetFile);

  return (fortint) 0;

}

int readChanges(FILE * changeList, Changes ** realChanges) {
char line[80], expver[5];
int type, value;
Changes * changes;

  fscanf(changeList,"%s",line);
  if( strcmp(line,"&USHEAD") != 0 ) {
    printf("changeList does not start with &USHEAD\n");
    return 1;
  }

  changes = (Changes *) malloc(sizeof(Changes));
  changes->numberOfValues = 0;
  changes->types = NULL;
  changes->values  = NULL;

  do {
    fscanf(changeList,"%s",line);
    if( strcmp(line,"/") != 0 ) {
      if(  sscanf(line,"CNMEXP='%4s',",expver) == 1 )     /* single quotes */
        type = EXPVER;
      else
        if(  sscanf(line,"CNMEXP=\"%4s\",",expver) == 1 ) /* double quotes */
          type = EXPVER;
        else
          if(  sscanf(line,"CNMEXP=%4s,",expver) == 1 )   /* no quotes */
            type = EXPVER;
          else
            sscanf(line,"IUSEC1(%d)=%d,",&type,&value);

      changes->numberOfValues++;
      changes->types =
        (int *) realloc(changes->types,changes->numberOfValues*sizeof(int));
      changes->types[changes->numberOfValues-1] = type;

      changes->values =
        (int *) realloc(changes->values,changes->numberOfValues*sizeof(int));

      if( type == EXPVER ) {
        sscanf(line,"IUSEC1(%d)=%s,",&type,expver);
        memcpy(&(changes->values[changes->numberOfValues-1]),expver,4);
      }
      else
        changes->values[changes->numberOfValues-1]  = value;

    }
  } while( !feof(changeList) );

  fclose(changeList);
  *realChanges = changes;
  return 0;
}

void patchHeader(
  unsigned char * buffer,
  Changes * changes,
  long ecmwfLocalUsage,
  long productNumber)
{
int next, value;

  for( next = 0; next < (changes->numberOfValues); next++ ) {
    switch( changes->types[next] ) {

      case YEAR:
        buffer[YEAROCTET] = (unsigned char) changes->values[next];
        break;

      case MONTH:
        buffer[MONTHOCTET] = (unsigned char) changes->values[next];
        break;

      case DAY:
        buffer[DAYOCTET] = (unsigned char) changes->values[next];
        break;

      case HOUR:
        buffer[HOUROCTET] = (unsigned char) changes->values[next];
        break;

      case MINUTE:
        buffer[MINUTEOCTET] = (unsigned char) changes->values[next];
        break;

      case CENTURY:
        buffer[CENTURYOCTET] = (unsigned char) changes->values[next];
        break;

      case TABLE2:
        buffer[TABLE2OCTET] = (unsigned char) changes->values[next];
        break;

      case CENTRE:
        buffer[CENTREOCTET] = (unsigned char) changes->values[next];
        break;

      case GENPID:
        buffer[GENPIDOCTET] = (unsigned char) changes->values[next];
        break;

      case GRID:
        buffer[GRIDOCTET] = (unsigned char) changes->values[next];
        break;

      case PARAM:
        buffer[PARAMOCTET] = (unsigned char) changes->values[next];
        break;

      case LEVELIND:
        buffer[LEVELINDOCTET] = (unsigned char) changes->values[next];
        break;

      case LEVEL1:
        buffer[LEVEL1OCTET] = (unsigned char) changes->values[next];
        break;

      case LEVEL2:
        buffer[LEVEL2OCTET] = (unsigned char) changes->values[next];
        break;

      case TIMEUNIT:
        buffer[TIMEUNITOCTET] = (unsigned char) changes->values[next];
        break;

      case TIME1:
        buffer[TIME1OCTET] = (unsigned char) changes->values[next];
        break;

      case TIME2:
        buffer[TIME2OCTET] = (unsigned char) changes->values[next];
        break;

      case TIMERANGE:
        buffer[TIMERANGEOCTET] = (unsigned char) changes->values[next];
        break;

      case NUMINAV:
        value = changes->values[next];
        buffer[NUMINAVOCTET] = (unsigned char) ( (value >> 8 ) & 0xff);
        buffer[NUMINAVOCTET+1] = (unsigned char) ( value & 0xff);
        break;

      case NUMMISSING:
        buffer[NUMMISSINGOCTET] = (unsigned char) changes->values[next];
        break;

      case SUBCENTRE:
        buffer[SUBCENTREOCTET] = (unsigned char) changes->values[next];
        break;

      case DECSCALE:
        value = changes->values[next];
        buffer[DECSCALEOCTET] = (unsigned char) ( (value >> 8 ) & 0xff);
        buffer[DECSCALEOCTET+1] = (unsigned char) ( value & 0xff);
        break;

      case LOCALUSE:
/*
//      buffer[LOCALUSEOCTET] = (unsigned char) changes->values[next];
*/
        printf("Following change ignored for product %d\n", productNumber);   
        printf(" IUSEC1(%d)=%d,\n",changes->types[next], changes->values[next]);
        printf("Not allowed to change ECMWF local usage definition number\n");
        break;

      case CLASS:
        if( ecmwfLocalUsage )
          buffer[CLASSOCTET] = (unsigned char) changes->values[next];
        else {
          printf("Following change ignored for product %d\n", productNumber);   
          printf(" IUSEC1(%d)=%d,\n",changes->types[next], changes->values[next]);
          printf("Cannot change CLASS, no ECMWF local definition in GRIB\n");
        }
        break;

      case TYPE:
        if( ecmwfLocalUsage )
          buffer[TYPEOCTET] = (unsigned char) changes->values[next];
        else {
          printf("Following change ignored for product %d\n", productNumber);   
          printf(" IUSEC1(%d)=%d,\n",changes->types[next], changes->values[next]);
          printf("Cannot change TYPE, no ECMWF local definition in GRIB\n");
        }
        break;

      case STREAM:
        if( ecmwfLocalUsage ) {
          value = changes->values[next];
          buffer[STREAMOCTET] = (unsigned char) ( (value >> 8 ) & 0xff);
          buffer[STREAMOCTET+1] = (unsigned char) ( value & 0xff);
        }
        else {
          printf("Following change ignored for product %d\n", productNumber);
          printf(" IUSEC1(%d)=%d,\n",changes->types[next], changes->values[next]);
          printf("Cannot change STREAM, no ECMWF local definition in GRIB\n");
        }
        break;

      case EXPVER:
        if( ecmwfLocalUsage )
          memcpy(&buffer[EXPVEROCTET],&(changes->values[next]),4);
        else {
          printf("Following change ignored for product %d\n", productNumber);
          {
           char expver[5];
             memcpy(expver,&(changes->values[next]),4);
             expver[4] = 0;
             printf(" IUSEC1(%d)=%s,\n",changes->types[next], expver);
          }
          printf("Cannot change EXPVER, no ECMWF local definition in GRIB\n");
        }
        break;

      default:
        printf("Following change ignored for product %d\n", productNumber);
        printf(" IUSEC1(%d)=%d,\n",changes->types[next], changes->values[next]);
        printf("Requested change not suitable for section 1 in the GRIB\n");
        printf("Only bytes found in ECMWF local definition 1 can be changed\n");
        break;

    }

  }
  return;
}
