#if  defined  (HAVE_CONFIG_H)
#  include "config.h"
#endif

#define H5_USE_16_API

#if  defined  (HAVE_LIBHDF5)
#  include "hdf5.h"
#endif

#include <ctype.h>

#include "cdi.h"
#include "cdo.h"
#include "cdo_int.h"
#include "pstream.h"


#define  MAX_DSETS  1024

typedef struct {
  char *name;
  char *description;
  char *units;
  int dtype;
  int nx;
  int ny;
  int nz;
  int nt;
  int gridsize;
  int lscale;
  int loffset;
  int lmissval;
  double scale;
  double offset;
  double missval;
  double *array;
}
DSET_OBJ;

typedef struct {
  int nsets;
  int lgeoloc;
  int lregion;
  DSET_OBJ obj[MAX_DSETS];
}
DSETS;


#if  defined  (HAVE_LIBHDF5)
static
void print_filter(hid_t dset_id, char *varname)
{
  hid_t plist;
  H5Z_filter_t filter;
  unsigned int flags;
  int idx;
  unsigned int cd_values;
  int nfilter;
  size_t cd_nelmts = 1;
  size_t pnamelen = 64;
  char pname[64];

  /* get filter */
  plist = H5Dget_create_plist(dset_id);
  nfilter = H5Pget_nfilters(plist);

  for ( idx = 0; idx < nfilter; idx++ )
    {
      filter = H5Pget_filter(plist, idx, &flags, &cd_nelmts, &cd_values, 
			     pnamelen, pname);
      cdoPrint("Dataset %s: filter %d =  %s", varname, idx+1, pname);
    }

  H5Pclose(plist);
}
#endif


#define  NINT(x)   ((x) < 0 ? (int)((x)-0.5) : (int)((x)+0.5))


void get_grid_info(double c0, double re, int *nrxp, int *nryp,
		   double *r0p, double *s0p, double *cp)
{
  const double pi = M_PI;
  double git, phi, s90;
  double r0, s0, c;
  int nrx, nry;

  git=2.*pi*re*cos(pi/6.)/c0;
  /* number of longitude pixels */
  nrx=2*NINT(0.5*git);

  /* central index in longitude */
  r0=nrx/2+0.5;

  /* resolution in km */
  c=2.*pi*re*cos(30.*pi/180.)/nrx;

  phi=pi/2.;
  s90= re/c *sin(phi) / cos(30.*pi/180.);

  nry=floor(s90);
  /* central index in latitude */
  s0=nry+0.5;
  /* number of latitude pixels */
  nry=2*nry;

  *nrxp = nrx;
  *nryp = nry;
  *r0p  = r0;
  *s0p  = s0;
  *cp   = c;
}


double det_lon_atovs(double r, double r0, double lts, double c, double re)
{
  const double pi = M_PI;
  double xla;

  xla=(r-r0)*c/re/cos(lts*pi/180.); /* longitude */
  xla=180.*xla/pi;

  return (xla);
}


double det_lat_atovs(double s, double s0, double lts, double c, double re)
{
  const double pi = M_PI;
  double siphi;
  double phi;

  siphi=(s-s0)*c*cos(lts*pi/180.)/re;
  phi=180.*asin(siphi)/pi; /* latitude */

  return (phi);
}


int defLonLatGrid(int nx, int ny, double c0, double lts, double re)
{
  static char func[] = "defLonLatGrid";
  int gridID;
  int nrx, nry, i;
  double c;
  double r0, s0;
  double r, s;
  double xla, phi;
  double *xvals, *yvals, *xbounds, *ybounds;

  get_grid_info(c0, re, &nrx, &nry, &r0, &s0, &c);

  if ( nx != nrx || ny != nry )
    {
      printf("nrx=%d nry=%d\n", nrx, nry);
      return(-1);
    }

  xvals = (double *) malloc(nx*sizeof(double));
  yvals = (double *) malloc(ny*sizeof(double));
  xbounds = (double *) malloc(nx*2*sizeof(double));
  ybounds = (double *) malloc(nx*2*sizeof(double));

  for ( i = 0; i < nx; ++i )
    {
      r = i+1;
      xla = det_lon_atovs(r, r0, lts, c, re);
      xvals[i] = xla;
      xla = det_lon_atovs(r-0.5, r0, lts, c, re);
      xbounds[2*i] = xla;
      xla = det_lon_atovs(r+0.5, r0, lts, c, re);
      xbounds[2*i+1] = xla;
      /* printf("xla[%d]=%g\n", i, xla); */
    }

  for ( i = 0; i < ny; ++i )
    {
      s = (nry-i-1)+1;
      phi = det_lat_atovs(s, s0, lts, c, re);
      yvals[i] = phi;
      phi = det_lat_atovs(s-0.5, s0, lts, c, re);
      ybounds[2*i] = phi;
      phi = det_lat_atovs(s+0.5, s0, lts, c, re);
      ybounds[2*i+1] = phi;
      /* printf("phi[%d]=%g\n", i, phi); */
    }

  gridID = gridCreate(GRID_LONLAT, nx*ny);
  gridDefXsize(gridID, nx);
  gridDefYsize(gridID, ny);
  gridDefXvals(gridID, xvals);
  gridDefYvals(gridID, yvals);
  /*
  gridDefXbounds(gridID, xbounds);
  gridDefYbounds(gridID, ybounds);
  */
  free(xvals);
  free(yvals);
  free(xbounds);
  free(ybounds);

  return (gridID);
}


