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

  Copyright (C) 2006 Brockmann Consult
  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:

      Yseaspctl  yseaspctl       Multi-year seasonal percentiles
*/

#include <cdi.h>

#include "cdo_options.h"
#include "cdo_vlist.h"
#include "cdo_season.h"
#include "datetime.h"
#include "process_int.h"
#include "param_conversion.h"
#include "percentiles_hist.h"

void *
Yseaspctl(void *process)
{
  constexpr int MaxSeasons = 4;
  long nsets[MaxSeasons] = { 0 };
  CdoDateTime datetime1[MaxSeasons], datetime2[MaxSeasons];
  std::vector<bool> vars1(MaxSeasons, false);
  HistogramSet hsets[MaxSeasons];

  cdo_initialize(process);
  cdo_operator_add("yseaspctl", FieldFunc_Pctl, 0, nullptr);

  operator_input_arg("percentile number");
  const auto pn = parameter_to_double(cdo_operator_argv(0));

  const auto streamID1 = cdo_open_read(0);
  const auto streamID2 = cdo_open_read(1);
  const auto streamID3 = cdo_open_read(2);

  const auto vlistID1 = cdo_stream_inq_vlist(streamID1);
  const auto vlistID2 = cdo_stream_inq_vlist(streamID2);
  const auto vlistID3 = cdo_stream_inq_vlist(streamID3);
  const auto vlistID4 = vlistDuplicate(vlistID1);

  vlist_compare(vlistID1, vlistID2, CMP_ALL);
  vlist_compare(vlistID1, vlistID3, CMP_ALL);

  const auto taxisID1 = vlistInqTaxis(vlistID1);
  const auto taxisID2 = vlistInqTaxis(vlistID2);
  const auto taxisID3 = vlistInqTaxis(vlistID3);
  // TODO - check that time axes 2 and 3 are equal

  const auto taxisID4 = taxisDuplicate(taxisID1);
  if (taxisHasBounds(taxisID4)) taxisDeleteBounds(taxisID4);
  vlistDefTaxis(vlistID4, taxisID4);

  const auto streamID4 = cdo_open_write(3);
  cdo_def_vlist(streamID4, vlistID4);

  const auto ntsteps = vlistNtsteps(vlistID1);
  const auto nvars = vlistNvars(vlistID1);

  const auto maxrecs = vlistNrecs(vlistID1);
  std::vector<RecordInfo> recList(maxrecs);

  FieldVector constFields(maxrecs);

  Field field1, field2;

  VarList varList1;
  varListInit(varList1, vlistID1);

  int tsID = 0;
  while (true)
    {
      const auto nrecs = cdo_stream_inq_timestep(streamID2, tsID);
      if (nrecs == 0) break;

      if (nrecs != cdo_stream_inq_timestep(streamID3, tsID))
        cdo_abort("Number of records at time step %d of %s and %s differ!", tsID + 1, cdo_get_stream_name(1),
                  cdo_get_stream_name(2));

      const auto vdate = taxisInqVdate(taxisID2);
      const auto vtime = taxisInqVtime(taxisID2);

      if (vdate != taxisInqVdate(taxisID3))
        cdo_abort("Verification dates at time step %d of %s and %s differ!", tsID + 1, cdo_get_stream_name(1),
                  cdo_get_stream_name(2));

      if (Options::cdoVerbose) cdo_print("process timestep: %d %d %d", tsID + 1, vdate, vtime);

      const auto seas = month_to_season(decode_month(vdate));

      set_date_time(datetime2[seas], vdate, vtime);

      if (!vars1[seas])
        {
          vars1[seas] = true;
          hsets[seas].create(nvars, ntsteps);
          for (int varID = 0; varID < nvars; varID++)
            hsets[seas].createVarLevels(varID, varList1[varID].nlevels, varList1[varID].gridsize);
        }

      for (int recID = 0; recID < nrecs; recID++)
        {
          int varID, levelID;
          cdo_inq_record(streamID2, &varID, &levelID);
          field1.init(varList1[varID]);
          cdo_read_record(streamID2, field1);

          cdo_inq_record(streamID3, &varID, &levelID);
          field2.init(varList1[varID]);
          cdo_read_record(streamID3, field2);

          hsets[seas].defVarLevelBounds(varID, levelID, field1, field2);
        }

      tsID++;
    }

  tsID = 0;
  while (true)
    {
      const auto nrecs = cdo_stream_inq_timestep(streamID1, tsID);
      if (nrecs == 0) break;

      const auto vdate = taxisInqVdate(taxisID1);
      const auto vtime = taxisInqVtime(taxisID1);

      const auto month = decode_month(vdate);
      if (month < 0 || month > 16) cdo_abort("Month %d out of range!", month);

      const auto seas = month_to_season(month);

      set_date_time(datetime1[seas], vdate, vtime);

      if (!vars1[seas]) cdo_abort("No data for season %d in %s and %s", seas, cdo_get_stream_name(1), cdo_get_stream_name(2));

      for (int recID = 0; recID < nrecs; recID++)
        {
          int varID, levelID;
          cdo_inq_record(streamID1, &varID, &levelID);

          if (tsID == 0)
            {
              recList[recID].varID = varID;
              recList[recID].levelID = levelID;
              recList[recID].lconst = (vlistInqVarTimetype(vlistID1, varID) == TIME_CONSTANT);
            }

          if (tsID == 0 && recList[recID].lconst)
            {
              constFields[recID].init(varList1[varID]);
              cdo_read_record(streamID1, constFields[recID]);
            }
          else
            {
              field1.init(varList1[varID]);
              cdo_read_record(streamID1, field1);

              hsets[seas].addVarLevelValues(varID, levelID, field1);
            }
        }

      nsets[seas]++;
      tsID++;
    }

  int otsID = 0;
  for (int seas = 0; seas < MaxSeasons; seas++)
    if (nsets[seas])
      {
        if (decode_month_and_day(datetime1[seas].date) != decode_month_and_day(datetime2[seas].date))
          cdo_abort("Verification dates for the season %d of %s and %s are different!", seas, cdo_get_stream_name(0),
                    cdo_get_stream_name(1));

        taxisDefVdate(taxisID4, datetime1[seas].date);
        taxisDefVtime(taxisID4, datetime1[seas].time);
        cdo_def_timestep(streamID4, otsID);

        for (int recID = 0; recID < maxrecs; recID++)
          {
            if (otsID && recList[recID].lconst) continue;

            const auto varID = recList[recID].varID;
            const auto levelID = recList[recID].levelID;
            cdo_def_record(streamID4, varID, levelID);

            if (recList[recID].lconst)
              {
                cdo_write_record(streamID4, constFields[recID]);
              }
            else
              {
                field1.init(varList1[varID]);
                hsets[seas].getVarLevelPercentiles(field1, varID, levelID, pn);
                cdo_write_record(streamID4, field1);
              }
          }

        otsID++;
      }

  cdo_stream_close(streamID4);
  cdo_stream_close(streamID3);
  cdo_stream_close(streamID2);
  cdo_stream_close(streamID1);

  cdo_finish();

  return nullptr;
}
