/*
  This file is part of CDO. CDO is a collection of Operators to
  manipulate and analyse Climate model Data.

  Copyright (C) 2003-2018 Uwe Schulzweida, <uwe.schulzweida AT mpimet.mpg.de>
  See COPYING file for copying and redistribution conditions.

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 2 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
*/

#include <cdi.h>
#include "cdi_uuid.h"
#include "cdo_int.h"
#include "griddes.h"

#define MAX_LINE_LEN 65536

void
cdo_read_field(const char *name, char *pline, int size, double *field, int *lineno, FILE *fp, const char *dname)
{
  char line[MAX_LINE_LEN];
  double fval;
  char *endptr;
  for (int i = 0; i < size; i++)
    {
      endptr = pline;
      fval = strtod(pline, &endptr);
      if (pline == endptr)
        {
          (*lineno)++;
          if (!readline(fp, line, MAX_LINE_LEN)) cdoAbort("Incomplete command: >%s< (line: %d file: %s)", name, *lineno, dname);
          pline = line;
          fval = strtod(pline, &endptr);
        }
      field[i] = fval;
      pline = endptr;
    }
}

struct kvmap_t
{
  keyValues_t *kv;
  bool isValid;
};

static void
grid_read_data(size_t ikv, size_t nkv, kvmap_t *kvmap, GridDesciption &grid, size_t *iproj, size_t *igmap, const char *dname)
{
  char uuidStr[256];

  for (size_t ik = ikv; ik < nkv; ++ik)
    {
      if (!kvmap[ik].isValid) continue;

      keyValues_t *kv = kvmap[ik].kv;
      const char *key = kv->key;
      size_t nvalues = kv->nvalues;
      const char *value = (kv->nvalues > 0) ? kv->values[0] : NULL;
      // bool lv1 = (kv->nvalues == 1);

      // printf("%s = ", key); if ( value  ) printf("%s", kv->value);
      // printf("\n");

      if (STR_IS_EQ(key, "gridtype"))
        {
          const char *gridtype = parameter2word(value);

          if (grid.type != CDI_UNDEFID)
            {
              if (STR_IS_EQ(gridtype, "projection")) *iproj = ik;
              return;
            }

          // clang-format off
          if      (STR_IS_EQ(gridtype, "lonlat"))           grid.type = GRID_LONLAT;
          else if (STR_IS_EQ(gridtype, "latlon"))           grid.type = GRID_LONLAT;
          else if (STR_IS_EQ(gridtype, "gaussian"))         grid.type = GRID_GAUSSIAN;
          else if (STR_IS_EQ(gridtype, "gaussian_reduced")) grid.type = GRID_GAUSSIAN_REDUCED;
          else if (STR_IS_EQ(gridtype, "curvilinear"))      grid.type = GRID_CURVILINEAR;
          else if (STR_IS_EQ(gridtype, "unstructured"))     grid.type = GRID_UNSTRUCTURED;
          else if (STR_IS_EQ(gridtype, "cell"))             grid.type = GRID_UNSTRUCTURED;
          else if (STR_IS_EQ(gridtype, "spectral"))         grid.type = GRID_SPECTRAL;
          else if (STR_IS_EQ(gridtype, "gme"))              grid.type = GRID_GME;
          else if (STR_IS_EQ(gridtype, "projection"))       grid.type = GRID_PROJECTION;
          else if (STR_IS_EQ(gridtype, "generic"))          grid.type = GRID_GENERIC;
          else cdoAbort("Invalid gridtype: %s (grid description file: %s)", gridtype, dname);
          // clang-format on

          if (grid.type == GRID_LONLAT || grid.type == GRID_GAUSSIAN)
            grid.nvertex = 2;
          else if (grid.type == GRID_CURVILINEAR)
            grid.nvertex = 4;
        }
      else if (STR_IS_EQ(key, "datatype"))
        {
          const char *datatype = parameter2word(value);

          if (STR_IS_EQ(datatype, "double"))
            grid.datatype = CDI_DATATYPE_FLT64;
          else if (STR_IS_EQ(datatype, "float"))
            grid.datatype = CDI_DATATYPE_FLT32;
          else
            cdoAbort("Invalid datatype: %s (zaxis description file: %s)", datatype, dname);
        }
      else if (STR_IS_EQ(key, "gridsize"))
        grid.size = parameter2sizet(value);
      else if (STR_IS_EQ(key, "xsize"))
        grid.xsize = parameter2sizet(value);
      else if (STR_IS_EQ(key, "nlon"))
        grid.xsize = parameter2sizet(value);
      else if (STR_IS_EQ(key, "ysize"))
        grid.ysize = parameter2sizet(value);
      else if (STR_IS_EQ(key, "nlat"))
        grid.ysize = parameter2sizet(value);
      else if (STR_IS_EQ(key, "truncation"))
        grid.ntr = parameter2int(value);
      else if (STR_IS_EQ(key, "np"))
        grid.np = parameter2int(value);
      else if (STR_IS_EQ(key, "complexpacking"))
        grid.lcomplex = parameter2int(value);
      else if (STR_IS_EQ(key, "nvertex"))
        grid.nvertex = parameter2int(value);
      else if (STR_IS_EQ(key, "ni"))
        {
          grid.ni = parameter2int(value);
          grid.nd = 10;
        }
      else if (STR_IS_EQ(key, "position"))
        grid.position = parameter2int(value);
      else if (STR_IS_EQ(key, "number"))
        grid.number = parameter2int(value);
      else if (STR_IS_EQ(key, "scanningMode"))
        {
          int scmode = parameter2int(value);
          if ((scmode == 0) || (scmode == 64) || (scmode == 96))
            {
              grid.scanningMode = scmode;  // -1: not used; allowed modes: <0,
                                           // 64, 96>; Default is 64
            }
          else
            {
              cdoWarning("Warning: %d not in allowed modes: <0, 64, 96>; Using default: 64\n", scmode);
              grid.scanningMode = 64;
            }
        }
      else if (STR_IS_EQ(key, "xname"))
        strcpy(grid.xname, parameter2word(value));
      else if (STR_IS_EQ(key, "yname"))
        strcpy(grid.yname, parameter2word(value));
      else if (STR_IS_EQ(key, "xdimname"))
        strcpy(grid.xdimname, parameter2word(value));
      else if (STR_IS_EQ(key, "ydimname"))
        strcpy(grid.ydimname, parameter2word(value));
      else if (STR_IS_EQ(key, "vdimname"))
        strcpy(grid.vdimname, parameter2word(value));
      else if (STR_IS_EQ(key, "xlongname"))
        strcpy(grid.xlongname, value);
      else if (STR_IS_EQ(key, "ylongname"))
        strcpy(grid.ylongname, value);
      else if (STR_IS_EQ(key, "xunits"))
        strcpy(grid.xunits, value);
      else if (STR_IS_EQ(key, "yunits"))
        strcpy(grid.yunits, value);
      else if (STR_IS_EQ(key, "path"))
        strcpy(grid.path, value);
      else if (STR_IS_EQ(key, "uuid"))
        {
          strcpy(uuidStr, value);
          cdiStr2UUID(uuidStr, grid.uuid);
        }
      else if (STR_IS_EQ(key, "xfirst"))
        {
          grid.xfirst = parameter2double(value);
          grid.def_xfirst = true;
        }
      else if (STR_IS_EQ(key, "yfirst"))
        {
          grid.yfirst = parameter2double(value);
          grid.def_yfirst = true;
        }
      else if (STR_IS_EQ(key, "xlast"))
        {
          grid.xlast = parameter2double(value);
          grid.def_xlast = true;
        }
      else if (STR_IS_EQ(key, "ylast"))
        {
          grid.ylast = parameter2double(value);
          grid.def_ylast = true;
        }
      else if (STR_IS_EQ(key, "xinc"))
        {
          grid.xinc = parameter2double(value);
          grid.def_xinc = true;
        }
      else if (STR_IS_EQ(key, "yinc"))
        {
          grid.yinc = parameter2double(value);
          grid.def_yinc = true;
        }
      else if (STR_IS_EQ(key, "a"))
        grid.a = parameter2double(value);
      else if (STR_IS_EQ(key, "uvRelativeToGrid"))
        grid.uvRelativeToGrid = parameter2bool(value);
      else if (STR_IS_EQ(key, "xvals"))
        {
          size_t size = (grid.type == GRID_CURVILINEAR || grid.type == GRID_UNSTRUCTURED) ? grid.size : grid.xsize;
          if (size == 0) cdoAbort("xsize or gridsize undefined (grid description file: %s)!", dname);
          if (size != nvalues)
            cdoAbort("xsize=%zu and size of xvals=%zu differ (grid description file: %s)!", nvalues, size, dname);

          grid.xvals.resize(size);
          for (size_t i = 0; i < size; ++i) grid.xvals[i] = parameter2double(kv->values[i]);
        }
      else if (STR_IS_EQ(key, "yvals"))
        {
          size_t size = (grid.type == GRID_CURVILINEAR || grid.type == GRID_UNSTRUCTURED) ? grid.size : grid.ysize;
          if (size == 0) cdoAbort("ysize or gridsize undefined (grid description file: %s)!", dname);
          if (size != nvalues)
            cdoAbort("ysize=%zu and size of yvals=%zu differ (grid description file: %s)!", nvalues, size, dname);

          grid.yvals.resize(size);
          for (size_t i = 0; i < size; ++i) grid.yvals[i] = parameter2double(kv->values[i]);
        }
      else if (STR_IS_EQ(key, "xbounds"))
        {
          size_t size = (grid.type == GRID_CURVILINEAR || grid.type == GRID_UNSTRUCTURED) ? grid.size : grid.xsize;
          if (size == 0) cdoAbort("xsize or gridsize undefined (grid description file: %s)!", dname);
          if (grid.nvertex == 0) cdoAbort("nvertex undefined (grid description file: %s)!", dname);
          if (grid.nvertex * size != nvalues)
            cdoAbort("Number of xbounds=%zu and size of xbounds=%zu differ (grid description file: %s)!", nvalues,
                     grid.nvertex * size, dname);

          grid.xbounds.resize(grid.nvertex * size);
          for (size_t i = 0; i < grid.nvertex * size; ++i) grid.xbounds[i] = parameter2double(kv->values[i]);
        }
      else if (STR_IS_EQ(key, "ybounds"))
        {
          size_t size = (grid.type == GRID_CURVILINEAR || grid.type == GRID_UNSTRUCTURED) ? grid.size : grid.ysize;
          if (size == 0) cdoAbort("ysize or gridsize undefined (grid description file: %s)!", dname);
          if (grid.nvertex == 0) cdoAbort("nvertex undefined (grid description file: %s)!", dname);
          if (grid.nvertex * size != nvalues)
            cdoAbort("Number of ybounds=%zu and size of ybounds=%zu differ (grid description file: %s)!", nvalues,
                     grid.nvertex * size, dname);

          grid.ybounds.resize(grid.nvertex * size);
          for (size_t i = 0; i < grid.nvertex * size; ++i) grid.ybounds[i] = parameter2double(kv->values[i]);
        }
      else if (STR_IS_EQ(key, "gridlatlon"))
        {
          if (grid.size == 0) grid.size = grid.xsize * grid.ysize;
          if (grid.size == 0) cdoAbort("gridsize undefined (grid description file: %s)!", dname);
          if ((size_t) grid.size * 2 != nvalues)
            cdoAbort("Number of gridlonlat values=%zu and size of grid=%zu differ (grid description file: %s)!", nvalues,
                     grid.size * 2, dname);
          grid.xvals.resize(grid.size);
          grid.yvals.resize(grid.size);
          for (size_t i = 0; i < (size_t) grid.size; ++i)
            {
              grid.yvals[i] = parameter2double(kv->values[2 * i]);
              grid.xvals[i] = parameter2double(kv->values[2 * i + 1]);
            }
        }
      else if (STR_IS_EQ(key, "mask"))
        {
          size_t size = grid.size;
          if (grid.size == 0) cdoAbort("gridsize undefined (grid description file: %s)!", dname);
          if (size != nvalues)
            cdoAbort("Number of mask values=%zu and size of grid=%zu differ (grid description file: %s)!", nvalues, size, dname);
          grid.mask.resize(size);
          size_t count = 0;
          for (size_t i = 0; i < size; ++i)
            {
              grid.mask[i] = parameter2int(kv->values[i]);
              if (grid.mask[i] == 1) count++;
            }
          if (count == size) vectorFree(grid.mask);
        }
      else if (STR_IS_EQ(key, "rowlon"))
        {
          size_t size = grid.ysize;
          if (size == 0) cdoAbort("ysize undefined (grid description file: %s)!", dname);
          grid.rowlon.resize(size);
          for (size_t i = 0; i < size; ++i) grid.rowlon[i] = parameter2int(kv->values[i]);
        }
      else if (STR_IS_EQ(key, "grid_mapping_name"))
        {
          *igmap = ik;
          break;
        }
      else if (STR_IS_EQ(key, "grid_mapping"))
        {
          *igmap = ik;
          break;
        }
      else
        cdoAbort("Invalid key word >%s< (grid description file: %s)", key, dname);
    }
}