static
int defSinusoidalGrid(int nx, int ny, double xmin, double xmax, double ymin, double ymax, 
		      double dx, double dy, double p1, double p2, double p3, double p4)
{
  static char func[] = "defSinusoidalGrid";
  int gridID;
  int i;
  double *xvals, *yvals;

  xvals = (double *) malloc(nx*sizeof(double));
  yvals = (double *) malloc(ny*sizeof(double));

  for ( i = 0; i < nx; ++i )
    {
      xvals[i] = xmin + i*dx + dx/2;
      /* printf("x[%d]=%g\n", i, xvals[i]); */
    }

  for ( i = 0; i < ny; ++i )
    {
      yvals[i] = ymax - i*dx - dx/2;;
      /* printf("y[%d]=%g\n", i, yvals[i]); */
    }

  gridID = gridCreate(GRID_SINUSOIDAL, nx*ny);
  gridDefXsize(gridID, nx);
  gridDefYsize(gridID, ny);
  gridDefXvals(gridID, xvals);
  gridDefYvals(gridID, yvals);

  free(xvals);
  free(yvals);

  return (gridID);
}


static
int defLaeaGrid(int nx, int ny, double xmin, double xmax, double ymin, double ymax, 
		double dx, double dy, double a, double lon0, double lat0)
{
  static char func[] = "defLaeaGrid";
  int gridID;
  int i;
  double *xvals, *yvals;

  xvals = (double *) malloc(nx*sizeof(double));
  yvals = (double *) malloc(ny*sizeof(double));

  for ( i = 0; i < nx; ++i )
    {
      xvals[i] = xmin + i*dx + dx/2;
      /* printf("x[%d]=%g\n", i, xvals[i]); */
    }

  for ( i = 0; i < ny; ++i )
    {
      yvals[i] = ymax - i*dx - dx/2;;
      /* printf("y[%d]=%g\n", i, yvals[i]); */
    }

  gridID = gridCreate(GRID_LAEA, nx*ny);
  gridDefXsize(gridID, nx);
  gridDefYsize(gridID, ny);
  gridDefXvals(gridID, xvals);
  gridDefYvals(gridID, yvals);

  gridDefLaea(gridID, a, lon0, lat0);

  free(xvals);
  free(yvals);

  return (gridID);
}


static
int scan_pcs_def(char *pcs_def, char proj[128], double *a, double *lon0, double *lat0)
{
  char *pcs[64];
  int npcs = 0;
  int i;
  int len;
  int nfound = 0;

  strcpy(proj, "unknown");
  *a = 1;
  *lon0 = 0;
  *lat0 = 0;

  pcs[npcs++] = &pcs_def[0];
  len = (int)strlen(pcs_def);
  for ( i = 0; i < len; ++i )
    if ( pcs_def[i] == ',' && npcs < 64 )
      {
	pcs_def[i] = 0;
	pcs[npcs++] = &pcs_def[i+1];
      }

  for ( i = 0; i < npcs; ++i )
    {
      if ( strncmp(pcs[i], "proj=", 5) == 0 )
	{
	  pcs[i] += 5;
	  strcpy(proj, pcs[i]);
	  nfound++;
	}
      else if ( strncmp(pcs[i], "a=", 2) == 0 )
	{
	  pcs[i] += 2;
	  *a = atof(pcs[i]);
	  nfound++;
	}
      else if ( strncmp(pcs[i], "lon_0=", 6) == 0 )
	{
	  pcs[i] += 6;
	  *lon0 = atof(pcs[i]);
	  nfound++;
	}
      else if ( strncmp(pcs[i], "lat_0=", 6) == 0 )
	{
	  pcs[i] += 6;
	  *lat0 = atof(pcs[i]);
	  nfound++;
	}
    }

  return (nfound);
}


