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

#ifdef HAVE_LIBNETCDF

#include <stdio.h>
#include <string.h>

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

static int
cdfDefTimeBounds(int fileID, int nctimevarid, int nctimedimid, const char *taxis_name, taxis_t *taxis)
{
  int dims[2];

  dims[0] = nctimedimid;

  static const char bndsName[] = "bnds";
  if (nc_inq_dimid(fileID, bndsName, &dims[1]) != NC_NOERR) cdf_def_dim(fileID, bndsName, 2, &dims[1]);

  const char *bndsAttName, *bndsAttVal;
  size_t bndsAttValLen;
  char tmpstr[CDI_MAX_NAME];
  if (taxis->climatology)
    {
      static const char climatology_bndsName[] = "climatology_bnds", climatology_bndsAttName[] = "climatology";
      bndsAttName = climatology_bndsAttName;
      bndsAttValLen = sizeof(climatology_bndsName) - 1;
      bndsAttVal = climatology_bndsName;
    }
  else
    {
      size_t taxisnameLen = strlen(taxis_name);
      memcpy(tmpstr, taxis_name, taxisnameLen);
      tmpstr[taxisnameLen] = '_';
      memcpy(tmpstr + taxisnameLen + 1, bndsName, sizeof(bndsName));
      size_t tmpstrLen = taxisnameLen + sizeof(bndsName);
      static const char generic_bndsAttName[] = "bounds";
      bndsAttName = generic_bndsAttName;
      bndsAttValLen = tmpstrLen;
      bndsAttVal = tmpstr;
    }

  int time_bndsid = -1;
  cdf_def_var(fileID, bndsAttVal, NC_DOUBLE, 2, dims, &time_bndsid);
  cdf_put_att_text(fileID, nctimevarid, bndsAttName, bndsAttValLen, bndsAttVal);

  return time_bndsid;
}

static void
cdfDefTimeUnits(char *unitstr, taxis_t *taxis)
{
  if (taxis->units && taxis->units[0])
    {
      strcpy(unitstr, taxis->units);
    }
  else
    {
      unitstr[0] = 0;

      if (taxis->type == TAXIS_ABSOLUTE)
        {
          static const char *const unitstrfmt[3] = { "year as %Y.%f", "month as %Y%m.%f", "day as %Y%m%d.%f" };
          size_t fmtidx = (taxis->unit == TUNIT_YEAR ? 0 : (taxis->unit == TUNIT_MONTH ? 1 : 2));
          strcpy(unitstr, unitstrfmt[fmtidx]);
        }
      else
        {
          const int year = taxis->rDateTime.date.year;
          const int month = taxis->rDateTime.date.month;
          const int day = taxis->rDateTime.date.day;
          const int hour = taxis->rDateTime.time.hour;
          const int minute = taxis->rDateTime.time.minute;
          const int second = taxis->rDateTime.time.second;

          int timeunit = (taxis->unit != -1) ? taxis->unit : TUNIT_HOUR;
          if (timeunit == TUNIT_QUARTER)
            timeunit = TUNIT_MINUTE;
          else if (timeunit == TUNIT_30MINUTES)
            timeunit = TUNIT_MINUTE;
          else if (timeunit == TUNIT_3HOURS || timeunit == TUNIT_6HOURS || timeunit == TUNIT_12HOURS)
            timeunit = TUNIT_HOUR;

          sprintf(unitstr, "%s since %d-%d-%d %02d:%02d:%02d", tunitNamePtr(timeunit), year, month, day, hour, minute, second);
        }
    }
}

static void
cdfDefForecastTimeUnits(char *unitstr, int timeunit)
{
  unitstr[0] = 0;

  if (timeunit == -1)
    timeunit = TUNIT_HOUR;
  else if (timeunit == TUNIT_QUARTER)
    timeunit = TUNIT_MINUTE;
  else if (timeunit == TUNIT_30MINUTES)
    timeunit = TUNIT_MINUTE;
  else if (timeunit == TUNIT_3HOURS || timeunit == TUNIT_6HOURS || timeunit == TUNIT_12HOURS)
    timeunit = TUNIT_HOUR;

  strcpy(unitstr, tunitNamePtr(timeunit));
}

