/* ----------------------------------------------------------------------
   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 "fix_vector.h"

#include "arg_info.h"
#include "compute.h"
#include "error.h"
#include "input.h"
#include "memory.h"
#include "modify.h"
#include "update.h"
#include "variable.h"

#include <cstring>

using namespace LAMMPS_NS;
using namespace FixConst;

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

FixVector::FixVector(LAMMPS *lmp, int narg, char **arg) :
    Fix(lmp, narg, arg), vector(nullptr), array(nullptr)
{
  if (narg < 5) utils::missing_cmd_args(FLERR, "fix vector", error);

  nevery = utils::inumeric(FLERR, arg[3], false, lmp);
  if (nevery <= 0) error->all(FLERR, 3, "Invalid fix vector every argument: {}", nevery);

  nmaxval = MAXSMALLINT;
  nindex = 0;

  // parse values

  int iarg = 4;
  values.clear();

  while (iarg < narg) {
    ArgInfo argi(arg[iarg]);

    value_t val;
    val.which = argi.get_type();
    val.argindex = argi.get_index1();
    val.id = argi.get_name();
    val.val.c = nullptr;

    if ((val.which == ArgInfo::UNKNOWN) || (argi.get_dim() > 1))
      error->all(FLERR, "Invalid fix vector argument: {}", arg[iarg]);

    if (val.which == ArgInfo::NONE) break;

    values.push_back(val);
    ++iarg;
  }

  while (iarg < narg) {

    if (strcmp(arg[iarg], "nmax") == 0) {
      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix vector nmax", error);
      nmaxval = utils::bnumeric(FLERR, arg[iarg + 1], false, lmp);
      if (nmaxval < 1) error->all(FLERR, "Invalid nmax value");
      iarg += 2;
    } else {
      error->all(FLERR, "Unknown fix vector keyword: {}", arg[iarg]);
    }
  }

  // setup and error check
  // for fix inputs, check that fix frequency is acceptable
  // this fix produces either a global vector or array
  // intensive/extensive flags set by compute,fix,variable that produces value

  int value, finalvalue;
  bool first = true;
  for (auto &val : values) {
    if (val.which == ArgInfo::COMPUTE) {
      auto *icompute = modify->get_compute_by_id(val.id);
      if (!icompute) error->all(FLERR, "Compute ID {} for fix vector does not exist", val.id);
      if (val.argindex == 0 && icompute->scalar_flag == 0)
        error->all(FLERR, "Fix vector compute {} does not calculate a scalar", val.id);
      if (val.argindex && icompute->vector_flag == 0)
        error->all(FLERR, "Fix vector compute {} does not calculate a vector", val.id);
      if (val.argindex && (val.argindex > icompute->size_vector))
        error->all(FLERR, "Fix vector compute {} vector is accessed out-of-range{}",
                   val.id, utils::errorurl(20));

      if (val.argindex == 0)
        value = icompute->extscalar;
      else if (icompute->extvector >= 0)
        value = icompute->extvector;
      else
        value = icompute->extlist[val.argindex - 1];
      val.val.c = icompute;

    } else if (val.which == ArgInfo::FIX) {
      auto *ifix = modify->get_fix_by_id(val.id);
      if (!ifix) error->all(FLERR, "Fix ID {} for fix vector does not exist", val.id);
      if (val.argindex == 0 && ifix->scalar_flag == 0)
        error->all(FLERR, "Fix vector fix {} does not calculate a scalar", val.id);
      if (val.argindex && ifix->vector_flag == 0)
        error->all(FLERR, "Fix vector fix {} does not calculate a vector", val.id);
      if (val.argindex && val.argindex > ifix->size_vector)
        error->all(FLERR, "Fix vector fix {} vector is accessed out-of-range{}",
                   val.id, utils::errorurl(20));
      if (nevery % ifix->global_freq)
        error->all(FLERR, "Fix for fix {} vector not computed at compatible time{}",
                   val.id, utils::errorurl(7));

      if (val.argindex == 0)
        value = ifix->extscalar;
      else if (ifix->extvector >= 0)
        value = ifix->extvector;
      else
        value = ifix->extlist[val.argindex - 1];
      val.val.f = ifix;

    } else if (val.which == ArgInfo::VARIABLE) {
      int ivariable = input->variable->find(val.id.c_str());
      if (ivariable < 0)
        error->all(FLERR, "Variable name {} for fix vector does not exist", val.id);
      if (val.argindex == 0 && input->variable->equalstyle(ivariable) == 0)
        error->all(FLERR, "Fix vector variable {} is not equal-style variable", val.id);
      if (val.argindex && input->variable->vectorstyle(ivariable) == 0)
        error->all(FLERR, "Fix vector variable {} is not vector-style variable", val.id);
      value = 0;
      val.val.v = ivariable;
    }

    if (first) {
      finalvalue = value;
      first = false;
    } else if (value != finalvalue)
      error->all(FLERR, "Fix vector cannot set output array intensive/extensive from these inputs");
  }

  if (values.size() == 1) {
    vector_flag = 1;
    extvector = finalvalue;
  } else {
    array_flag = 1;
    size_array_cols = values.size();
    extarray = finalvalue;
  }

  global_freq = nevery;
  time_depend = 1;

  // ncount = current size of vector or array

  vector = nullptr;
  array = nullptr;
  ncount = ncountmax = nindex = 0;
  if (values.size() == 1)
    size_vector = 0;
  else
    size_array_rows = 0;

  // nextstep = next step on which end_of_step does something
  // add nextstep to all computes that store invocation times
  // since don't know a priori which are invoked by this fix
  // once in end_of_step() can set timestep for ones actually invoked

  nextstep = (update->ntimestep / nevery) * nevery;
  if (nextstep < update->ntimestep) nextstep += nevery;
  modify->addstep_compute_all(nextstep);

  // initialstep = first step the vector/array will store values for

  initialstep = nextstep;
}

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

FixVector::~FixVector()
{
  values.clear();
  memory->destroy(vector);
  memory->destroy(array);
}

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

int FixVector::setmask()
{
  int mask = 0;
  mask |= END_OF_STEP;
  return mask;
}

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

void FixVector::init()
{
  // set current indices for all computes,fixes,variables

  for (auto &val : values) {
    if (val.which == ArgInfo::COMPUTE) {
      val.val.c = modify->get_compute_by_id(val.id);
      if (!val.val.c) error->all(FLERR, "Compute ID {} for fix vector does not exist", val.id);

    } else if (val.which == ArgInfo::FIX) {
      val.val.f = modify->get_fix_by_id(val.id);
      if (!val.val.f) error->all(FLERR, "Fix ID {} for fix vector does not exist", val.id);

    } else if (val.which == ArgInfo::VARIABLE) {
      int ivariable = input->variable->find(val.id.c_str());
      if (ivariable < 0) error->all(FLERR, "Variable name for fix vector does not exist");
      val.val.v = ivariable;
    }
  }

  // reallocate vector or array for accumulated size at end of run
  // use endstep to allow for subsequent runs with "pre no"
  // nsize = # of entries from initialstep to finalstep

  bigint finalstep = update->endstep / nevery * nevery;
  if (finalstep > update->endstep) finalstep -= nevery;
  ncountmax = (finalstep - initialstep) / nevery + 1;
  if (ncountmax > nmaxval) ncountmax = nmaxval;
  if (values.size() == 1)
    memory->grow(vector, ncountmax, "vector:vector");
  else
    memory->grow(array, ncountmax, values.size(), "vector:array");
}

/* ----------------------------------------------------------------------
   only does something if nvalid = current timestep
------------------------------------------------------------------------- */