#if  defined  (HAVE_LIBHDF5)
static
int read_geolocation(hid_t loc_id, int nx, int ny)
{
  static char func[] = "read_geolocation";
  int gridID = -1;
  hid_t grp_id;
  hid_t proj_id, region_id;
  hid_t proj_tid, region_tid;
  hid_t str_tid, fltarr_tid;
  herr_t     status;
  hsize_t dims;
  int xsize, ysize;
  typedef struct proj_t {
    char name[64];
    char ellipsoid[64];
    float  parameter[10];
  } proj_t;
  typedef struct region_t {
    float  xmin;
    float  xmax;
    float  ymin;
    float  ymax;
    float  dx;
    float  dy;
  } region_t;

  proj_t proj;
  region_t region;

  if ( cdoVerbose ) cdoPrint("Read geolocation:");

  str_tid = H5Tcopy(H5T_C_S1);
  H5Tset_size(str_tid, 64);
  dims = 10;
  fltarr_tid = H5Tarray_create(H5T_NATIVE_FLOAT, 1, &dims, NULL);

  proj_tid = H5Tcreate(H5T_COMPOUND, sizeof(proj_t));
  H5Tinsert(proj_tid, "Projetion name", HOFFSET(proj_t, name), str_tid);
  H5Tinsert(proj_tid, "Reference ellipsoid", HOFFSET(proj_t, ellipsoid), str_tid);
  H5Tinsert(proj_tid, "Projection parameter", HOFFSET(proj_t, parameter), fltarr_tid);

  grp_id = H5Gopen(loc_id, "Geolocation");

  proj_id = H5Dopen(grp_id, "Projection");
  /*
  {
    hid_t tid;
    int nmem;
    int im;

    tid = H5Dget_type(proj_id);
    nmem = H5Tget_nmembers(tid);
    for ( im = 0; im < nmem; ++im )
      {
	printf("%d %s\n", im, H5Tget_member_name(tid, im));
      }
  }
  */
  status = H5Dread(proj_id, proj_tid, H5S_ALL, H5S_ALL, H5P_DEFAULT, &proj);

  H5Dclose(proj_id);
  H5Tclose(proj_tid);
  H5Tclose(str_tid);
  H5Tclose(fltarr_tid);

  if ( cdoVerbose )
    cdoPrint("  Projection: name=%s ellipsoid=%s parameter=%g %g %g %g",  
	     proj.name, proj.ellipsoid, 
	     proj.parameter[0], proj.parameter[1], proj.parameter[2], proj.parameter[3]);

  region_tid = H5Tcreate(H5T_COMPOUND, sizeof(region_t));
  H5Tinsert(region_tid, "xmin", HOFFSET(region_t, xmin), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "xmax", HOFFSET(region_t, xmax), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "ymin", HOFFSET(region_t, ymin), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "ymax", HOFFSET(region_t, ymax), H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "dx",   HOFFSET(region_t, dx),   H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "dy",   HOFFSET(region_t, dy),   H5T_NATIVE_FLOAT);

  region_id = H5Dopen(grp_id, "Region");

  status = H5Dread(region_id, region_tid, H5S_ALL, H5S_ALL, H5P_DEFAULT, &region);

  H5Dclose(region_id);
  H5Tclose(region_tid);

  if ( cdoVerbose ) 
    cdoPrint("  Region: xmin=%g xmax=%g ymin=%g ymax=%g dx=%g dy=%g",
	     region.xmin, region.xmax, region.ymin, region.ymax, region.dx, region.dy);

  H5Gclose(grp_id);

  /* check region */
  xsize = NINT((region.xmax-region.xmin)/region.dx);
  ysize = NINT((region.ymax-region.ymin)/region.dy);

  if ( cdoVerbose ) cdoPrint("  Size: xsize=%d  ysize=%d", xsize, ysize);

  if ( nx == xsize && ny == ysize && 
       strcmp(proj.name, "sinusoidal") == 0 && 
       strcmp(proj.ellipsoid, "WGS-84") == 0 )
    {
      gridID = defSinusoidalGrid(nx, ny, region.xmin, region.xmax, region.ymin, region.ymax, 
				 region.dx, region.dy,
				 proj.parameter[0], proj.parameter[1], 
				 proj.parameter[2], proj.parameter[3]);
    }
  else
    {
      if ( nx == 386 && ny == 162 )
	{
	  double c0 = 90;       /* nominal spatial resolution */
	  double lts = 30;      
	  double re = 6371.22;  /* Earth radius */
	  gridID = defLonLatGrid(nx, ny, c0, lts, re);
	}
    }

  return (gridID);
}
#endif


#if  defined  (HAVE_LIBHDF5)
static
int read_region(hid_t loc_id, int nx, int ny)
{
  static char func[] = "read_region";
  int gridID = -1;
  hsize_t		memb_size[1] = {4};
  hid_t grp_id;
  hid_t proj_id, region_id;
  hid_t proj_tid, region_tid;
  hid_t str64_tid, str128_tid, fltarr_tid;
  herr_t     status;
  hsize_t dims;
  int xsize, ysize;
  hid_t      file, dataset, datatype; /* Handles */
  typedef struct region_t {
    double area_extent[4];
    int xsize;
    int ysize;
    float xscale;
    float yscale;
    float lat_0;
    float lon_0;
    float lat_ts;
    char id[128];
    char name[128];
    char pcs_id[128];
    char pcs_def[128];
  } region_t;
  region_t region;
  int nfound;
  char proj[128];
  double a, lon0, lat0;
  double xmin, ymin, xmax, ymax;
  double dx, dy;

  if ( cdoVerbose ) cdoPrint("Read region:");

  /*
   * Create a data type for region
   */
  region_tid = H5Tcreate(H5T_COMPOUND, sizeof(region_t));
  dims = 4;
  fltarr_tid = H5Tarray_create(H5T_NATIVE_DOUBLE, 1, &dims, NULL);
  str64_tid = H5Tcopy(H5T_C_S1);
  H5Tset_size(str64_tid, 128);
  str128_tid = H5Tcopy(H5T_C_S1);
  H5Tset_size(str128_tid, 128);

  H5Tinsert(region_tid, "area_extent", HOFFSET(region_t, area_extent), fltarr_tid);
  H5Tinsert(region_tid, "xsize",   HOFFSET(region_t, xsize),   H5T_NATIVE_INT);
  H5Tinsert(region_tid, "ysize",   HOFFSET(region_t, ysize),   H5T_NATIVE_INT);
  H5Tinsert(region_tid, "xscale",  HOFFSET(region_t, xscale),  H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "yscale",  HOFFSET(region_t, yscale),  H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "lat_0",   HOFFSET(region_t, lat_0),   H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "lon_0",   HOFFSET(region_t, lon_0),   H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "lat_ts",  HOFFSET(region_t, lat_ts),  H5T_NATIVE_FLOAT);
  H5Tinsert(region_tid, "id",      HOFFSET(region_t, id),      str64_tid);
  H5Tinsert(region_tid, "name",    HOFFSET(region_t, name),    str64_tid);
  H5Tinsert(region_tid, "pcs_id",  HOFFSET(region_t, pcs_id),  str64_tid);
  H5Tinsert(region_tid, "pcs_def", HOFFSET(region_t, pcs_def), str128_tid);

  grp_id = H5Gopen(loc_id, "/");

  region_id = H5Dopen(grp_id, "region");
  /*
  {
    hid_t tid;
    int nmem;
    int im;

    tid = H5Dget_type(proj_id);
    nmem = H5Tget_nmembers(tid);
    for ( im = 0; im < nmem; ++im )
      {
	printf("%d %s\n", im, H5Tget_member_name(tid, im));
      }
  }
  */
  status = H5Dread(region_id, region_tid, H5S_ALL, H5S_ALL, H5P_DEFAULT, &region);

  if ( cdoVerbose )
    {
      printf("area_extent[0] = %g\n", region.area_extent[0]);
      printf("area_extent[1] = %g\n", region.area_extent[1]);
      printf("area_extent[2] = %g\n", region.area_extent[2]);
      printf("area_extent[3] = %g\n", region.area_extent[3]);
      printf("xsize = %d\n", region.xsize);
      printf("ysize = %d\n", region.ysize);
      printf("xscale = %g\n", region.xscale);
      printf("yscale = %g\n", region.yscale);
      printf("lat_0 = %g\n", region.lat_0);
      printf("lon_0 = %g\n", region.lon_0);
      printf("lat_ts = %g\n", region.lat_ts);
      printf("id = %s\n", region.id);
      printf("name = %s\n", region.name);
      printf("pcs_id = %s\n", region.pcs_id);
      printf("pcs_def = %s\n", region.pcs_def);
    }

  H5Dclose(region_id);
  H5Tclose(region_tid);
  H5Tclose(str64_tid);
  H5Tclose(str128_tid);
  H5Tclose(fltarr_tid);

  H5Gclose(grp_id);

  /* check region */

  nfound = scan_pcs_def(region.pcs_def, proj, &a, &lon0, &lat0);

  if ( cdoVerbose )
    {
      printf("proj = %s\n", proj);
      printf("a    = %g\n", a);
      printf("lon0 = %g\n", lon0);
      printf("lat0 = %g\n", lat0);
    }

  xmin =  region.area_extent[0];
  ymin =  region.area_extent[1];
  xmax =  region.area_extent[2];
  ymax =  region.area_extent[3];

  dx = (xmax-xmin) / nx;
  dy = (ymax-ymin) / ny;
  /*
  xsize = NINT((region.xmax-region.xmin)/region.dx);
  ysize = NINT((region.ymax-region.ymin)/region.dy);

  if ( cdoVerbose ) cdoPrint("  Size: xsize=%d  ysize=%d", xsize, ysize);
  */

  if ( nfound == 4 &&
       nx == region.xsize && ny == region.ysize && 
       strcmp(proj, "laea") == 0 )
    {
      gridID = defLaeaGrid(nx, ny, xmin, xmax, ymin, ymax, 
			   dx, dy, a, lon0, lat0);
    }

  return (gridID);
}
#endif


