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

  Copyright (C) 2003-2021 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:

      Merge      mergetime       Merge datasets sorted by date and time
*/

#include <cdi.h>

#include "cdo_options.h"
#include "cdo_rlimit.h"
#include "process_int.h"
#include "cdo_vlist.h"
#include "util_files.h"
#include "printinfo.h"

struct StreamInfo
{
  CdoStreamID streamID;
  int vlistID;
  int taxisID;
  int tsID;
  int64_t vdate;
  int vtime;
  int nrecs;
  VarList varList;
};

bool
getenv_skip_same_time()
{
  const auto envstr = getenv("SKIP_SAME_TIME");
  if (envstr)
    {
      const auto ival = atoi(envstr);
      if (ival == 1)
        {
          if (Options::cdoVerbose) cdo_print("Set SKIP_SAME_TIME to %d", ival);
          return true;
        }
    }

  return false;
}

static void
open_all_files(int nfiles, std::vector<StreamInfo> &streamInfo)
{
  for (int fileID = 0; fileID < nfiles; fileID++)
    {
      if (Options::cdoVerbose) cdo_print("process: %s", cdo_get_stream_name(fileID));

      auto &si = streamInfo[fileID];
      si.streamID = cdo_open_read(fileID);
      si.vlistID = cdo_stream_inq_vlist(si.streamID);
      si.taxisID = vlistInqTaxis(si.vlistID);
      varListInit(si.varList, si.vlistID);
    }
}

static void
read_first_timestep(int nfiles, std::vector<StreamInfo> &streamInfo)
{
  for (int fileID = 0; fileID < nfiles; fileID++)
    {
      auto &si = streamInfo[fileID];
      si.tsID = 0;
      si.nrecs = cdo_stream_inq_timestep(si.streamID, si.tsID);
      if (si.nrecs == 0)
        {
          cdo_stream_close(si.streamID);
          si.streamID = CDO_STREAM_UNDEF;
        }
      else
        {
          si.vdate = taxisInqVdate(si.taxisID);
          si.vtime = taxisInqVtime(si.taxisID);
        }
    }
}

void *
Mergetime(void *process)
{
  int tsID2 = 0;
  int taxisID2 = CDI_UNDEFID;
  int64_t last_vdate = -1;
  int last_vtime = -1;

  cdo_initialize(process);

  operator_check_argc(0);

  const auto skipSameTime = getenv_skip_same_time();

  const auto dataIsUnchanged = data_is_unchanged();

  const auto nfiles = cdo_stream_cnt() - 1;
  std::vector<StreamInfo> streamInfo(nfiles);

  cdo::set_numfiles(nfiles + 8);

  open_all_files(nfiles, streamInfo);

  // check that the contents is always the same
  for (int fileID = 1; fileID < nfiles; fileID++) vlist_compare(streamInfo[0].vlistID, streamInfo[fileID].vlistID, CMP_ALL);

  // read the first time step
  read_first_timestep(nfiles, streamInfo);

  auto ofilename = cdo_get_stream_name(nfiles);
  if (!Options::cdoOverwriteMode && FileUtils::file_exists(ofilename) && !FileUtils::user_file_overwrite(ofilename))
    cdo_abort("Outputfile %s already exists!", ofilename);

  const auto streamID2 = cdo_open_write(nfiles);

  Field field;

  while (true)
    {
      bool process_timestep = true;

      int next_fileID = -1;
      int64_t vdate = 0;
      int vtime = 0;
      for (int fileID = 0; fileID < nfiles; fileID++)
        {
          if (streamInfo[fileID].streamID != CDO_STREAM_UNDEF)
            if (next_fileID == -1 || streamInfo[fileID].vdate < vdate
                || (streamInfo[fileID].vdate == vdate && streamInfo[fileID].vtime < vtime))
              {
                next_fileID = fileID;
                vdate = streamInfo[fileID].vdate;
                vtime = streamInfo[fileID].vtime;
              }
        }

      const auto fileID = next_fileID;
      if (Options::cdoVerbose) cdo_print("nextstep = %d  vdate = %ld  vtime = %d", fileID, vdate, vtime);
      if (fileID == -1) break;

      auto &si = streamInfo[fileID];

      if (skipSameTime && vdate == last_vdate && vtime == last_vtime)
        {
          cdo_print("Timestep %4d in stream %d (%s %s) already exists, skipped!", si.tsID + 1, si.streamID->get_id(),
                    date_to_string(vdate).c_str(), time_to_string(vtime).c_str());
          process_timestep = false;
        }

      if (process_timestep)
        {
          if (tsID2 == 0)
            {
              const auto vlistID1 = si.vlistID;
              const auto vlistID2 = vlistDuplicate(vlistID1);
              const auto taxisID1 = vlistInqTaxis(vlistID1);
              taxisID2 = taxisDuplicate(taxisID1);
              vlistDefTaxis(vlistID2, taxisID2);

              cdo_def_vlist(streamID2, vlistID2);
            }

          last_vdate = vdate;
          last_vtime = vtime;

          cdo_taxis_copy_timestep(taxisID2, si.taxisID);
          cdo_def_timestep(streamID2, tsID2);

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

              if (tsID2 > 0 && si.tsID == 0 && si.varList[varID].timetype == TIME_CONSTANT) continue;

              cdo_def_record(streamID2, varID, levelID);

              if (dataIsUnchanged)
                {
                  cdo_copy_record(streamID2, si.streamID);
                }
              else
                {
                  field.init(si.varList[varID]);
                  cdo_read_record(si.streamID, field);
                  cdo_write_record(streamID2, field);
                }
            }

          tsID2++;
        }

      si.nrecs = cdo_stream_inq_timestep(si.streamID, ++si.tsID);
      if (si.nrecs == 0)
        {
          cdo_stream_close(si.streamID);
          si.streamID = CDO_STREAM_UNDEF;
        }
      else
        {
          si.vdate = taxisInqVdate(si.taxisID);
          si.vtime = taxisInqVtime(si.taxisID);
        }
    }

  cdo_stream_close(streamID2);

  cdo_finish();

  return nullptr;
}
