#include <stdio.h>
#include <stdlib.h>
#include <string.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

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


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


int main( int argc, char ** argv ) {
FILE * changeList, * sourceFile, * targetFile;
int status;
Changes * changes = NULL;
unsigned char * buffer;
long size, position, buflen, bufsize = BUFFLEN;

  if(argc < 4 ) {
    printf("\nUsage: changeGrib changeList sourceFile targetFile\n");
    printf("changeList is formattted as a Fortran-style NAMELIST\n\n");
    printf("  &USHEAD\n");
    printf("    IUSEC1(10)=99,\n");
    printf("    IUSEC1(11)=12,\n");
    printf("    IUSEC1(12)=25,\n");
    printf("    IUSEC1(13)=23,\n");
    printf("    IUSEC1(41)=jdc1,\n");
    printf(" /\n");

    exit(1);
  }

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

  if( ( sourceFile = fopen(argv[2],"r")) == NULL ) {
    printf("Problem opening file %s for reading\n",argv[2]);
    exit(1);
  }

  if( (targetFile = fopen(argv[3],"w")) == NULL ) {
    printf("Problem opening file %s for writing\n",argv[3]);
    exit(1);
  }
/*
// Allocate buffer for reading GRIB products
*/
   buffer = (unsigned char *) malloc(bufsize);
   if( buffer == NULL ) {
    printf("Problem with malloc for GRIB buffer\n");
    exit(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:

        {unsigned char * p = buffer+SECTION0;
         int length = ((*p)<<16) + (*(p+1)<<8) + (*(p+2));

         if( length < 29 ) {
           printf("GRIB is not using an ECMWF local definition\n");
           exit(1);
         }
        }

        patchHeader(buffer,changes);
        size = fwrite(buffer, (size_t) 1, (size_t) buflen, targetFile);
        if( size != buflen ) {
          printf("Problem writing to file %s\n", argv[3]);
          exit(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");
          exit(1);
        }
        status = (int) readgrib(sourceFile,buffer,&buflen);
        if( status == BUFFERTOOSMALL ) {
          printf("Buffer size still too small at %d bytes\n",bufsize);
          exit(1);
        }

        {unsigned char * p = buffer+SECTION0;
         int length = ((*p)<<16) + (*(p+1)<<8) + (*(p+2));

         if( length < 29 ) {
           printf("GRIB is not using an ECMWF local definition\n");
           exit(1);
         }
        }

        patchHeader(buffer,changes);
        size = fwrite(buffer, (size_t) 1, (size_t) buflen, targetFile);
        if( size != buflen ) {
          patchHeader(buffer,changes);
          size = fwrite(buffer, (size_t) 1, (size_t) buflen, targetFile);
          if( size != buflen ) {
            printf("Problem writing to file %s\n", argv[3]);
            exit(1);
          }
        }
        break;

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

    }

  } while( status == 0 );

/*
// That's all
*/
/*
??free(&(changes->types));
??free(&(changes->values));
*/
  free(changes);
  fclose(sourceFile);
  fclose(targetFile);

}

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 ) {
      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) {
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("Not allowed to change ECMWF local usage definition number\n");
        break;

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

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

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

      case EXPVER:
        memcpy(&buffer[EXPVEROCTET],&(changes->values[next]),4);
        break;

    }

  }
  return;
}