#if  defined  (HAVE_LIBHDF5)
static
void read_dataset(hid_t loc_id, const char *name, void *opdata)
{
  static char func[] = "read_dataset";
  hid_t dset_id, type_id;
  hid_t   dataspace;   
  hsize_t dims_out[9];  /* dataset dimensions           */
  herr_t  status;	/* Generic return value		*/
  hid_t   attr, atype, atype_mem;
  hid_t   native_type;
  int iattr;
  char attname[256];
  H5T_class_t  type_class;
  H5T_class_t atype_class;
  int     rank;
  int nx = 0, ny = 0, nz = 0, nt = 0;
  int gridsize, offset;
  double *array;
  double addoffset = 0, scalefactor = 1, missval = cdiInqMissval();
  int lmissval = 0;
  int nset;
  int i;
  int ftype = 0;
  int len;
  int laddoffset, lscalefactor;
  int dtype = DATATYPE_FLT32;
  char attstring[4096];     /* Buffer to read string attribute back */
  char varname[256];
  short *mask = NULL;
  double minval, maxval;
  int nmiss;
  int num_attrs;

  attstring[0] = 0;
  strcpy(varname, name);

  dset_id = H5Dopen(loc_id, varname);
  
  type_id = H5Dget_type(dset_id);  /* get datatype*/

  type_class = H5Tget_class(type_id);
  if ( type_class < 0 )
    {
      cdoAbort(" Invalid datatype for %s", varname);
    }
  /*
  else {
    if(type_class == H5T_INTEGER)
      puts("   Datatype is 'H5T_NATIVE_INTEGER'.\n");
    if(type_class == H5T_FLOAT)
      puts("   Datatype is 'H5T_NATIVE_FLOAT'.\n");
    if(type_class == H5T_STRING)
      puts("   Datatype is 'H5T_NATIVE_STRING'.\n");
    if(type_class == H5T_BITFIELD)
      puts("   Datatype is 'H5T_NATIVE_BITFIELD'.\n");
    if(type_class == H5T_OPAQUE)
      puts("   Datatype is 'H5T_NATIVE_OPAQUE'.\n");
    if(type_class == H5T_COMPOUND)
      puts("   Datatype is 'H5T_NATIVE_COMPOUND'.\n");
  }
  */
  native_type = H5Tget_native_type(type_id, H5T_DIR_ASCEND);
  if      ( H5Tequal(native_type, H5T_NATIVE_SCHAR)  > 0 ) {ftype=0; dtype = DATATYPE_INT8;}
  else if ( H5Tequal(native_type, H5T_NATIVE_UCHAR)  > 0 ) {ftype=0; dtype = DATATYPE_UINT8;}
  else if ( H5Tequal(native_type, H5T_NATIVE_SHORT)  > 0 ) {ftype=0; dtype = DATATYPE_INT16;}
  else if ( H5Tequal(native_type, H5T_NATIVE_USHORT) > 0 ) {ftype=0; dtype = DATATYPE_UINT16;}
  else if ( H5Tequal(native_type, H5T_NATIVE_INT)    > 0 ) {ftype=0; dtype = DATATYPE_INT32;}
  else if ( H5Tequal(native_type, H5T_NATIVE_UINT)   > 0 ) {ftype=0; dtype = DATATYPE_UINT32;}
  else if ( H5Tequal(native_type, H5T_NATIVE_FLOAT)  > 0 ) {ftype=1; dtype = DATATYPE_FLT32;}
  else if ( H5Tequal(native_type, H5T_NATIVE_DOUBLE) > 0 ) {ftype=1; dtype = DATATYPE_FLT64;}
  else
    {
      cdoWarning("Dataset %s skipped, unsupported native datatype!", varname);
      goto RETURN;
    }
  H5Tclose(native_type);

  dataspace = H5Dget_space(dset_id);    /* dataspace handle */
  rank      = H5Sget_simple_extent_ndims(dataspace);
  status    = H5Sget_simple_extent_dims(dataspace, dims_out, NULL);

  if ( rank == 2 )
    {
      nx = dims_out[1];
      ny = dims_out[0];
      nz = 1;
      nt = 1;
    }
  else if ( rank == 3 )
    {
      nx = dims_out[2];
      ny = dims_out[1];
      nz = 1;
      nt = dims_out[0];
    }
  else
    {
      cdoWarning("Dataset %s skipped, unsupported rank (=%d)!", varname, rank);
      goto RETURN;
    }


  len = (int) strlen(varname);
  if ( isdigit(varname[len-1]) )
    {
      nz = atoi(&varname[len-1]);
      varname[len-1] = 0;
    }

  gridsize = nx*ny;

  if ( nz == 1 )
    nset = ((DSETS *) opdata)->nsets;
  else
    {
      for ( nset = 0; nset < ((DSETS *) opdata)->nsets; ++nset )
	{
	  if ( strcmp(varname, ((DSETS *) opdata)->obj[nset].name) == 0 ) break;
	}

      if ( nset >= ((DSETS *) opdata)->nsets )
	cdoAbort("3D var %s not found!", varname);
    }

  if ( nset < MAX_DSETS )
    {
      if ( cdoVerbose ) print_filter(dset_id, varname);

      num_attrs = H5Aget_num_attrs(dset_id);
      for( i = 0; i < num_attrs; i++ )
	{
	  attr = H5Aopen_idx(dset_id, i);
	  atype = H5Aget_type(attr);
	  H5Aget_name(attr, sizeof(attname), attname);

	  if ( strcmp(attname, "CLASS") == 0 ||
	       strcmp(attname, "IMAGE_VERSION") == 0 ||
	       strcmp(attname, "PALETTE") == 0 ) continue;

	  atype_mem = H5Tget_native_type(atype, H5T_DIR_ASCEND);
	  atype_class = H5Tget_class(atype);

	  if ( strcmp(attname, "intercept") == 0 )
	    {
	      if ( atype_class == H5T_FLOAT )
		{
		  H5Aread(attr, H5T_NATIVE_DOUBLE, &addoffset);
		  laddoffset = 1;
		}
	      else if ( atype_class == H5T_INTEGER )
		{
		  H5Aread(attr, H5T_NATIVE_INT, &iattr);
		  addoffset = iattr;
		  laddoffset = 1;
		}
	      else
		cdoWarning("Attribute %s has unsupported data type!", attname);
	    }
	  else if ( strcmp(attname, "gain") == 0 )
	    {
	      if ( atype_class == H5T_FLOAT )
		{
		  H5Aread(attr, H5T_NATIVE_DOUBLE, &scalefactor);
		  lscalefactor = 1;
		}
	      else if ( atype_class == H5T_INTEGER )
		{
		  H5Aread(attr, H5T_NATIVE_INT, &iattr);
		  scalefactor = iattr;
		  lscalefactor = 1;
		}
	      else
		cdoWarning("Attribute %s has unsupported data type!", attname);
	    }
	  else if ( strcmp(attname, "no_data_value") == 0 ||
		    strcmp(attname, "nodata value") == 0  ||
		    strcmp(attname, "nodata") == 0 )
	    {
	      if ( atype_class == H5T_FLOAT )
		{
		  H5Aread(attr, H5T_NATIVE_DOUBLE, &missval);
		  lmissval = 1;
		}
	      else if ( atype_class == H5T_INTEGER )
		{
		  H5Aread(attr, H5T_NATIVE_INT, &iattr);
		  missval = iattr;
		  lmissval = 1;
		}
	      else
		cdoWarning("Attribute %s has unsupported data type!", attname);
	    }
	  else if ( strcmp(attname, "description") == 0 )
	    {
	      H5Aread(attr, atype_mem, attstring);
	      if ( ((DSETS *) opdata)->obj[nset].description )
		free(((DSETS *) opdata)->obj[nset].description);
	      ((DSETS *) opdata)->obj[nset].description = strdup(attstring);
	    }
	  /*
	  else if ( strcmp(attname, "title") == 0 )
	    {
	      H5Aread(attr, atype_mem, attstring);
	      if ( ((DSETS *) opdata)->obj[nset].description == NULL )
		((DSETS *) opdata)->obj[nset].description = strdup(attstring);
	    }
	  */
	  else if ( strcmp(attname, "unit") == 0 )
	    {
	      H5Aread(attr, atype_mem, attstring);
	      ((DSETS *) opdata)->obj[nset].units = strdup(attstring);
	    }

	  H5Tclose(atype_mem);
	  H5Aclose(attr);
	  H5Tclose(atype);
	}
      
      offset = gridsize*(nz-1);
      array  = ((DSETS *) opdata)->obj[nset].array;
      array  = (double *) realloc(array, gridsize*nz*nt*sizeof(double));
      ((DSETS *) opdata)->obj[nset].array    = array;
      array  = array+offset;

      mask = (short *) malloc(gridsize*nt*sizeof(short));
      memset(mask, 0, gridsize*nt*sizeof(short));

      if ( ftype )
	{
	  status = H5Dread(dset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, array);
	}
      else
	{
	  int *iarray, i;
	  iarray = (int *) malloc(gridsize*nt*sizeof(int));
	  status = H5Dread(dset_id, H5T_NATIVE_INT, H5S_ALL, H5S_ALL, H5P_DEFAULT, iarray);
	  for ( i = 0; i < gridsize*nt; ++i ) array[i] = iarray[i];
	  free(iarray);
	}

      ((DSETS *) opdata)->obj[nset].name     = strdup(varname);
      ((DSETS *) opdata)->obj[nset].nx       = nx;
      ((DSETS *) opdata)->obj[nset].ny       = ny;
      ((DSETS *) opdata)->obj[nset].nz       = nz;
      ((DSETS *) opdata)->obj[nset].nt       = nt;
      ((DSETS *) opdata)->obj[nset].gridsize = gridsize;

      ((DSETS *) opdata)->obj[nset].dtype    = dtype;

      ((DSETS *) opdata)->obj[nset].loffset  = laddoffset;
      ((DSETS *) opdata)->obj[nset].lscale   = lscalefactor;
      ((DSETS *) opdata)->obj[nset].lmissval = lmissval;
      ((DSETS *) opdata)->obj[nset].offset   = addoffset;
      ((DSETS *) opdata)->obj[nset].scale    = scalefactor;
      ((DSETS *) opdata)->obj[nset].missval  = missval;

      if ( nz == 1 ) ((DSETS *) opdata)->nsets++;

      nmiss  = 0;
 
      minval =  1e35;
      maxval = -1e35;

      for ( i = 0; i < gridsize; i++ )
	{
	  if ( array[i] < minval ) minval = array[i];
	  if ( array[i] > maxval ) maxval = array[i];
	}

      if ( cdoVerbose )
	cdoPrint("Dataset %s: dtype = %d  minval = %g  maxval = %g",
		 varname, dtype,  minval, maxval);

      if ( dtype == DATATYPE_UINT8 )
	{
	  if ( minval >= 0 && maxval <= 127 )
	    {
	      dtype = DATATYPE_INT8;
	      ((DSETS *) opdata)->obj[nset].dtype    = dtype;	      
	    }
	}
      else if ( dtype == DATATYPE_UINT16 )
	{
	  if ( minval >= 0 && maxval <= 32767 )
	    {
	      dtype = DATATYPE_INT16;
	      ((DSETS *) opdata)->obj[nset].dtype    = dtype;	      
	    }
	}

      laddoffset   = !DBL_IS_EQUAL(addoffset, 0);
      lscalefactor = !DBL_IS_EQUAL(scalefactor,  1);

      if ( laddoffset || lscalefactor )
	{	  
	  for ( i = 0; i < gridsize; i++ )
	    if ( !DBL_IS_EQUAL(array[i], missval) )
	      {
		mask[i] = 0;

		if ( lscalefactor ) array[i] *= scalefactor;
		if ( laddoffset   ) array[i] += addoffset;
	      }
	    else
	      {
		nmiss++;
		mask[i] = 1;
	      }
	}
	    
      minval =  1e35;
      maxval = -1e35;

      for ( i = 0; i < gridsize; i++ )
	if ( mask[i] == 0 )
	  {
	    if ( array[i] < minval ) minval = array[i];
	    if ( array[i] > maxval ) maxval = array[i];
	  }

      if ( cdoVerbose )
	cdoPrint("Dataset %s: dtype = %d  minval = %g  maxval = %g",
		 varname, dtype,  minval, maxval);

      if ( nmiss > 0 )
	{
	  if ( ! (missval < minval || missval > maxval) )
	    {
	      if ( DBL_IS_EQUAL(missval, 255) && dtype == DATATYPE_UINT8 )
		{
		  missval = -255;
		  dtype   = DATATYPE_INT16;
		  ((DSETS *) opdata)->obj[nset].dtype    = dtype;
		  ((DSETS *) opdata)->obj[nset].missval  = missval;
		  cdoPrint("Dataset %s: change missval to %g and datatype to INT16!",
			   varname, missval);

		  for ( i = 0; i < gridsize; i++ )
		    if ( mask[i] ) array[i] = missval;
		}
	      else
		cdoWarning(" Missval is inside of valid values!\n"
                           " Name: %s  Range: %g - %g  Missval: %g\n",
			   varname, minval, maxval, missval);
	    }
	}

      free(mask); 
      mask = NULL;
    }
  else
    {
      cdoWarning("Too many datasets (MAX = %d)!", MAX_DSETS);
      goto RETURN;
    }

  H5Sclose(dataspace);

 RETURN:

  H5Dclose(dset_id);
  H5Tclose(type_id);
}
#endif


