#ifndef CDI_INT_H
#define CDI_INT_H

// strdup() from string.h
#ifdef __STDC_ALLOC_LIB__
#define __STDC_WANT_LIB_EXT2__ 1
#else
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#ifdef HAVE_LIBFDB5
#include "cdi_fdb.h"
#endif

#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <sys/types.h>

#include "cdi.h"

#ifdef HAVE_LIBPTHREAD
#include <pthread.h>
extern pthread_mutex_t CDI_IO_Mutex;
#define CDI_IO_LOCK() pthread_mutex_lock(&CDI_IO_Mutex)
#define CDI_IO_UNLOCK() pthread_mutex_unlock(&CDI_IO_Mutex)
#else
#define CDI_IO_LOCK()
#define CDI_IO_UNLOCK()
#endif

// Base file types

#define CDI_FILETYPE_GRIB 100    // File type GRIB
#define CDI_FILETYPE_NETCDF 101  // File type NetCDF

// dummy use of unused parameters to silence compiler warnings
#ifndef UNUSED
#define UNUSED(x) (void) x
#endif

// clang-format off
#define FREE_AND_NULL(p) do { if (p) { Free(p); (p) = NULL; } } while(0)
// clang-format on

char *str_to_lower(char *str);
bool strStartsWith(const char *vstr, const char *cstr);

static inline bool
is_filetype_nc4(int filetype)
{
  return filetype == CDI_FILETYPE_NC4 || filetype == CDI_FILETYPE_NC4C || filetype == CDI_FILETYPE_NCZARR;
}

static inline bool
str_is_equal(const char *x, const char *y)
{
  return (*x == *y) && (strcmp(x, y) == 0);
}

#ifndef M_PI
#define M_PI 3.14159265358979323846 /* pi */
#endif

#ifndef ERROR_H
#include "error.h"
#endif
#ifndef _BASETIME_H
#include "basetime.h"
#endif
#ifndef JULIAN_DATE_H
#include "julian_date.h"
#endif
#ifndef TAXIS_H
#include "taxis.h"
#endif
#ifndef CDI_LIMITS_H
#include "cdi_limits.h"
#endif
#ifndef _SERVICE_H
#include "service.h"
#endif
#ifndef _EXTRA_H
#include "extra.h"
#endif
#ifndef _IEG_H
#include "ieg.h"
#endif
#ifndef RESOURCE_HANDLE_H
#include "resource_handle.h"
#endif