static void
grid_read_mapping(size_t igmap, size_t nkv, kvmap_t *kvmap, int gridID)
{
  for (size_t ik = igmap; ik < nkv; ++ik)
    {
      if (!kvmap[ik].isValid) continue;

      keyValues_t *kv = kvmap[ik].kv;
      const char *key = kv->key;
      size_t nvalues = kv->nvalues;
      const char *value = (kv->nvalues > 0) ? kv->values[0] : NULL;
      // bool lv1 = (kv->nvalues == 1);

      // printf("%s = ", key); if ( value  ) printf("%s", kv->value);
      // printf("\n");

      if (STR_IS_EQ(key, "grid_mapping"))
        {
          cdiGridDefKeyStr(gridID, CDI_KEY_MAPPING, (int) strlen(value) + 1, value);
          continue;
        }

      if (STR_IS_EQ(key, "grid_mapping_name")) cdiGridDefKeyStr(gridID, CDI_KEY_MAPNAME, (int) strlen(value) + 1, value);

      int dtype = literals_find_datatype(nvalues, kv->values);

      if (dtype == CDI_DATATYPE_INT8 || dtype == CDI_DATATYPE_INT16 || dtype == CDI_DATATYPE_INT32)
        {
          int *ivals = (int *) Malloc(nvalues * sizeof(int));
          for (size_t i = 0; i < nvalues; ++i) ivals[i] = literal_to_int(kv->values[i]);
          cdiDefAttInt(gridID, CDI_GLOBAL, key, dtype, nvalues, ivals);
          Free(ivals);
        }
      else if (dtype == CDI_DATATYPE_FLT32 || dtype == CDI_DATATYPE_FLT64)
        {
          double *dvals = (double *) Malloc(nvalues * sizeof(double));
          for (size_t i = 0; i < nvalues; ++i) dvals[i] = literal_to_double(kv->values[i]);
          cdiDefAttFlt(gridID, CDI_GLOBAL, key, dtype, nvalues, dvals);
          Free(dvals);
        }
      else
        {
          int len = (value && *value) ? (int) strlen(value) : 0;
          cdiDefAttTxt(gridID, CDI_GLOBAL, key, len, value);
        }
    }
}