#if  defined  (HAVE_LIBHDF5)
static herr_t
obj_info(hid_t loc_id, const char *name, void *opdata)
{
  H5G_obj_t obj_type;
  H5G_stat_t statbuf;

  /* avoid compiler warnings */
  loc_id = loc_id;
  opdata = opdata;

  H5Gget_objinfo(loc_id, name, FALSE, &statbuf);

  obj_type = statbuf.type;

  switch (obj_type) {
  case H5G_GROUP:
    if ( cdoVerbose ) cdoPrint(" Object with name %s is a group", name);
    if ( strcmp(name, "Data") == 0 )
      {
	H5Giterate(loc_id, name, NULL, obj_info, opdata);
      }
    else if ( strcmp(name, "Geolocation") == 0 )
      {
	((DSETS *) opdata)->lgeoloc = TRUE;
      }
    break;
  case H5G_DATASET: 
    if ( cdoVerbose ) cdoPrint(" Object with name %s is a dataset", name);
    if ( strstr(name, "PALETTE") )
      {
	if ( cdoVerbose ) cdoPrint("   Skip dataset: %s", name);
      }
    /*else if ( strstr(name, "egion") ) */
    else if ( strcmp(name, "region") == 0 )
      {
	((DSETS *) opdata)->lregion = TRUE;
      }
    else
      {
	if ( cdoVerbose ) cdoPrint("   Read dataset: %s", name);
	read_dataset(loc_id, name, opdata);
      }
    break;
  case H5G_TYPE: 
    if ( cdoVerbose ) cdoPrint(" Object with name %s is a named datatype", name);
    break;
  default:
    cdoAbort(" Unable to identify an object %s", name);
  }

  return 0;
}
#endif


