#include <stdio.h>
#include <stdlib.h>
#include "ruby.h"
#include "grib_api.h"
#include "narray.h"


#define MAX_VALUE_LENGTH 1024

// error check
static void
check_error(int code)
{
  if (code) rb_raise(rb_eRuntimeError, grib_get_error_message(code));
};




// flexible length array
#define BUF_SIZE 256
#define MAX_BUF_NUM 1000
typedef struct {
  grib_handle ***buffer;
  size_t size;
  size_t len;
} msg_array;
static msg_array*
alloc_msg_array(void)
{
  msg_array *ary = ALLOC(msg_array);
  if (ary) {
    ary->buffer = (grib_handle***) malloc(sizeof(grib_handle**)*MAX_BUF_NUM);
    if (ary->buffer) {
      ary->buffer[0] = (grib_handle**) malloc(sizeof(grib_handle*)*BUF_SIZE);
      if (ary->buffer[0]) {
	ary->size = BUF_SIZE;
	ary->len = 0;
	return ary;
      }
      free(ary->buffer);
    }
    free(ary);
  }
  return NULL;
}
static
void free_msg_array(msg_array *ary)
{
  int i, j;
  int num = ary->size/BUF_SIZE;
  int len;
  for (i=0; i<num; i++) {
    len = i==num-1 ? ary->len%BUF_SIZE : BUF_SIZE;
    for (j=0; j<len; j++) grib_handle_delete(ary->buffer[i][j]);
    free(ary->buffer[i]);
  }
  free(ary->buffer);
  free(ary);
}
static void
push_msg(msg_array *ary, grib_handle *handle)
{
  int idx = ary->size/BUF_SIZE-1;
  if (ary->len >= ary->size) {
    idx += 1;
    if (idx < MAX_BUF_NUM) {
      ary->buffer[idx] = (grib_handle**)malloc(sizeof(grib_handle*)*BUF_SIZE);
      if (ary->buffer[idx]) {
	ary->size += BUF_SIZE;
      } else {
	rb_raise(rb_eRuntimeError, "cannot allocate memory");
      }
    } else {
      rb_raise(rb_eRuntimeError, "cannot allocate memory");
    }
  }
  ary->buffer[idx][ary->len%BUF_SIZE] = handle;
  ary->len += 1;
}
static grib_handle*
get_msg(msg_array *ary, size_t index)
{
  if (index < ary->len) {
    return ary->buffer[index/BUF_SIZE][index%BUF_SIZE];
  } else {
    rb_raise(rb_eRuntimeError, "index exceed size of array");
  }
  return NULL;
}


