// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   https://www.lammps.org/, Sandia National Laboratories
   LAMMPS development team: developers@lammps.org

   Copyright (2003) Sandia Corporation.  Under the terms of Contract
   DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
   certain rights in this software.  This software is distributed under
   the GNU General Public License.

   See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */

#include "rerun.h"

#include "domain.h"
#include "error.h"
#include "finish.h"
#include "input.h"
#include "integrate.h"
#include "modify.h"
#include "output.h"
#include "read_dump.h"
#include "timer.h"
#include "update.h"
#include "variable.h"

#include <cmath>
#include <cstring>

using namespace LAMMPS_NS;

static constexpr double EPSDT = 1.0e-6;
/* ---------------------------------------------------------------------- */

Rerun::Rerun(LAMMPS *lmp) : Command(lmp) {}

/* ---------------------------------------------------------------------- */

void Rerun::command(int narg, char **arg)
{
  if (domain->box_exist == 0)
    error->all(FLERR,"Rerun command before simulation box is defined");

  if (narg < 2) error->all(FLERR,"Illegal rerun command");

  // list of dump files = args until a keyword

  int iarg = 0;
  while (iarg < narg) {
    if (strcmp(arg[iarg],"first") == 0) break;
    if (strcmp(arg[iarg],"last") == 0) break;
    if (strcmp(arg[iarg],"every") == 0) break;
    if (strcmp(arg[iarg],"skip") == 0) break;
    if (strcmp(arg[iarg],"start") == 0) break;
    if (strcmp(arg[iarg],"stop") == 0) break;
    if (strcmp(arg[iarg],"dump") == 0) break;
    if (strcmp(arg[iarg],"post") == 0) break;
    iarg++;
  }
  int nfile = iarg;
  if (nfile == 0 || nfile == narg) error->all(FLERR,"Illegal rerun command");

  // parse optional args up until "dump"
  // use MAXBIGINT -1 so Output can add 1 to it and still be a big int

  bigint first = 0;
  bigint last = MAXBIGINT - 1;
  int nevery = 0;
  int nskip = 1;
  int startflag = 0;
  int stopflag = 0;
  int postflag = 0;
  bigint start = -1;
  bigint stop = -1;

  while (iarg < narg) {
    if (strcmp(arg[iarg],"first") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      first = utils::bnumeric(FLERR,arg[iarg+1],false,lmp);
      if (first < 0) error->all(FLERR,"Illegal rerun command");
      iarg += 2;
    } else if (strcmp(arg[iarg],"last") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      last = utils::bnumeric(FLERR,arg[iarg+1],false,lmp);
      if (last < 0) error->all(FLERR,"Illegal rerun command");
      iarg += 2;
    } else if (strcmp(arg[iarg],"every") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      nevery = utils::inumeric(FLERR,arg[iarg+1],false,lmp);
      if (nevery < 0) error->all(FLERR,"Illegal rerun command");
      iarg += 2;
    } else if (strcmp(arg[iarg],"skip") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      nskip = utils::inumeric(FLERR,arg[iarg+1],false,lmp);
      if (nskip <= 0) error->all(FLERR,"Illegal rerun command");
      iarg += 2;
    } else if (strcmp(arg[iarg],"start") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      startflag = 1;
      start = utils::bnumeric(FLERR,arg[iarg+1],false,lmp);
      if (start < 0) error->all(FLERR,"Illegal rerun command");
      iarg += 2;
    } else if (strcmp(arg[iarg],"stop") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      stopflag = 1;
      stop = utils::bnumeric(FLERR,arg[iarg+1],false,lmp);
      if (stop < 0) error->all(FLERR,"Illegal rerun command");
      iarg += 2;
    } else if (strcmp(arg[iarg],"post") == 0) {
      if (iarg+2 > narg) error->all(FLERR,"Illegal rerun command");
      postflag = utils::logical(FLERR,arg[iarg+1],false,lmp);
      iarg += 2;
    } else if (strcmp(arg[iarg],"dump") == 0) {
      break;
    } else error->all(FLERR,"Illegal rerun command");
  }

  int nremain = narg - iarg - 1;
  if (nremain <= 0) error->all(FLERR,"Illegal rerun command");
  if (first > last) error->all(FLERR,"Illegal rerun command");
  if (startflag && stopflag && start > stop)
    error->all(FLERR,"Illegal rerun command");

  // pass list of filenames to ReadDump
  // along with post-"dump" args and post-"format" args

  auto rd = new ReadDump(lmp);

  rd->store_files(nfile,arg);
  if (nremain)
    nremain = rd->fields_and_keywords(nremain,&arg[narg-nremain]);
  else nremain = rd->fields_and_keywords(0,nullptr);
  if (nremain) rd->setup_reader(nremain,&arg[narg-nremain]);
  else rd->setup_reader(0,nullptr);

  // perform the pseudo run
  // invoke lmp->init() only once
  // read all relevant snapshots
  // use setup_minimal() since atoms are already owned by correct procs
  // addstep_compute_all() ensures energy/virial computed on every snapshot

  update->whichflag = 1;

  if (startflag) update->beginstep = update->firststep = start;
  else update->beginstep = update->firststep = first;
  if (stopflag) update->endstep = update->laststep = stop;
  else update->endstep = update->laststep = last;

  int firstflag = 1;
  int ndump = 0;

  lmp->init();

  timer->init();
  timer->barrier_start();

  bigint ntimestep = rd->seek(first,0);
  if (ntimestep < 0)
    error->all(FLERR,"Rerun dump file does not contain requested snapshot");

  while (true) {
    ndump++;
    rd->header(firstflag);
    update->reset_timestep(ntimestep, false);
    rd->atoms();

    modify->init();
    update->integrate->setup_minimal(1);
    modify->end_of_step();

    // fix up the "next_dump" settings for dumps if the sequence differs from what is read in

    for (int idump = 0; idump < output->ndump; ++idump) {
      // dumps triggered by timestep
      if (output->mode_dump[idump] == 0) {
        // rerun has advanced the timestep faster than the dump expected so we need to catch it up
        if (output->next_dump[idump] < ntimestep) {
          // equidistant dumps
          if (output->every_dump[idump]) {
            // if current step compatible with dump frequency, adjust next_dump setting for dump
            if (ntimestep % output->every_dump[idump] == 0) output->next_dump[idump] = ntimestep;
          } else {
            // next dump is determined by variable.
            // advance next timestep computation from variable until it is equal or larger
            // than the current dump timestep; trigger dump only if equal.
            bigint savedstep = update->ntimestep;
            update->ntimestep = output->next_dump[idump];
            bigint nextdump;
            do {
              nextdump = (bigint) input->variable->compute_equal(output->ivar_dump[idump]);
              update->ntimestep = nextdump;
            } while (nextdump < ntimestep);
            output->next_dump[idump] = nextdump;
            update->ntimestep = savedstep;
          }
        }
      } else {
        // dumps triggered by time
        double tcurrent = update->atime + (ntimestep - update->atimestep) * update->dt;
        // rerun time has moved beyond expected time for dump so we need to catch it up
        if (output->next_time_dump[idump] < tcurrent) {
          // equidistant dumps in time
          if (output->every_time_dump[idump] > 0.0) {
            // trigger dump if current time is within +/- half a timestep of the every interval
            double every = output->every_time_dump[idump];
            double rest = fabs(tcurrent/every - round(tcurrent/every)) * every/update->dt;
            if (rest < 0.5) {
              output->next_dump[idump] = ntimestep;
              output->next_time_dump[idump] = tcurrent;
            } else {
              double nexttime = (floor(tcurrent/every) + 1.0) * every;
              output->next_dump[idump] = update->ntimestep
                + (bigint) ((nexttime - (update->atime + (update->ntimestep - update->atimestep) *
                                         update->dt) - EPSDT*update->dt) / update->dt) + 1;
              output->next_time_dump[idump] = nexttime;
            }
          } else {
            // next dump time is determined by variable.
            // advance next time computation from variable until is is equal or larger
            // than the current time/timestep
            bigint savedstep = update->ntimestep;
            update->ntimestep = output->next_dump[idump];
            double nexttime = output->next_time_dump[idump];
            bigint nextstep = output->next_dump[idump];
            while (nextstep < ntimestep) {
              nexttime = input->variable->compute_equal(output->ivar_dump[idump]);
              nextstep = update->ntimestep
                + (bigint) ((nexttime - (update->atime + (update->ntimestep - update->atimestep) *
                                         update->dt) - EPSDT*update->dt) / update->dt) + 1;
              update->ntimestep = nextstep;
            };
            if (ntimestep > 0) {
              output->next_time_dump[idump] = nexttime;
              output->next_dump[idump] = nextstep;
            }
            update->ntimestep = savedstep;
          }
        }
      }
    }

    output->next_dump_any = ntimestep;
    if (firstflag) output->setup();
    else if (output->next) output->write(ntimestep);

    firstflag = 0;
    ntimestep = rd->next(ntimestep,last,nevery,nskip);
    if (stopflag && ntimestep > stop)
      error->all(FLERR,"Read rerun dump file timestep {} > specified stop {}", ntimestep, stop);
    if (ntimestep < 0) break;
  }

  // ensure thermo output on last dump timestep

  output->next_thermo = update->ntimestep;
  output->write(update->ntimestep);

  timer->barrier_stop();

  update->integrate->cleanup();

  // set update->nsteps to ndump for Finish stats to print

  update->nsteps = ndump;

  Finish finish(lmp);
  finish.end(postflag);

  update->whichflag = 0;
  update->firststep = update->laststep = 0;
  update->beginstep = update->endstep = 0;

  // clean-up

  delete rd;
}