void FixVector::setup(int /*vflag*/)
{
  end_of_step();
}

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

void FixVector::end_of_step()
{
  // skip if not step which requires doing something

  if (update->ntimestep != nextstep) return;

  // wrap around when vector/array is full
  nindex = ncount % ncountmax;

  // accumulate results of computes,fixes,variables to local copy
  // compute/fix/variable may invoke computes so wrap with clear/add

  double *result;
  if (values.size() == 1)
    result = &vector[nindex];
  else
    result = array[nindex];

  modify->clearstep_compute();

  int i = 0;
  for (auto &val : values) {

    // invoke compute if not previously invoked

    if (val.which == ArgInfo::COMPUTE) {

      if (val.argindex == 0) {
        if (!(val.val.c->invoked_flag & Compute::INVOKED_SCALAR)) {
          val.val.c->compute_scalar();
          val.val.c->invoked_flag |= Compute::INVOKED_SCALAR;
        }
        result[i] = val.val.c->scalar;
      } else {
        if (!(val.val.c->invoked_flag & Compute::INVOKED_VECTOR)) {
          val.val.c->compute_vector();
          val.val.c->invoked_flag |= Compute::INVOKED_VECTOR;
        }
        result[i] = val.val.c->vector[val.argindex - 1];
      }

      // access fix fields, guaranteed to be ready

    } else if (val.which == ArgInfo::FIX) {
      if (val.argindex == 0)
        result[i] = val.val.f->compute_scalar();
      else
        result[i] = val.val.f->compute_vector(val.argindex - 1);

      // evaluate equal-style or vector-style variable

    } else if (val.which == ArgInfo::VARIABLE) {
      if (val.argindex == 0)
        result[i] = input->variable->compute_equal(val.val.v);
      else {
        double *varvec;
        int nvec = input->variable->compute_vector(val.val.v, &varvec);
        int index = val.argindex;
        if (nvec < index)
          result[i] = 0.0;
        else
          result[i] = varvec[index - 1];
      }
    }
    ++i;
  }

  // trigger computes on next needed step

  nextstep += nevery;
  modify->addstep_compute(nextstep);

  // update size of vector or array

  ncount++;
  if (values.size() == 1)
    size_vector = MIN(size_vector + 1, ncountmax);
  else
    size_array_rows = MIN(size_array_rows + 1, ncountmax);
}

/* ----------------------------------------------------------------------
   return Ith vector value
------------------------------------------------------------------------- */

double FixVector::compute_vector(int i)
{
  int idx = i;
  if (ncount >= ncountmax) idx = (i + ncount) % ncountmax;
  return vector[idx];
}

/* ----------------------------------------------------------------------
   return I,J array value
------------------------------------------------------------------------- */

double FixVector::compute_array(int i, int j)
{
  int idx = i;
  if (ncount >= ncountmax) idx = (i + ncount) % ncountmax;
  return array[idx][j];
}