// variable list
typedef struct var_list var_list;
struct var_list {
  char *vname;
  long ni;
  long nj;
  char *gtype;
  long ltype_id;
  char *ltype;
  msg_array *ary;
  var_list *next;
  VALUE file;
  long disc;
  long mtabl;
  long cent;
  long ltabl;
  long categ;
  long pnum;
};
static var_list*
var_list_alloc(VALUE file)
{
  var_list *var = ALLOC(var_list);
  if (var) {
    var->ary = alloc_msg_array();
    if (var->ary) {
      var->vname = (char*) malloc(sizeof(char)*MAX_VALUE_LENGTH*2);
      if (var->vname) {
	var->gtype = (char*) malloc(sizeof(char)*MAX_VALUE_LENGTH);
	if (var->gtype) {
	  var->ltype = (char*) malloc(sizeof(char)*MAX_VALUE_LENGTH);
	  if (var->ltype) {
	    var->next = NULL;
	    var->file = file;
	    return var;
	  }
	  free(var->gtype);
	}
	free(var->vname);
      }
      free_msg_array(var->ary);
    }
    free(var);
  }
  return NULL;
}
static void
var_list_free(var_list *var)
{
  var_list *next;
  while(var) {
    free_msg_array(var->ary);
    free(var->vname);
    free(var->gtype);
    free(var->ltype);
    var->file = Qnil;
    next = var->next;
    free(var);
    var = next;
  }
}
static void
push_msg_var(var_list **pvar, grib_handle *handle, VALUE file)
{
  size_t len = MAX_VALUE_LENGTH;
  char vname[MAX_VALUE_LENGTH];
  /*
  if (grib_get_string(handle, "shortName", vname, &len) != GRIB_SUCCESS ||
      strcmp("unknown", vname) == 0) {
    long id;
    if (grib_get_long(handle, "indicatorOfParameter", &id) == GRIB_SUCCESS ||
	grib_get_long(handle, "parameterNumber", &id) == GRIB_SUCCESS) {
      sprintf(vname, "id%ld", id);
    } else {
      printf("%s\n", vname);
      rb_raise(rb_eRuntimeError, "cannot get variable name");
    }
  }
  */
  long edition;
  long cent, mtabl, pnum;
  long disc, ltabl, categ;
  long ni, nj, ltype_id;
  char gtype[MAX_VALUE_LENGTH];
  len = MAX_VALUE_LENGTH;

  grib_get_long(handle, "editionNumber", &edition);
  grib_get_long(handle, "centre", &cent);
  if(grib_get_long(handle, "Ni", &ni) != GRIB_SUCCESS ||
     grib_get_long(handle, "Nj", &nj) != GRIB_SUCCESS ||
     grib_get_string(handle, "typeOfGrid", gtype, &len) != GRIB_SUCCESS ||
     (grib_get_long(handle, "indicatorOfTypeOfLevel", &ltype_id) != GRIB_SUCCESS && grib_get_long(handle, "typeOfFirstFixedSurface", &ltype_id) != GRIB_SUCCESS))
    rb_raise(rb_eRuntimeError, "connot identify grid type");
  switch (edition) {
  case 1:
    if(grib_get_long(handle, "table2Version", &mtabl) != GRIB_SUCCESS ||
       grib_get_long(handle, "indicatorOfParameter", &pnum) != GRIB_SUCCESS)
      rb_raise(rb_eRuntimeError, "connot identify variable");
    break;
  case 2:
    if (grib_get_long(handle, "tablesVersion", &mtabl) != GRIB_SUCCESS ||
	grib_get_long(handle, "parameterNumber", &pnum) != GRIB_SUCCESS ||
	grib_get_long(handle, "discipline", &disc) != GRIB_SUCCESS ||
	grib_get_long(handle, "localTablesVersion", &ltabl) != GRIB_SUCCESS ||
	grib_get_long(handle, "parameterCategory", &categ) != GRIB_SUCCESS)
      rb_raise(rb_eRuntimeError, "connot identify variable");
  }
  var_list *last = NULL;
  var_list *var = pvar[0];
  if (var) {
    while (var) {
      if (var->cent == cent && var->mtabl == mtabl && var->pnum == pnum &&
	  var->ni == ni && var->nj == nj &&
	  strcmp(var->gtype,gtype)==0 &&
	  var->ltype_id==ltype_id) {
	if (edition == 1)
	  push_msg(var->ary, handle);
	else if (edition == 2 &&
	    var->disc == disc && var->ltabl == ltabl &&  var->categ == categ)
	  push_msg(var->ary, handle);
	return;
      }
      last = var;
      var = var->next;
    }
  }
  var_list *var_new = var_list_alloc(file);
  if (var_new == NULL)
    rb_raise(rb_eRuntimeError, "cannot allocate memory");
  var_new->cent = cent;
  var_new->mtabl = mtabl;
  var_new->pnum = pnum;
  if (edition == 2) {
    var_new->disc = disc;
    var_new->ltabl = ltabl;
    var_new->categ = categ;
  }
  var_new->ni = ni;
  var_new->nj = nj;
  var_new->ltype_id = ltype_id;
  strcpy(var_new->gtype, gtype);

  len = MAX_VALUE_LENGTH;
  bzero(vname, len);
  if (grib_get_string(handle, "shortName", vname, &len) != GRIB_SUCCESS ||
      strcmp("unknown", vname) == 0) {
    sprintf(vname, "id%ld", pnum);
  }
  strcpy(var_new->vname, vname);

  len = MAX_VALUE_LENGTH;
  bzero(vname, len);
  check_error(grib_get_string(handle, "typeOfLevel", vname, &len));
  if (strcmp(vname, "unknown")==0 || strcmp(vname,"isobaricInhPa")== 0 || strcmp(vname,"pl")==0)
    strcpy(var_new->ltype, vname);
  else {
    int i, j;
    var_new->ltype[0] = vname[0];
    for (i=1,j=1; i<strlen(vname); i++) {
      if (vname[i]>=65 && vname[i]<=90) {
	var_new->ltype[j] = vname[i]+32;
	j++;
      }
    }
    if (j==1)
      strcpy(var_new->ltype, vname);
    else
      var_new->ltype[j] = 0;
  }
  push_msg(var_new->ary, handle);
  if (last)
    last->next = var_new;
  else
    pvar[0] = var_new;
}




VALUE cGrib;
VALUE cVar;
VALUE cMessage;