#if  defined  (HAVE_LIBHDF5)
void get_global_att(hid_t file_id, int vlistID)
{
  hid_t   attr, atype, atype_mem, obj_id, grp_id = -1;
  char attname[256];
  H5T_class_t  type_class;
  int attint;
  double attflt;
  int i, pos;
  int num_attrs;
  char attstring[4096];     /* Buffer to read string attribute back */

  attstring[0] = 0;

  obj_id = H5Gopen(file_id, "/");

  num_attrs = H5Aget_num_attrs(obj_id);
  if ( num_attrs == 0 )
    {
      grp_id = H5Gopen(obj_id, "Metadata");

      if ( grp_id < 0 ) return;

      obj_id = grp_id;

      num_attrs = H5Aget_num_attrs(obj_id);
    }

  for( i = 0; i < num_attrs; i++ )
    {
      attr = H5Aopen_idx(obj_id, i);
      atype = H5Aget_type(attr);
      H5Aget_name(attr, sizeof(attname), attname);

      /* remove illegal characters */
      for ( pos = 0; pos < (int)strlen(attname); ++pos )
	if ( attname[pos] == '&' ) attname[pos] = '_';

      atype_mem = H5Tget_native_type(atype, H5T_DIR_ASCEND);
      type_class = H5Tget_class(atype);
      if ( type_class == H5T_STRING )
	{
	  H5Aread(attr, atype_mem, attstring);
	  vlistDefAttTxt(vlistID, CDI_GLOBAL, attname, (int)strlen(attstring), attstring);
	} 
      else if ( type_class == H5T_INTEGER )
	{
	  H5Aread(attr, H5T_NATIVE_INT, &attint);
	  vlistDefAttInt(vlistID, CDI_GLOBAL, attname, 1, &attint);
	} 
      else if ( type_class == H5T_FLOAT )
	{
	  H5Aread(attr, H5T_NATIVE_DOUBLE, &attflt);
	  vlistDefAttFlt(vlistID, CDI_GLOBAL, attname, 1, &attflt);
	} 
      H5Tclose(atype_mem);
      H5Aclose(attr);
      H5Tclose(atype);
    }

  if ( grp_id >= 0 ) H5Gclose(grp_id);	

}
#endif


