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

#include <ctype.h>

#include "cdf.h"
#include "cdi.h"
#include "cdi_int.h"
#include "cdf_int.h"

const char *
cdfLibraryVersion(void)
{
#ifdef HAVE_LIBNETCDF
  return nc_inq_libvers();
#else
  return "library undefined";
#endif
}

#ifdef HAVE_H5GET_LIBVERSION
#ifdef __cplusplus
extern "C"
{
#endif
  int H5get_libversion(unsigned *, unsigned *, unsigned *);
#ifdef __cplusplus
}
#endif
#endif

const char *
hdfLibraryVersion(void)
{
#ifdef HAVE_H5GET_LIBVERSION
  static char hdf_libvers[256];
  static int linit = 0;
  if (!linit)
    {
      linit = 1;
      unsigned majnum, minnum, relnum;
      H5get_libversion(&majnum, &minnum, &relnum);
#ifdef HAVE_NC4HDF5_THREADSAFE
      snprintf(hdf_libvers, sizeof(hdf_libvers), "%u.%u.%u threadsafe", majnum, minnum, relnum);
#else
      snprintf(hdf_libvers, sizeof(hdf_libvers), "%u.%u.%u", majnum, minnum, relnum);
#endif
    }

  return hdf_libvers;
#else
  return "library undefined";
#endif
}

int CDF_Debug = 0; /* If set to 1, debugging           */

void
cdfDebug(int debug)
{
  CDF_Debug = debug;

  if (CDF_Debug) Message("debug level %d", debug);
}

#ifdef HAVE_LIBNETCDF
static void
cdfComment(int ncid)
{
  static char comment[256] = "Climate Data Interface version ";
  static bool init = false;

  if (!init)
    {
      init = true;
      const char *libvers = cdiLibraryVersion();

      if (!isdigit((int) *libvers))
        strcat(comment, "??");
      else
        strcat(comment, libvers);
      strcat(comment, " (https://mpimet.mpg.de/cdi)");
    }

  cdf_put_att_text(ncid, NC_GLOBAL, "CDI", strlen(comment), comment);
}
#endif

static bool
has_uri_scheme(const char *uri)
{
  char *pos = strstr(uri, "://");
  if (pos)
    {
      const int len = pos - uri;
      if (strncmp(uri, "file", len) == 0 || strncmp(uri, "https", len) == 0 || strncmp(uri, "s3", len) == 0) return true;
    }

  return false;
}

static int
cdfOpenFile(const char *filename, const char *mode, int *filetype)
{
  int ncid = -1;
#ifdef HAVE_LIBNETCDF
  const int fmode = tolower(*mode);
  int writemode = NC_CLOBBER;
  int readmode = NC_NOWRITE;

  if (filename == NULL)
    ncid = CDI_EINVAL;
  else
    {
      switch (fmode)
        {
        case 'r':
          {
            const int status = cdf_open(filename, readmode, &ncid);
            if (status > 0 && ncid < 0) ncid = CDI_ESYSTEM;
#ifdef HAVE_NETCDF4
            else
              {
                int format;
                (void) nc_inq_format(ncid, &format);
                if (format == NC_FORMAT_NETCDF4_CLASSIC) *filetype = CDI_FILETYPE_NC4C;
              }
#endif
          }
          break;
        case 'w':
#ifdef NC_64BIT_OFFSET
          if (*filetype == CDI_FILETYPE_NC2) writemode |= NC_64BIT_OFFSET;
#endif
#ifdef NC_64BIT_DATA
          if (*filetype == CDI_FILETYPE_NC5) writemode |= NC_64BIT_DATA;
#endif
#ifdef HAVE_NETCDF4
          if (*filetype == CDI_FILETYPE_NC4C) writemode |= (NC_NETCDF4 | NC_CLASSIC_MODEL);
          if (*filetype == CDI_FILETYPE_NC4) writemode |= NC_NETCDF4;
          if (*filetype == CDI_FILETYPE_NCZARR) writemode |= NC_NETCDF4;
#endif
          const bool hasUriScheme = has_uri_scheme(filename);

          if (*filetype == CDI_FILETYPE_NCZARR)
            {
              if (!hasUriScheme)
                {
                  fprintf(stderr, "URI scheme is missing in NCZarr path!\n");
                  return CDI_EINVAL;
                }

              cdf_create(filename, writemode, &ncid);
            }
          else
            {
              if (hasUriScheme) fprintf(stderr, "URI scheme defined for non NCZarr Data Model!\n");

              cdf__create(filename, writemode, &ncid);
            }

          if (CDI_Version_Info) cdfComment(ncid);
          cdf_put_att_text(ncid, NC_GLOBAL, "Conventions", 6, "CF-1.6");
          break;
        case 'a': cdf_open(filename, NC_WRITE, &ncid); break;
        default: ncid = CDI_EINVAL;
        }
    }
#endif

  return ncid;
}