typedef struct {
  FILE *file;
  char *fname;
  var_list *var;
} rg_file;
typedef struct {
  VALUE file;
  var_list *var_list;
} rg_var;
typedef struct {
  VALUE file;
  grib_handle *handle;
} rg_message;


static void
message_free(rg_message *message)
{
  if (message) free(message);
}
static void
message_mark(rg_message *message)
{
  if (message && message->file != Qnil) rb_gc_mark(message->file);
}
static VALUE
message_alloc(VALUE klass)
{
  rg_message *message = ALLOC(rg_message);
  message->file = Qnil;
  return Data_Wrap_Struct(klass, message_mark, message_free, message);
}
static void
var_free(rg_var *var)
{
  if (var) free(var);
}
static void
var_mark(rg_var *var)
{
  if (var && var->file != Qnil) rb_gc_mark(var->file);
}
static VALUE
var_alloc(VALUE klass)
{
  rg_var *var = ALLOC(rg_var);
  var->file = Qnil;
  return Data_Wrap_Struct(klass, var_mark, var_free, var);
}
static void
file_close(rg_file *gfile)
{
  if (gfile) {
    if (gfile->var) {
      var_list_free(gfile->var);
      gfile->var = NULL;
    }
    if (gfile->file) {
      fclose(gfile->file);
      gfile->file = NULL;
    }
    if (gfile->fname) {
      free(gfile->fname);
      gfile->fname = NULL;
    }
  }
}
static void
file_free(rg_file *gfile)
{
  if (gfile) {
    file_close(gfile);
    free(gfile);
  }
}
static VALUE
file_alloc(VALUE klass)
{
  rg_file *gfile = ALLOC(rg_file);
  gfile->file = NULL;
  gfile->fname = NULL;
  gfile->var = NULL;
  return Data_Wrap_Struct(klass, 0, file_free, gfile);
}

/*
  NumRu::Grib.multi=(flag) -> True/False
*/
static VALUE
rg_multi(VALUE self, VALUE flag)
{
  if (flag == Qtrue)
    grib_multi_support_on(0);
  else if (flag == Qfalse)
    grib_multi_support_off(0);
  else
    rb_raise(rb_eArgError, "flag must be true or false");
  return flag;
}

/*
  NumRu::Grib#initialize(filename, [, mode])
*/
static VALUE
rg_file_initialize(int argc, VALUE *argv, VALUE self)
{
  char *fname, *mode;
  VALUE rfname, rmode;
  rb_scan_args(argc, argv, "11", &rfname, &rmode);
  fname = StringValueCStr(rfname);
  if (rmode == Qnil)
    mode = "r";
  else
    mode = StringValueCStr(rmode);

  FILE *file = fopen(fname, mode);
  if (!file)
    rb_raise(rb_eRuntimeError, "unable to open file %s", fname);

  rg_file *gfile;
  Data_Get_Struct(self, rg_file, gfile);
  gfile->file = file;
  gfile->fname = ALLOC_N(char, strlen(fname)+1);
  strcpy(gfile->fname, fname);

  int error;
  grib_handle *handle;
  var_list *var = NULL;
  while ((handle = grib_handle_new_from_file(0, file, &error))) {
    check_error(error);
    if (handle == NULL)
      rb_raise(rb_eRuntimeError, "unable to create handle in %s", fname);
    push_msg_var(&var, handle, self);
  }
  gfile->var = var;
  var_list *var2;
  const char *vname;
  char buf[MAX_VALUE_LENGTH];
  int first_ltype, first_gtype;
  int i;
  while (var) {
    vname = var->vname;
    var2 = var->next;
    i = 0;
    first_ltype = 1;
    first_gtype = 1;
    while (var2) {
      if (strcmp(var2->vname, vname) == 0) {
	if (var->ltype_id != var2->ltype_id) {
	  if (first_ltype) {
	    if (strcmp(var->ltype,"isobaricInhPa") != 0 && strcmp(var->ltype,"pl") !=0) {
	      if (strcmp(var->ltype, "unknown")==0)
		sprintf(buf, "%s_level%ld", var->vname, var->ltype_id);
	      else
		sprintf(buf, "%s_%s", var->vname, var->ltype);
	      strcpy(var->vname, buf);
	    }
	    first_ltype = 0;
	  }
	  if (strcmp(var2->ltype,"isobaricInhPa") != 0 && strcmp(var2->ltype,"pl") != 0) {
	    if (strcmp(var2->ltype, "unknown")==0)
	      sprintf(buf, "%s_level%ld", var2->vname, var2->ltype_id);
	    else
	      sprintf(buf, "%s_%s", var2->vname, var2->ltype);
	    strcpy(var2->vname, buf);
	  }
	} else if (strcmp(var->gtype, var2->gtype) != 0) {
	  if (first_gtype) {
	    sprintf(buf, "%s_%s", var->vname, var->gtype);
	    strcpy(var->vname, buf);
	    first_gtype = 0;
	  }
	  sprintf(buf, "%s_%s", var2->vname, var2->gtype);
	  strcpy(var2->vname, buf);
	} else {
	  if (i==0) {
	    sprintf(buf, "%s_%d", var->vname, i);
	    strcpy(var->vname, buf);
	    i += 1;
	  }
	  sprintf(buf, "%s_%d", var2->vname, i);
	  strcpy(var2->vname, buf);
	  i += 1;
	}
      }
      var2 = var2->next;
    }
    var = var->next;
  }
  
  return self;
}
/*
  NumRu::Grib#close() -> nil
 */