void dsets_init(DSETS *dsets)
{
  int i;

  dsets->nsets = 0;
  dsets->lgeoloc = 0;
  dsets->lregion = 0;

  for ( i = 0; i < MAX_DSETS; ++i )
    {
      dsets->obj[i].nx          = 0;
      dsets->obj[i].ny          = 0;
      dsets->obj[i].nz          = 0;
      dsets->obj[i].name        = NULL;
      dsets->obj[i].description = NULL;
      dsets->obj[i].units       = NULL;
      dsets->obj[i].dtype       = cdoDefaultDataType;
      dsets->obj[i].lscale      = 0;
      dsets->obj[i].loffset     = 0;
      dsets->obj[i].lmissval    = 0;
      dsets->obj[i].missval     = cdiInqMissval();
      dsets->obj[i].array       = NULL;   
    }
}


void *Importcmsaf(void *argument)
{
#if  defined  (HAVE_LIBHDF5)
  static char func[] = "Importcmsaf";
  int streamID;
  int gridID = -1, zaxisID, taxisID, vlistID;
  int i, offset;
  int nmiss;
  int ivar;
  int varID, levelID, tsID;
  int nx, ny, nz, nt, gridsize;
  double *array;
  double missval, minval, maxval;
  hid_t	  file_id;	/* HDF5 File ID	        	*/
  herr_t  status;	/* Generic return value		*/
  DSETS dsets;

  cdoInitialize(argument);

  dsets_init(&dsets);

  /* Open an existing file. */
  file_id = H5Fopen(cdoStreamName(0), H5F_ACC_RDONLY, H5P_DEFAULT);

  /* cmsaf_type = get_cmsaf_type(file_id); */

  H5Giterate(file_id, "/", NULL, obj_info, (void *) &dsets);

  if ( dsets.nsets == 0 ) cdoAbort("No dataset found!");

  gridsize = dsets.obj[0].gridsize;
  nx = dsets.obj[0].nx;
  ny = dsets.obj[0].ny;
  nz = dsets.obj[0].nz;
  nt = dsets.obj[0].nt;

  if ( cdoVerbose )
    for ( ivar = 0; ivar < dsets.nsets; ++ivar )
      cdoPrint(" Var %d %-20s %dx%d nlev = %d", ivar, dsets.obj[ivar].name, nx, ny, nz);

  for ( ivar = 1; ivar < dsets.nsets; ++ivar )
    {
      if ( nx != dsets.obj[0].nx || ny != dsets.obj[0].ny )
	cdoAbort("Gridsize must not change!");
      if ( nz != dsets.obj[0].nz )
	cdoAbort("Number of levels must not change!");
    }

  if ( dsets.lgeoloc )
    {
      gridID = read_geolocation(file_id, nx, ny);
    }
  else if ( dsets.lregion )
    {
      gridID = read_region(file_id, nx, ny);
    }
  
  if ( gridID == -1 )
    {
      gridID = gridCreate(GRID_GENERIC, gridsize);
      gridDefXsize(gridID, nx);
      gridDefYsize(gridID, ny);
    }

  if ( nz == 1 )
    zaxisID = zaxisCreate(ZAXIS_SURFACE, 1);
  else
    {
      double *levels;
      levels = (double *) malloc(nz*sizeof(double));
      for ( i = 0; i < nz; ++i ) levels[i] = i+1;
      zaxisID = zaxisCreate(ZAXIS_GENERIC, nz);
      zaxisDefLevels(zaxisID, levels);
      free(levels);
    }

  vlistID = vlistCreate();

  taxisID = taxisCreate(TAXIS_ABSOLUTE);
  vlistDefTaxis(vlistID, taxisID);

  for ( ivar = 0; ivar < dsets.nsets; ++ivar )
    {
      if ( dsets.obj[ivar].nt > 1 ) 
	varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARIABLE);
      else
	varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_CONSTANT);

      vlistDefVarName(vlistID, varID,  dsets.obj[ivar].name);
      if ( dsets.obj[ivar].description )
	vlistDefVarLongname(vlistID, varID,  dsets.obj[ivar].description);
      if ( dsets.obj[ivar].units )
	vlistDefVarUnits(vlistID, varID,  dsets.obj[ivar].units);
	
      /*
      vlistDefVarUnits(vlistID, varID, units[i]);
      */
      vlistDefVarDatatype(vlistID, varID, dsets.obj[ivar].dtype);
      if ( dsets.obj[ivar].lmissval )
	vlistDefVarMissval(vlistID, varID, dsets.obj[ivar].missval);
      if ( dsets.obj[ivar].lscale )
	vlistDefVarScalefactor(vlistID, varID, dsets.obj[ivar].scale);
      if ( dsets.obj[ivar].loffset )
	vlistDefVarAddoffset(vlistID, varID, dsets.obj[ivar].offset);
    }

  get_global_att(file_id, vlistID);

  streamID = streamOpenWrite(cdoStreamName(1), cdoFiletype());
  if ( streamID < 0 ) cdiError(streamID, "Open failed on %s", cdoStreamName(1));

  streamDefVlist(streamID, vlistID);

  for ( tsID = 0; tsID < nt; ++tsID )
    {
      taxisDefVdate(taxisID, 0);
      taxisDefVtime(taxisID, tsID*100);
      streamDefTimestep(streamID, tsID);

      for ( ivar = 0; ivar < dsets.nsets; ++ivar )
	{
	  varID   = ivar;
	  
	  gridsize = dsets.obj[ivar].gridsize;
	  missval  = dsets.obj[ivar].missval;

	  for ( levelID = 0; levelID < nz; ++levelID )
	    {
	      offset = gridsize*levelID;
	      if ( nz == 1 ) offset = gridsize*tsID;
	      array  = dsets.obj[ivar].array+offset;
	      
	      nmiss  = 0;
	      minval =  1e35;
	      maxval = -1e35;

	      for ( i = 0; i < gridsize; i++ )
		{
		  if ( !DBL_IS_EQUAL(array[i], missval) )
		    {
		      if ( array[i] < minval ) minval = array[i];
		      if ( array[i] > maxval ) maxval = array[i];
		    }
		}

	      for ( i = 0; i < gridsize; i++ )
		if ( DBL_IS_EQUAL(array[i], missval) ) nmiss++;

	      if ( cdoVerbose )
		cdoPrint(" Write var %d,  level %d, nmiss %d, missval %g, minval %g, maxval %g",
			 varID, levelID, nmiss, missval, minval, maxval);
	      /*
		if ( ! (missval < minval || missval > maxval) )
		cdoWarning(" Missval is inside of valid values! Name: %s  Range: %g - %g  Missval: %g\n",
		dsets.obj[ivar].name, minval, maxval, missval);
	      */
	      streamDefRecord(streamID,  varID,  levelID);
	      streamWriteRecord(streamID, array, nmiss);
	    }
	}
    }

  /* Close file */
  status = H5Fclose(file_id);

  processDefVarNum(vlistNvars(vlistID), streamID);

  streamClose(streamID);

  vlistDestroy(vlistID);
  gridDestroy(gridID);
  zaxisDestroy(zaxisID);
  taxisDestroy(taxisID);

  for ( ivar = 0; ivar < dsets.nsets; ++ivar )
    free(dsets.obj[ivar].array);

  cdoFinish();
#else
  cdoWarning("HDF5 support not compiled in!");
#endif

  return (0);
}
