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

#include <stdbool.h>
#include <stdio.h>

#include "cdi.h"
#include "cksum.h"
#include "stream_cksum.h"
#include "dmemory.h"

const char *zaxisNamePtr(int zaxistype);

struct cksum_table *
cksum_stream(const char *fname, size_t *table_len)
{
  int nvars;
  uint32_t *checksum_state = NULL;
  enum directionZ
  {
    DIRECTION_DOWN = 1,
    DIRECTION_NONE,
    DIRECTION_UP
  };
  struct var_desc_t
  {
    int x, y, z;
    enum directionZ zDirection;
    int code;
    size_t chars;
  } *varDesc = NULL;
  size_t var_size_max_chars = 0;
  double *buf = NULL;
  struct cksum_table *file_vars = NULL;

  do {
    // Open the dataset
    int streamID = streamOpenRead(fname);
    if (streamID < 0)
    {
      fprintf(stderr, "Cannot open data input file %s: %s\n", fname, cdiStringError(streamID));
      nvars = -1;
      break;
    }

    // Get the variable list of the dataset
    int vlistID = streamInqVlist(streamID);
    int fileType = streamInqFiletype(streamID);
    bool isLegacyFile = fileType == CDI_FILETYPE_SRV || fileType == CDI_FILETYPE_EXT;
    nvars = vlistNvars(vlistID);
    int ngrids = vlistNumGrids(vlistID);
    int nzaxis = vlistNumZaxis(vlistID);
    if (nzaxis < 0 || ngrids < 0)
    {
      fprintf(stderr, "Error in grid/zaxis count query %d:%d\n", ngrids, nzaxis);
      nvars = -1;
      break;
    }
    checksum_state = (uint32_t *) Calloc((size_t) nvars, sizeof(checksum_state[0]));
    varDesc = (struct var_desc_t *) Malloc((size_t) nvars * sizeof(varDesc[0]));

    for (int varIdx = 0; varIdx < nvars; ++varIdx)
    {
      int grid = vlistInqVarGrid(vlistID, varIdx), gridType, varCode = vlistInqVarCode(vlistID, varIdx);
      varDesc[varIdx].code = varCode;
      int zaxisID = vlistInqVarZaxis(vlistID, varIdx);
      if (grid == CDI_UNDEFID || zaxisID == CDI_UNDEFID)
      {
        fputs("error in axis/grid inquiry\n", stderr);
        nvars = -1;
        break;
      }
      int zSize;
      if ((zSize = varDesc[varIdx].z = zaxisInqSize(zaxisID)) <= 0)
      {
        fputs("invalid Z-axis found\n", stderr);
        nvars = -1;
        break;
      }
      if (isLegacyFile)
        varDesc[varIdx].zDirection = DIRECTION_NONE;
      else if (zSize > 1)
      {
        double lev[2];
        for (int levIdx = 0; levIdx < 2; ++levIdx) lev[levIdx] = zaxisInqLevel(zaxisID, levIdx);
        int zaxistype = zaxisInqType(zaxisID);
        switch (zaxistype)
        {
          case ZAXIS_PRESSURE:
            if (lev[0] < lev[1])
              varDesc[varIdx].zDirection = DIRECTION_DOWN;
            else if (lev[1] < lev[0])
              varDesc[varIdx].zDirection = DIRECTION_UP;
            else
            {
              fprintf(stderr,
                      "unexpected level ordering on z-Axis for variable"
                      " code %d found: lev[0]=%g, lev[1]=%g\n",
                      varCode, lev[0], lev[1]);
              nvars = -1;
            }
            break;
          default:
            fprintf(stderr,
                    "unexpected type of z-Axis for variable"
                    " code %d found: %s\n",
                    varCode, zaxisNamePtr(zaxistype));
            nvars = -1;
        }
      }
      else
        varDesc[varIdx].zDirection = DIRECTION_NONE;
      if (nvars == -1) break;
      if ((gridType = gridInqType(grid)) != GRID_LONLAT && gridType != GRID_GENERIC && gridType != GRID_CURVILINEAR)
      {
        fprintf(stderr, "unexpected non-lonlat grid found: %d\n", gridType);
        nvars = -1;
        break;
      }
      if ((varDesc[varIdx].x = gridInqXsize(grid)) < 0)
      {
        fprintf(stderr, "invalid X-size found: %d\n", varDesc[varIdx].x);
        nvars = -1;
        break;
      }
      if (varDesc[varIdx].x == 0) varDesc[varIdx].x = 1;
      if ((varDesc[varIdx].y = gridInqYsize(grid)) < 0)
      {
        fprintf(stderr, "invalid Y-size found: %d\n", varDesc[varIdx].y);
        nvars = -1;
        break;
      }
      if (varDesc[varIdx].y == 0) varDesc[varIdx].y = 1;
      varDesc[varIdx].chars = (size_t) varDesc[varIdx].x * (size_t) varDesc[varIdx].y * (size_t) varDesc[varIdx].z * sizeof(buf[0]);
      if (var_size_max_chars < varDesc[varIdx].chars) var_size_max_chars = varDesc[varIdx].chars;
    }
    buf = (double *) Malloc(var_size_max_chars);

    if (nvars == -1) break;

    // Get the Time axis from the variable list
    int taxisID = vlistInqTaxis(vlistID);

    int tsID = 0;
    // Inquire the time step
    while (streamInqTimestep(streamID, tsID))
    {
      // Get the verification date and time
      int vdatetime[2] = { taxisInqVtime(taxisID), (int) taxisInqVdate(taxisID) };
      // Read var1 and var2
      for (int varIdx = 0; varIdx < nvars; ++varIdx)
      {
        SizeType nmiss;
        streamReadVar(streamID, varIdx, buf, &nmiss);
        memcrc_r(checksum_state + varIdx, (const unsigned char *) vdatetime, sizeof(vdatetime));
        if (varDesc[varIdx].zDirection == DIRECTION_UP || varDesc[varIdx].zDirection == DIRECTION_NONE)
          memcrc_r(checksum_state + varIdx, (const unsigned char *) buf, varDesc[varIdx].chars);
        else
        {
          size_t nlev = (size_t) varDesc[varIdx].z,
                 charsPerLev = (size_t) varDesc[varIdx].x * (size_t) varDesc[varIdx].y * sizeof(buf[0]);
          for (size_t lev = 0; lev < nlev; ++lev)
            memcrc_r(checksum_state + varIdx, (const unsigned char *) buf + (nlev - lev - 1) * charsPerLev, charsPerLev);
        }
      }
      ++tsID;
    }

    file_vars = (struct cksum_table *) Malloc((size_t) nvars * sizeof(file_vars[0]));
    for (int varIdx = 0; varIdx < nvars; ++varIdx)
    {
      file_vars[varIdx].code = varDesc[varIdx].code;
      file_vars[varIdx].cksum
          = memcrc_finish(checksum_state + varIdx, (off_t) ((varDesc[varIdx].chars + sizeof(int) * 2) * (size_t) tsID));
    }
    // Close the input stream
    streamClose(streamID);
  } while (0);

  // free resources
  free(checksum_state);
  free(varDesc);
  free(buf);
  *table_len = (size_t) nvars;

  return file_vars;
}

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