static VALUE
rg_file_close(VALUE self)
{
  rg_file *gfile;
  Data_Get_Struct(self, rg_file, gfile);
  file_close(gfile);
  return Qnil;
}

/*
  NumRu::Grib#path() -> String
*/
static VALUE
rg_file_path(VALUE self)
{
  rg_file *gfile;
  Data_Get_Struct(self, rg_file, gfile);
  return rb_str_new2(gfile->fname);
}

/*
  NumRu::Grib#var_names() -> Array
*/
static VALUE
rg_file_var_names(VALUE self)
{
  rg_file *gfile;
  Data_Get_Struct(self, rg_file, gfile);
  VALUE ary = rb_ary_new();
  var_list *var = gfile->var;
  while (var) {
    rb_ary_push(ary, rb_str_new2(var->vname));
    var = var->next;
  }
  return ary;
}

static VALUE id_init;
/*
  NumRu::Grib#var(String) -> NumRu::GribVar
*/
static VALUE
rg_file_var(VALUE self, VALUE rvname)
{
  char *vname = StringValueCStr(rvname);
  rg_file *gfile;
  Data_Get_Struct(self, rg_file, gfile);
  var_list *var_list = gfile->var;
  while (var_list) {
    if (strcmp(var_list->vname, vname) == 0) {
      VALUE rvar = var_alloc(cVar);
      rg_var *var;
      Data_Get_Struct(rvar, rg_var, var);
      var->file = self;
      var->var_list = var_list;
      rb_funcall(rvar, id_init, 0);
      return rvar;
    }
    var_list = var_list->next;
  }
  rb_raise(rb_eArgError, "cannot find variable: %s", vname);
}

/*
  NumRu::GribVar#file() -> NumRu::Grib
*/
static VALUE
rg_var_file(VALUE self)
{
  rg_var *var;
  Data_Get_Struct(self, rg_var, var);
  return var->file;
}

/*
  NumRu::GribVar#name() -> String
*/
static VALUE
rg_var_name(VALUE self)
{
  rg_var *var;
  Data_Get_Struct(self, rg_var, var);
  return rb_str_new2(var->var_list->vname);
}

/*
  NumRu::GribVar#get_messages() -> Array
*/
static VALUE
rg_var_get_messages(VALUE self)
{
  rg_var *var;
  Data_Get_Struct(self, rg_var, var);
  var_list *var_list = var->var_list;
  size_t len = var_list->ary->len;
  VALUE ary = rb_ary_new2(len);
  rg_message *message;
  VALUE rmessage;
  int i;
  for (i=0; i<len; i++) {
    rmessage = message_alloc(cMessage);
    Data_Get_Struct(rmessage, rg_message, message);
    message->file = self;
    message->handle = get_msg(var_list->ary, i);
    rb_ary_store(ary, i, rmessage);
  }
  return ary;

}

/*
  NumRu::GribMessage#initialize()
*/
static VALUE
rg_message_initialize(VALUE self, VALUE file)
{
  rg_message *message;
  Data_Get_Struct(self, rg_message, message);
  message->file = file;
  return self;
}