#define check_parg(arg) \
  if (arg == 0) Warning("Argument '" #arg "' not allocated!")

#ifdef __xlC__ /* performance problems on IBM */
#ifndef DBL_IS_NAN
#define DBL_IS_NAN(x) ((x) != (x))
#endif
#else
#ifndef DBL_IS_NAN
#if defined(HAVE_DECL_ISNAN)
#define DBL_IS_NAN(x) (isnan(x))
#elif defined(FP_NAN)
#define DBL_IS_NAN(x) (fpclassify(x) == FP_NAN)
#else
#define DBL_IS_NAN(x) ((x) != (x))
#endif
#endif
#endif

#ifndef DBL_IS_EQUAL
// #define DBL_IS_EQUAL(x,y) (!(x < y || y < x))
#define DBL_IS_EQUAL(x, y) (DBL_IS_NAN(x) || DBL_IS_NAN(y) ? (DBL_IS_NAN(x) && DBL_IS_NAN(y)) : !(x < y || y < x))
#endif

#ifndef IS_EQUAL
#define IS_NOT_EQUAL(x, y) (x < y || y < x)
#define IS_EQUAL(x, y) (!IS_NOT_EQUAL(x, y))
#endif

static inline bool
dbl_is_nan(double x)
{
#ifdef HAVE_DECL_ISNAN
  return isnan(x);
#elif defined(FP_NAN)
  return (fpclassify(x) == FP_NAN);
#else
  return x != x;
#endif
}

static inline bool
dbl_is_equal(double x, double y)
{
  return (dbl_is_nan(x) || dbl_is_nan(y)) ? dbl_is_nan(x) && dbl_is_nan(y) : !(x < y || y < x);
}

enum
{
  TYPE_REC,
  TYPE_VAR,
};

enum
{
  MEMTYPE_DOUBLE = 1,
  MEMTYPE_FLOAT,
};

typedef struct
{
  void *buffer;       // gribapi, cgribex
  size_t buffersize;  // gribapi, cgribex
  off_t position;     // file position
  int param;
  int ilevel;
  int vdate;
  int vtime;
  int gridID;
  int varID;
  int levelID;
  int prec;       // ext, srv
  void *objectp;  // pointer to ieg, ext, srv or cgribex objects
} Record;

// data structure specifying tile-related meta-data. structure contains "-1" if this is no tile-variable.
typedef struct
{
  int tileindex, totalno_of_tileattr_pairs, tileClassification, numberOfTiles, numberOfAttributes, attribute;
} var_tile_t;

typedef struct
{
  short perturbationNumber;
  short typeOfGeneratingProcess;
} VarScanKeys;

static inline void
varScanKeysInit(VarScanKeys *s)
{
  memset(s, 0, sizeof(VarScanKeys));
}

static inline bool
varScanKeysIsEqual(const VarScanKeys *s1, const VarScanKeys *s2)
{
  return memcmp(s1, s2, sizeof(VarScanKeys)) == 0;
}

typedef struct
{
  int levelID;
  short varID;
  short used;
} recinfo_t;

typedef struct
{
  off_t position;
  size_t size;
  size_t gridsize;
  int param;
  int ilevel;
  int ilevel2;
  short ltype;
  short tsteptype;
#ifdef HAVE_LIBGRIB
  int zip;
  VarScanKeys scanKeys;
  var_tile_t tiles;  // tile-related meta-data, currently for GRIB-API only.
#ifdef HAVE_LIBGRIB_API
  char varname[32];
#endif
#endif
#ifdef HAVE_LIBFDB5
  int fdbItemIndex;
#endif
} record_t;

typedef struct
{
  int *recIDs;  // IDs of non constant records
  recinfo_t *recinfo;
  record_t *records;
  int recordSize;   // number of allocated records
  int nrecs;        // number of used records
                    // tsID=0 nallrecs
                    // tsID>0 number of non constant records
  int nallrecs;     // number of all records
  int curRecID;     // current record ID
  int ncStepIndex;  // NetCDF timestep index
  off_t position;   // timestep file position
  taxis_t taxis;
  bool next;
} tsteps_t;

typedef struct
{
  int nlevs;
  int subtypeIndex;  // corresponding tile in subtype_t structure (subtype->self)
  int *recordID;     // record IDs: [nlevs]
  int *lindex;       // level index
} sleveltable_t;

typedef struct
{
  int ilev;
  int mlev;
  int ilevID;
  int mlevID;
} VCT;

#ifdef HAVE_LIBNETCDF
enum cdfIDIdx
{
  CDF_DIMID_E,  // 3rd dimID of cube sphere grid (len=6)
  CDF_DIMID_X,
  CDF_DIMID_Y,
  CDF_DIMID_RP,  // reducedPoints
  CDF_VARID_X,
  CDF_VARID_Y,
  CDF_VARID_RP,  // reducedPoints
  CDF_VARID_A,
  CDF_SIZE_NCID,
};

typedef struct
{
  void *buffer;
  size_t start[MAX_DIMENSIONS];
  size_t count[MAX_DIMENSIONS];
  size_t bufferSize;
  size_t cacheSize;
  size_t numSteps;
  size_t maxSteps;
  int numDims;
} CdfCache;

typedef struct
{
  int ncIdList[CDF_SIZE_NCID];
  int gridID;
  long start;
  long count;
} CdfGrid;

typedef struct
{
  int zaxisID;
  long start;
  long count;
} CdfZaxis;

typedef struct
{
  CdfGrid cdfGridList[MAX_GRIDS_PS];
  CdfZaxis cdfZaxisList[MAX_ZAXES_PS];
  size_t chunkSizeDimT;
  size_t chunkSizeDimZ;
  size_t ncDimLenList[MAX_DIMS_PS];
  int ncDimIdList[MAX_DIMS_PS];
  int zaxisIdList[MAX_ZAXES_PS];  // Warning: synchronous array to vlist_to_pointer(vlistID)->zaxisIDs
  int ncZvarIdList[MAX_ZAXES_PS];
  int ncNumDims;
  int complexFloatId;
  int complexDoubleId;
  VCT vct;
} CdfInfo;
#endif

typedef struct
{
#ifdef HAVE_LIBNETCDF
  CdfCache *cdfCache;
#endif
  sleveltable_t *recordTable;  // record IDs for each subtype
  int ncvarid;
  int subtypeSize;
  bool defmiss;  // true: if missval is defined in file
  bool isUsed;

  int gridID;
  int zaxisID;
  int tsteptype;  // TSTEP_*
  int subtypeID;  // subtype ID, e.g. for tile-related meta-data (currently for GRIB-API only).
} svarinfo_t;

typedef struct
{
  int self;
  int accesstype;  // TYPE_REC or TYPE_VAR
  int accessmode;
  int filetype;
  int byteorder;
  int fileID;
  int groupID;
  int filemode;
  int nrecs;  // number of records
  SizeType numvals;
  char *filename;
  Record *record;
  struct CdiQuery *query;
  svarinfo_t *vars;
  int nvars;  // number of variables
  int varsAllocated;
  int curTsID;   // current timestep ID
  int rtsteps;   // number of tsteps accessed
  long ntsteps;  // number of tsteps : only set if all records accessed
  int maxSteps;  // max. number of timesteps (needed for CDI_FILETYPE_NCZARR)
  tsteps_t *tsteps;
  int tstepsTableSize;
  int tstepsNextID;
  basetime_t basetime;
  int ncmode;
  int vlistID;
#ifdef HAVE_LIBNETCDF
  CdfInfo cdfInfo;
#endif
  long maxGlobalRecs;
  int globalatts;
  int localatts;
  bool unreduced;
  bool have_missval;
  int shuffle;
  // netcdf4/HDF5 filter
  char *filterSpec;

  int comptype;   // compression type
  int complevel;  // compression level
  bool sortname;
  bool lockIO;

  void *gribContainers;

  int numWorker;
  int nextGlobalRecId;
  int cachedTsID;
  void *jobs;
  void *jobManager;

  int protocol;
  void *protocolData;

#ifdef HAVE_LIBFDB5
  int fdbNumItems;
  fdbKeyValueEntry *fdbKeyValueList;
#endif
} stream_t;

// Length of optional keyword/value pair list
#define MAX_OPT_GRIB_ENTRIES 500

enum cdi_convention
{
  CDI_CONVENTION_ECHAM,
  CDI_CONVENTION_CF
};

// Data type specification for optional key/value pairs (GRIB)
typedef enum
{
  t_double = 0,
  t_int = 1,
  t_intarr = 2,
  t_doublearr = 3
} key_val_pair_datatype;

// Data structure holding optional key/value pairs for GRIB
typedef struct
{
  char *keyword;  // keyword string
  bool update;
  key_val_pair_datatype data_type;  // data type of this key/value pair
  double dbl_val;                   // double value (data_type == t_double)
  int int_val;                      // integer value (data_type == t_int)
  int *int_arr;                     // pointer to integer array (if data_type == t_intarr)
  double *dbl_arr;                  // pointer to double array (if data_type == t_doublearr)
  size_t arr_len;                   // length of array (if array type)
  int subtype_index;                // tile index for this key-value pair
} opt_key_val_pair_t;

// enum for differenciating between the different times that we handle
typedef enum
{
  kCdiTimeType_referenceTime,
  kCdiTimeType_startTime,
  kCdiTimeType_endTime
} CdiTimeType;

#define CDI_FILETYPE_UNDEF -1  // Unknown/not yet defined file type

extern int cdiDebugExt;
extern bool CDI_Debug;  // If set to 1, debuggig (default 0)
extern bool CDI_gribapi_debug;
extern bool CDI_gribapi_grib1;
extern double CDI_Default_Missval;
extern double CDI_Grid_Missval;
extern int CDI_Default_InstID;
extern int CDI_Default_ModelID;
extern int CDI_Default_TableID;
extern int cdiDefaultLeveltype;
extern int CDI_Default_Calendar;
// extern int cdiNcMissingValue;
extern int CDI_Netcdf_Chunksizehint;
extern int CDI_ChunkType;
extern bool CDI_Test;
extern bool CDI_Split_Ltype105;
extern bool CDI_Lock_IO;
extern bool CDI_Threadsafe;
extern bool cdiDataUnreduced;
extern bool cdiSortName;
extern bool cdiHaveMissval;
extern bool CDI_Ignore_Att_Coordinates;
extern bool CDI_Coordinates_Lon_Lat;
extern bool CDI_Ignore_Valid_Range;
extern bool CDI_Shuffle;
extern int CDI_Skip_Records;
extern const char *CDI_GRIB1_Template;
extern const char *CDI_GRIB2_Template;
extern int CDI_Convention;
extern int CDI_Inventory_Mode;
extern int CDI_Query_Abort;
extern int CDI_Version_Info;
extern int CDI_Convert_Cubesphere;
extern bool CDI_Read_Cell_Center;
extern bool CDI_Read_Cell_Corners;
extern int CDI_CMOR_Mode;
extern int CDI_Reduce_Dim;
extern int CDI_Reduce_DimX;
extern int CDI_Reduce_DimY;
extern int CDI_Reduce_DimZ;
extern int CDI_Reduce_DimT;
extern size_t CDI_Netcdf_Hdr_Pad;
extern bool CDI_Copy_ChunkSpec;
extern bool CDI_Remove_ChunkSpec;
extern bool CDI_Cache_Info;
extern bool CDI_Cache_NC4;
extern bool CDI_Cache_NCZ;
extern long CDI_CacheSize_In;
extern long CDI_CacheSize_Out;
extern long CDI_FilterSize_Min;
extern size_t CDI_CacheSize_Max;
extern bool CDI_Netcdf_Lazy_Grid_Load;
extern int STREAM_Debug;

extern int cdiPartabIntern;
extern const resOps streamOps;

static inline stream_t *
stream_to_pointer(int idx)
{
  return (stream_t *) reshGetVal(idx, &streamOps);
}

static inline void
stream_check_ptr(const char *caller, stream_t *streamptr)
{
  if (streamptr == NULL) Errorc("stream undefined!");
}

int streamInqFileID(int streamID);

void gridDefHasDims(int gridID, int hasdims);
int gridInqHasDims(int gridID);
int zaxisInqLevelID(int zaxisID, double level);

void streamCheckID(const char *caller, int streamID);

void streamDefineTaxis(int streamID);

int streamsNewEntry(int filetype);
void streamsInitEntry(int streamID);
void cdiStreamSetupVlist(stream_t *streamptr, int vlistID);
// default implementation of the overridable function
void cdiStreamSetupVlist_(stream_t *streamptr, int vlistID);
int stream_new_var(stream_t *streamptr, int gridID, int zaxisID, int tilesetID);

int tstepsNewEntry(stream_t *streamptr);

const char *strfiletype(int filetype);

void cdi_generate_vars(stream_t *streamptr);

void vlist_check_contents(int vlistID);

void cdi_create_records(stream_t *streamptr, int tsID, bool allocRecords);

void streamFCopyRecord(stream_t *streamptr2, stream_t *streamptr1, const char *container_name);

int recordNewEntry(stream_t *streamptr, int tsID);

void cdi_create_timesteps(size_t numTimesteps, stream_t *streamptr);

void recinfoInitEntry(recinfo_t *recinfo);

void cdiCheckZaxis(int zaxisID);

void stream_def_accesstype(stream_t *s, int type);

int getByteswap(int byteorder);

void cdiStreamGetIndexList(unsigned numIDs, int IDs[]);

void cdiInitialize(void);

char *cdiEscapeSpaces(const char *string);
char *cdiUnescapeSpaces(const char *string, const char **outStringEnd);

enum
{
  CDI_UNIT_PA = 1,
  CDI_UNIT_HPA,
  CDI_UNIT_MM,
  CDI_UNIT_CM,
  CDI_UNIT_DM,
  CDI_UNIT_M,
};

struct streamAssoc
{
  int streamID, vlistID;
};

struct streamAssoc streamUnpack(char *unpackBuffer, int unpackBufferSize, int *unpackBufferPos, int originNamespace, void *context);

int cdiStreamOpenDefaultDelegate(const char *filename, char filemode, int filetype, stream_t *streamptr,
                                 int recordBufIsToBeCreated);

int streamOpenID(const char *filename, char filemode, int filetype, int resH);

void cdiStreamDefVlist_(int streamID, int vlistID);

int cdiStreamWriteVar_(int streamID, int varID, int memtype, const void *data, SizeType numMissVals);

void cdiStreamWriteVarChunk_(int streamID, int varID, int memtype, const int rect[][2], const void *data, SizeType numMissVals);
void cdiStreamCloseDefaultDelegate(stream_t *streamptr, int recordBufIsToBeDeleted);

int cdiStreamDefTimestep_(stream_t *streamptr, int tsID);

void cdiStreamSync_(stream_t *streamptr);

const char *cdiUnitNamePtr(int cdi_unit);

enum
{
  // 8192 is known to work on most systems (4096 isn't on Alpha)
  commonPageSize = 8192,
};

size_t cdiGetPageSize(bool largePageAlign);

void zaxisGetIndexList(int nzaxis, int *zaxisIndexList);

// clang-format off

#ifdef __cplusplus
extern "C" {
#endif

// functions used in CDO !!!

void cdiDefTableID(int tableID);

void gridGenXvals(size_t xsize, double xfirst, double xlast, double xinc, double *xvals);
void gridGenYvals(int gridtype, size_t ysize, double yfirst, double ylast, double yinc, double *yvals);

static inline
void cdi_check_gridsize_int_limit(const char *format, SizeType gridsize)
{
  if (gridsize > INT_MAX) Error("%s format grid size (%zu) limit exceeded (%zu)!", format, gridsize, INT_MAX);
}

bool cdiFiletypeIsExse(int filetype);
int cdiBaseFiletype(int filetype);

void printDblsPrefixAutoBrk(FILE *fp, int dig, const char prefix[], size_t n, const double vals[]);


#ifdef __cplusplus
}
#endif

// clang-format on

#endif /* CDI_INT_H */
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
