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

  Copyright (C) 2003-2020 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.
*/

/*
   This module contains the following operators:

      Input     input          ASCII input
      Input     inputsrv       SERVICE input
      Input     inputext       EXTRA input
*/

#include <cdi.h>

#include <mpim_grid.h>
#include "cdo_cdi_wrapper.h"
#include "process_int.h"
#include "griddes.h"
#include "cdo_zaxis.h"
#include "grid_read_pingo.h"
#include "cdo_default_values.h"


static size_t
input_iarray(size_t nval, int *array)
{
  size_t ival = 0;

  for (size_t i = 0; i < nval; i++)
    {
      const auto n = scanf("%d", &array[i]);
      if (n != 1) break;

      ival++;
    }

  return ival;
}

void *
Input(void *process)
{
  int varID = 0;
  size_t gridsize0 = 0, gridsize = 0;
  int taxisID = 0;
  CdoStreamID streamID = CDO_STREAM_UNDEF;
  int vlistID = -1;
  int code = 0, level = 0, date = 0, time = 0, nlon = 0, nlat = 0;
  int output_filetype = CDI_FILETYPE_GRB;
  int ihead[8];
  double missval = 0;
  Varray<double> array;

  cdoInitialize(process);

  // clang-format off
  const auto INPUT    = cdoOperatorAdd("input",    0, 0, nullptr);
  const auto INPUTSRV = cdoOperatorAdd("inputsrv", 0, 0, nullptr);
  const auto INPUTEXT = cdoOperatorAdd("inputext", 0, 0, nullptr);
  // clang-format on

  const auto operatorID = cdoOperatorID();

  int gridID = -1;
  int zaxisID = -1;
  if (operatorID == INPUT)
    {
      operatorInputArg("grid description file or name");
      if (operatorArgc() == 1)
        operatorCheckArgc(1);
      else
        operatorCheckArgc(2);

      gridID = cdoDefineGrid(cdoOperatorArgv(0));
      if (operatorArgc() == 2) zaxisID = cdoDefineZaxis(cdoOperatorArgv(1));
    }
  else
    {
      operatorCheckArgc(0);
    }

  const int nlevs = (zaxisID == -1) ? 1 : zaxisInqSize(zaxisID);

  double dlevel = 0;
  int nrecs = 0;

  int tsID = 0;
  while (true)
    {
      if (operatorID == INPUT)
        {
          output_filetype = cdoFiletype();

          code = -1;
          gridsize = gridInqSize(gridID);
          date = 0;
          time = 0;

          if (nrecs == 0) array.resize(gridsize * nlevs);

          cdoPrint("Enter all %zu elements of timestep %d!", gridsize * nlevs, nrecs + 1);

          const auto rval = input_darray(stdin, gridsize * nlevs, array);

          if (nrecs > 0 && rval == 0) break;

          if (rval != gridsize * nlevs) cdoAbort("Too few input elements (%zu of %zu)!", rval, gridsize * nlevs);

          if (feof(stdin)) break;
        }
      else if (operatorID == INPUTEXT)
        {
          output_filetype = CdoDefault::FileType;
          if (output_filetype == CDI_UNDEFID) output_filetype = CDI_FILETYPE_EXT;

          cdoPrint("Enter header (code,level,date,time,nlon,nlat,dispo1,dispo2) of record %d (or EOF(=^D))!", nrecs + 1);

          auto rval = input_iarray(4, ihead);
          if (feof(stdin) && nrecs == 0) cdoAbort("Too few header elements (%d of %d)!", rval, 4);
          if (feof(stdin)) break;
          if (rval != 4) cdoAbort("Invalid header input!");

          date = ihead[0];
          code = ihead[1];
          level = ihead[2];
          gridsize = ihead[3];

          time = 0;

          if (nrecs == 0)
            {
              dlevel = level;
              gridsize0 = gridsize;
              array.resize(gridsize);
              gridID = gridCreate(GRID_GENERIC, gridsize);
            }
          else
            {
              if (gridsize != gridsize0) cdoAbort("Gridsize must not change!");
            }

          cdoPrint("Enter all %zu elements of record %d!", gridsize, nrecs + 1);

          rval = input_darray(stdin, gridsize, array);
          if (rval != gridsize) cdoAbort("Invalid data input!");
        }
      else if (operatorID == INPUTSRV)
        {
          output_filetype = CdoDefault::FileType;
          if (output_filetype == CDI_UNDEFID) output_filetype = CDI_FILETYPE_SRV;

          cdoPrint("Enter header (code,level,date,time,nlon,nlat,dispo1,dispo2) of record %d (or EOF(=^D))!", nrecs + 1);

          auto rval = input_iarray(8, ihead);
          if (feof(stdin) && nrecs == 0) cdoAbort("Too few header elements (%d of %d)!", rval, 8);
          if (feof(stdin)) break;
          if (rval != 8) cdoAbort("Invalid header input!");

          code = ihead[0];
          level = ihead[1];
          date = ihead[2];
          time = ihead[3];
          nlon = ihead[4];
          nlat = ihead[5];

          gridsize = nlon * nlat;

          if (nrecs == 0)
            {
              dlevel = level;
              gridsize0 = gridsize;
              array.resize(gridsize);
              gridID = gridCreate(GRID_GENERIC, gridsize);
              gridDefXsize(gridID, nlon);
              gridDefYsize(gridID, nlat);
            }
          else
            {
              if (gridsize != gridsize0) cdoAbort("Gridsize must not change!");
            }

          cdoPrint("Enter all %zu elements of record %d!", gridsize, nrecs + 1);

          rval = input_darray(stdin, gridsize, array);
          if (rval != gridsize) cdoAbort("Invalid data input!");
        }

      if (nrecs == 0)
        {
          if (zaxisID == -1)
            {
              zaxisID = zaxisCreate(ZAXIS_SURFACE, 1);
              zaxisDefLevels(zaxisID, &dlevel);
            }

          vlistID = vlistCreate();
          varID = vlistDefVar(vlistID, gridID, zaxisID, TIME_VARYING);
          vlistDefVarParam(vlistID, varID, cdiEncodeParam(code, 255, 255));

          missval = vlistInqVarMissval(vlistID, varID);

          taxisID = cdoTaxisCreate(TAXIS_RELATIVE);
          vlistDefTaxis(vlistID, taxisID);

          streamID = cdoOpenWrite(0, output_filetype);
          cdoDefVlist(streamID, vlistID);
        }

      const auto vdate = date;
      const auto vtime = time;
      taxisDefVdate(taxisID, vdate);
      taxisDefVtime(taxisID, vtime);
      cdoDefTimestep(streamID, tsID);

      for (int levelID = 0; levelID < nlevs; levelID++)
        {
          const auto offset = gridsize * levelID;
          const auto nmiss = arrayNumMV(gridsize, &array[offset], missval);
          cdoDefRecord(streamID, varID, levelID);
          cdoWriteRecord(streamID, &array[offset], nmiss);
        }

      nrecs++;
      tsID++;
    }

  if (streamID)
    {
      cdoStreamClose(streamID);
      vlistDestroy(vlistID);
    }

  cdoFinish();

  return nullptr;
}