/*
  NumRu::GribMessage#get_keys() -> Array
*/
static VALUE
rg_message_get_keys(int argc, VALUE *argv, VALUE self)
{
  VALUE rflag, rns;
  unsigned long flag = GRIB_KEYS_ITERATOR_SKIP_READ_ONLY || GRIB_KEYS_ITERATOR_SKIP_COMPUTED;
  //  unsigned long flag = GRIB_KEYS_ITERATOR_ALL_KEYS;
  char *name_space = NULL;
  rb_scan_args(argc, argv, "02", &rflag, &rns);
  if (rflag != Qnil) flag = NUM2ULONG(rflag);
  if (rns != Qnil) name_space = StringValueCStr(rns);
    
  rg_message *message;
  Data_Get_Struct(self, rg_message, message);
  grib_keys_iterator *ki = NULL;
  ki = grib_keys_iterator_new(message->handle, flag, name_space);
  if (!ki)
    rb_raise(rb_eRuntimeError, "unable to create key iterator");
  VALUE keys = rb_ary_new();
  while (grib_keys_iterator_next(ki)) {
    const char *name = grib_keys_iterator_get_name(ki);
    rb_ary_push(keys, rb_str_new2(name));
  }
  grib_keys_iterator_delete(ki);
  return keys;
}

/*
  NumRu::GribMessage#get_value(name [,type]) -> String
*/
static VALUE
rg_message_get_value(int argc, VALUE *argv, VALUE self)
{
  VALUE rname, rtype;
  rb_scan_args(argc, argv, "11", &rname, &rtype);
  char *name = StringValueCStr(rname);
  int type;
  rg_message *message;
  Data_Get_Struct(self, rg_message, message);
  if (rtype == Qnil)
    grib_get_native_type(message->handle, name, &type);
  else
    type = NUM2INT(rtype);
  size_t len;
  VALUE ret = Qnil;
  grib_get_size(message->handle, name, &len);
  switch (type) {
  case GRIB_TYPE_UNDEFINED:
  case GRIB_TYPE_STRING:
  case GRIB_TYPE_LABEL:
    {
      char value[MAX_VALUE_LENGTH];
      len = MAX_VALUE_LENGTH;
      bzero(value, len);
      if (grib_get_string(message->handle, name, value, &len) == GRIB_SUCCESS) {
	if (value[len-1] == '\0') len--;
	ret = rb_str_new(value, len);
      }
    }
    break;
  case GRIB_TYPE_BYTES:
    {
      unsigned char value[MAX_VALUE_LENGTH];
      len = MAX_VALUE_LENGTH;
      bzero(value, len);
      if (grib_get_bytes(message->handle, name, value, &len) == GRIB_SUCCESS)
	ret = rb_str_new((char*)value, len);
    }
    break;
  case GRIB_TYPE_LONG:
    {
      if (len == 1) {
	long l;
	if (rtype == Qnil) {
	  char value[MAX_VALUE_LENGTH];
	  len = MAX_VALUE_LENGTH;
	  bzero(value, len);
	  if (grib_get_string(message->handle, name, value, &len) == GRIB_SUCCESS) {
	    check_error(grib_get_long(message->handle, name, &l));
	    if (atol(value) == l)
	      ret = LONG2NUM(l);
	    else
	      ret = rb_str_new2(value);
	  }
	} else {
	  check_error(grib_get_long(message->handle, name, &l));
	  ret = LONG2NUM(l);
	}	  
      } else {
	int shape[1];
	struct NARRAY *nary;
	shape[0] = len;
	VALUE rnary = na_make_object(NA_LINT, 1, shape, cNArray);
	GetNArray(rnary, nary);
	if (grib_get_long_array(message->handle, name, (long*)nary->ptr, &len) == GRIB_SUCCESS)
	  ret = rnary;
      }
    }
    break;
  case GRIB_TYPE_DOUBLE:
    {
      if (len == 1) {
	double value;
	if (grib_get_double(message->handle, name, &value) == GRIB_SUCCESS)
	  ret = rb_float_new(value);
      } else {
	int shape[1];
	struct NARRAY *nary;
	shape[0] = len;
	VALUE rnary = na_make_object(NA_DFLOAT, 1, shape, cNArray);
	GetNArray(rnary, nary);
	if (grib_get_double_array(message->handle, name, (double*)nary->ptr, &len) == GRIB_SUCCESS)
	  ret = rnary;
      }
    }
    break;
  default:
    rb_raise(rb_eArgError, "type is invalid: %d", type);
  }
  return ret;
}