int
cdfOpen(const char *filename, const char *mode, int filetype)
{
  int fileID = -1;
  bool open_file = true;

  if (CDF_Debug) Message("Open %s with mode %c", filename, *mode);

#ifdef HAVE_LIBNETCDF
#ifndef NC_64BIT_OFFSET
  if (filetype == CDI_FILETYPE_NC2) open_file = false;
#endif
#ifndef NC_64BIT_DATA
  if (filetype == CDI_FILETYPE_NC5) open_file = false;
#endif
#endif

  if (open_file)
    {
      fileID = cdfOpenFile(filename, mode, &filetype);

      if (CDF_Debug) Message("File %s opened with id %d", filename, fileID);
    }
  else
    {
      fileID = CDI_ELIBNAVAIL;
    }

  return fileID;
}

static int
cdf4CheckLibVersions(void)
{
  int status = 0;
#ifdef HAVE_NETCDF4
#ifdef HAVE_H5GET_LIBVERSION
  static int checked = 0;
  if (!checked)
    {
      checked = 1;
      unsigned majnum, minnum, relnum;

      sscanf(nc_inq_libvers(), "%u.%u.%u", &majnum, &minnum, &relnum);
      // printf("netCDF %u.%u.%u\n", majnum, minnum, relnum);
      const unsigned ncmaxver = 4 * 1000000 + 4 * 1000;
      const unsigned nclibver = majnum * 1000000 + minnum * 1000 + relnum;

      if (nclibver <= ncmaxver)
        {
          H5get_libversion(&majnum, &minnum, &relnum);
          const unsigned hdf5maxver = 1 * 1000000 + 10 * 1000;
          const unsigned hdf5libver = majnum * 1000000 + minnum * 1000 + relnum;

          if (hdf5libver >= hdf5maxver)
            {
              fprintf(stderr, "NetCDF library 4.4.0 or earlier, combined with libhdf5 1.10.0 or greater not supported!\n");
              status = 1;
            }
        }
    }
#endif
#endif
  return status;
}

int
cdf4Open(const char *filename, const char *mode, int *filetype)
{
  if (CDF_Debug) Message("Open %s with mode %c", filename, *mode);

#ifdef HAVE_NETCDF4
  if (cdf4CheckLibVersions() == 0)
    {
      const int fileID = cdfOpenFile(filename, mode, filetype);
      if (CDF_Debug) Message("File %s opened with id %d", filename, fileID);
      return fileID;
    }

  return CDI_EUFTYPE;
#else
  return CDI_ELIBNAVAIL;
#endif
}

static void
cdfCloseFile(int fileID)
{
#ifdef HAVE_LIBNETCDF
  cdf_close(fileID);
#endif
}

void
cdfClose(int fileID)
{
  cdfCloseFile(fileID);
}
/*
 * Local Variables:
 * c-file-style: "Java"
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * show-trailing-whitespace: t
 * require-trailing-newline: t
 * End:
 */