static void
cdfDefCalendar(int fileID, int ncvarid, int calendar)
{
  static const struct
  {
    int calCode;
    const char *calStr;
  } calTab[] = {
    { CALENDAR_STANDARD, "standard" }, { CALENDAR_GREGORIAN, "gregorian" }, { CALENDAR_PROLEPTIC, "proleptic_gregorian" },
    { CALENDAR_NONE, "none" },         { CALENDAR_360DAYS, "360_day" },     { CALENDAR_365DAYS, "365_day" },
    { CALENDAR_366DAYS, "366_day" },
  };
  enum
  {
    calTabSize = sizeof calTab / sizeof calTab[0]
  };

  for (size_t i = 0; i < calTabSize; ++i)
    if (calTab[i].calCode == calendar)
      {
        const char *calstr = calTab[i].calStr;
        size_t len = strlen(calstr);
        cdf_put_att_text(fileID, ncvarid, "calendar", len, calstr);
        break;
      }
}

void
cdfDefTime(stream_t *streamptr)
{
  static const char defaultTimeAxisName[] = "time";

  if (streamptr->basetime.ncvarid != CDI_UNDEFID) return;

  const int fileID = streamptr->fileID;

  if (streamptr->ncmode == 0) streamptr->ncmode = 1;
  if (streamptr->ncmode == 2) cdf_redef(fileID);

  taxis_t *taxis = &streamptr->tsteps[0].taxis;

  const char *taxisName = (taxis->name && taxis->name[0]) ? taxis->name : defaultTimeAxisName;

  size_t timeDimLen = NC_UNLIMITED;
  if (streamptr->filetype == CDI_FILETYPE_NCZARR)
    {
      if (streamptr->maxSteps == CDI_UNDEFID)
        fprintf(stderr, "Max. number of timesteps undefined for NCZarr!\n");
      else
        timeDimLen = streamptr->maxSteps;
    }

  int timeDimId;
  cdf_def_dim(fileID, taxisName, timeDimLen, &timeDimId);
  streamptr->basetime.ncdimid = timeDimId;

  const int datatype = taxis->datatype;
  const nc_type xtype = (datatype == CDI_DATATYPE_INT32) ? NC_INT : ((datatype == CDI_DATATYPE_FLT32) ? NC_FLOAT : NC_DOUBLE);

  int timeVarId;
  cdf_def_var(fileID, taxisName, xtype, 1, &timeDimId, &timeVarId);
  streamptr->basetime.ncvarid = timeVarId;

#ifdef HAVE_NETCDF4
  if (timeDimLen == NC_UNLIMITED && (streamptr->filetype == CDI_FILETYPE_NC4 || streamptr->filetype == CDI_FILETYPE_NC4C))
    {
      const size_t chunk = 512;
      cdf_def_var_chunking(fileID, timeVarId, NC_CHUNKED, &chunk);
    }
#endif

  static const char timeStr[] = "time";
  cdf_put_att_text(fileID, timeVarId, "standard_name", sizeof(timeStr) - 1, timeStr);

  if (taxis->longname && taxis->longname[0])
    cdf_put_att_text(fileID, timeVarId, "long_name", strlen(taxis->longname), taxis->longname);

  if (taxis->has_bounds) streamptr->basetime.ncvarboundsid = cdfDefTimeBounds(fileID, timeVarId, timeDimId, taxisName, taxis);

  char unitsStr[CDI_MAX_NAME];
  cdfDefTimeUnits(unitsStr, taxis);
  size_t len = strlen(unitsStr);
  if (len) cdf_put_att_text(fileID, timeVarId, "units", len, unitsStr);

  if (taxis->calendar != -1) cdfDefCalendar(fileID, timeVarId, taxis->calendar);

  if (taxis->type == TAXIS_FORECAST)
    {
      int leadtimeid;
      cdf_def_var(fileID, "leadtime", xtype, 1, &timeDimId, &leadtimeid);
      streamptr->basetime.leadtimeid = leadtimeid;

      static const char stdName[] = "forecast_period";
      cdf_put_att_text(fileID, leadtimeid, "standard_name", sizeof(stdName) - 1, stdName);

      static const char longName[] = "Time elapsed since the start of the forecast";
      cdf_put_att_text(fileID, leadtimeid, "long_name", sizeof(longName) - 1, longName);

      cdfDefForecastTimeUnits(unitsStr, taxis->fc_unit);
      len = strlen(unitsStr);
      if (len) cdf_put_att_text(fileID, leadtimeid, "units", len, unitsStr);
    }

  cdf_put_att_text(fileID, timeVarId, "axis", 1, "T");

  if (streamptr->ncmode == 2) cdf_enddef(fileID);
}

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