/*
  NumRu::GribMessage#get_data() -> [lon, lat, value]
*/
static VALUE
rg_message_get_data(VALUE self)
{
  rg_message *message;
  Data_Get_Struct(self, rg_message, message);
  int error;
  grib_iterator *iter = grib_iterator_new(message->handle, 0, &error);
  check_error(error);
  long np;
  check_error(grib_get_long(message->handle, "numberOfPoints", &np));
  double *lon, *lat, *value;
  VALUE na_lon, na_lat, na_value;
  struct NARRAY *nary;
  int shape[1];
  shape[0] = np;
  na_lon = na_make_object(NA_DFLOAT, 1, shape, cNArray);
  GetNArray(na_lon, nary);
  lon = (double*) nary->ptr;
  na_lat = na_make_object(NA_DFLOAT, 1, shape, cNArray);
  GetNArray(na_lat, nary);
  lat = (double*) nary->ptr;
  na_value = na_make_object(NA_DFLOAT, 1, shape, cNArray);
  GetNArray(na_value, nary);
  value = (double*) nary->ptr;
  int n = 0;
  double lo, la, val;
  while( grib_iterator_next(iter, &la, &lo, &val) ) {
    lat[n] = la;
    lon[n] = lo;
    value[n] = val;
    n++;
  }
  grib_iterator_delete(iter);
  return rb_ary_new3(3, na_lon, na_lat, na_value);
}



void Init_grib()
{
  rb_require("narray");

  id_init = rb_intern("init");

  VALUE mNumRu = rb_define_module("NumRu");
  cGrib = rb_define_class_under(mNumRu, "Grib", rb_cObject);
  cVar = rb_define_class_under(mNumRu, "GribVar", rb_cObject);
  cMessage = rb_define_class_under(mNumRu, "GribMessage", rb_cObject);

  grib_multi_support_on(0);

  //rb_define_singleton_method(cGrib, "open", rg_open, -1);
  rb_define_singleton_method(cGrib, "multi=", rg_multi, 1);

  rb_define_alloc_func(cGrib, file_alloc);
  rb_define_method(cGrib, "initialize", rg_file_initialize, -1);
  rb_define_method(cGrib, "close", rg_file_close, 0);
  rb_define_method(cGrib, "path", rg_file_path, 0);
  rb_define_method(cGrib, "var_names", rg_file_var_names, 0);
  rb_define_method(cGrib, "var", rg_file_var, 1);

  rb_define_alloc_func(cVar, var_alloc);
  rb_define_method(cVar, "file", rg_var_file, 0);
  rb_define_method(cVar, "name", rg_var_name, 0);
  rb_define_method(cVar, "get_messages", rg_var_get_messages, 0);


  rb_define_alloc_func(cMessage, message_alloc);
  rb_define_method(cMessage, "initialize", rg_message_initialize, 1);
  rb_define_method(cMessage, "get_keys", rg_message_get_keys, -1);
  rb_define_method(cMessage, "get_value", rg_message_get_value, -1);
  rb_define_method(cMessage, "get_data", rg_message_get_data, 0);

  rb_define_const(cGrib, "TYPE_UNDEFINED", INT2NUM(GRIB_TYPE_UNDEFINED));
  rb_define_const(cGrib, "TYPE_LONG", INT2NUM(GRIB_TYPE_LONG));
  rb_define_const(cGrib, "TYPE_DOUBLE", INT2NUM(GRIB_TYPE_DOUBLE));
  rb_define_const(cGrib, "TYPE_STRING", INT2NUM(GRIB_TYPE_STRING));
  rb_define_const(cGrib, "TYPE_BYTES", INT2NUM(GRIB_TYPE_BYTES));
  rb_define_const(cGrib, "TYPE_SECTION", INT2NUM(GRIB_TYPE_SECTION));
  rb_define_const(cGrib, "TYPE_LABEL", INT2NUM(GRIB_TYPE_LABEL));
  rb_define_const(cGrib, "TYPE_MISSING", INT2NUM(GRIB_TYPE_MISSING));

  rb_define_const(cGrib, "NEAREST_SAME_GRID", ULONG2NUM(GRIB_NEAREST_SAME_GRID));
  rb_define_const(cGrib, "NEAREST_SAME_DATA", ULONG2NUM(GRIB_NEAREST_SAME_DATA));
  rb_define_const(cGrib, "NEAREST_SAME_POINT", ULONG2NUM(GRIB_NEAREST_SAME_POINT));

  rb_define_const(cGrib, "KEYS_ITERATOR_ALL_KEYS", ULONG2NUM(GRIB_KEYS_ITERATOR_ALL_KEYS));
  rb_define_const(cGrib, "KEYS_ITERATOR_SKIP_READ_ONLY", ULONG2NUM(GRIB_KEYS_ITERATOR_SKIP_READ_ONLY));
}