int
grid_read(FILE *gfp, const char *dname)
{
  list_t *pmlist = namelist_to_pmlist(gfp, dname);
  if (pmlist == NULL) return -1;
  list_t *kvlist = *(list_t **) pmlist->head->data;
  if (kvlist == NULL) return -1;

  size_t nkv = list_size(kvlist);
  if (nkv == 0) return -1;
  kvmap_t *kvmap = (kvmap_t *) Malloc(nkv * sizeof(kvmap_t));
  for (size_t i = 0; i < nkv; ++i) kvmap[i].isValid = false;

  size_t ik = 0;
  for (listNode_t *kvnode = kvlist->head; kvnode; kvnode = kvnode->next)
    {
      keyValues_t *kv = *(keyValues_t **) kvnode->data;
      if (ik == 0 && !STR_IS_EQ(kv->key, "gridtype"))
        cdoAbort("First grid description key word must be >gridtype< (found: %s)!", kv->key);

      if (kv->nvalues == 0)
        {
          cdoWarning("Grid description key word %s has no values, skipped!", kv->key);
        }
      else
        {
          kvmap[ik].isValid = true;
          kvmap[ik].kv = kv;
        }
      ik++;
    }

  size_t iproj = 0;
  size_t igmap = 0;
  GridDesciption grid;
  grid_read_data(0, nkv, kvmap, grid, &iproj, &igmap, dname);

  int gridID = (grid.type == CDI_UNDEFID) ? CDI_UNDEFID : gridDefine(grid);

  if (gridID != CDI_UNDEFID)
    {
      int gridprojID = gridID;

      if (iproj > 0)
        {
          GridDesciption proj;
          grid_read_data(iproj, nkv, kvmap, proj, &iproj, &igmap, dname);

          int projID = (proj.type == CDI_UNDEFID) ? CDI_UNDEFID : gridDefine(proj);
          if (projID != CDI_UNDEFID)
            {
              gridDefProj(gridID, projID);
              gridprojID = projID;
            }
        }

      if (igmap > 0) grid_read_mapping(igmap, nkv, kvmap, gridprojID);
    }

  list_destroy(pmlist);

  Free(kvmap);

  return gridID;
}
