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

#include "arg_info.h"
#include "compute.h"
#include "domain.h"
#include "error.h"
#include "fix.h"
#include "grid2d.h"
#include "grid3d.h"
#include "memory.h"
#include "modify.h"
#include "update.h"

#include <cstring>

using namespace LAMMPS_NS;

// customize by adding keyword
// also customize compute_atom_property.cpp

enum {COMPUTE,FIX};

static constexpr int ONEFIELD = 32;
static constexpr int DELTA = 1048576;

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

DumpGrid::DumpGrid(LAMMPS *lmp, int narg, char **arg) :
  Dump(lmp, narg, arg), idregion(nullptr), earg(nullptr), vtype(nullptr),
  vformat(nullptr), columns(nullptr), columns_default(nullptr),
  field2index(nullptr), field2grid(nullptr), field2data(nullptr),
  argindex(nullptr), id_compute(nullptr), id_fix(nullptr), pack_choice(nullptr)
{
  if (narg == 5) error->all(FLERR,"No dump grid arguments specified");

  clearstep = 1;

  nevery = utils::inumeric(FLERR,arg[3],false,lmp);
  if (nevery <= 0) error->all(FLERR,"Illegal dump grid nevery value: {}", nevery);

  // expand args if any have wildcard character "*"
  // ok to include trailing optional args,
  //   so long as they do not have "*" between square brackets
  // nfield may be shrunk below if extra optional args exist

  expand = 0;
  nfield = nargnew = utils::expand_args(FLERR,narg-5,&arg[5],1,earg,lmp);
  if (earg != &arg[5]) expand = 1;

  // allocate field vectors

  pack_choice = new FnPtrPack[nfield];
  vtype = new int[nfield];
  field2source = new int[nfield];
  field2index = new int[nfield];
  field2grid = new int[nfield];
  field2data = new int[nfield];
  argindex = new int[nfield];

  buffer_allow = 1;
  buffer_flag = 1;

  dimension = domain->dimension;

  // for 2d, set nzgrid = 1 for dump grid and grid/vtk files

  if (dimension == 2) nzgrid = 1;

  // computes and fixes which the dump accesses

  ncompute = 0;
  nfix = 0;

  // process attributes
  // ioptional = start of additional optional args in expanded args

  ioptional = parse_fields(nfield,earg);

  if (ioptional < nfield &&
      strcmp(style,"image") != 0 && strcmp(style,"movie") != 0)
    error->all(FLERR,"Invalid attribute {} in dump {} command", earg[ioptional],style);

  // noptional = # of optional args
  // reset nfield to subtract off optional args
  // reset ioptional to what it would be in original arg list
  // only dump image and dump movie styles process optional args,
  //   they do not use expanded earg list

  int noptional = nfield - ioptional;
  nfield -= noptional;
  size_one = nfield;
  ioptional = narg - noptional;

  // setup format strings

  vformat = new char*[nfield];
  std::string cols;

  cols.clear();
  for (int i = 0; i < nfield; i++) {
    if (vtype[i] == Dump::INT) cols += "%d ";
    else if (vtype[i] == Dump::DOUBLE) cols += "%g ";
    else if (vtype[i] == Dump::STRING) cols += "%s ";
    else if (vtype[i] == Dump::BIGINT) cols += BIGINT_FORMAT " ";
    vformat[i] = nullptr;
  }
  cols.resize(cols.size()-1);
  format_default = utils::strdup(cols);

  format_column_user = new char*[nfield];
  for (int i = 0; i < nfield; i++) format_column_user[i] = nullptr;

  // setup column string

  cols.clear();
  keyword_user.resize(nfield);
  for (int iarg = 0; iarg < nfield; iarg++) {
    key2col[earg[iarg]] = iarg;
    keyword_user[iarg].clear();
    if (cols.size()) cols += " ";
    cols += earg[iarg];
  }
  columns_default = utils::strdup(cols);
}

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

DumpGrid::~DumpGrid()
{
  // if wildcard expansion occurred, free earg memory from expand_args()
  // could not do in constructor, b/c some derived classes process earg

  if (expand) {
    for (int i = 0; i < nargnew; i++) delete[] earg[i];
    memory->sfree(earg);
  }

  delete[] pack_choice;
  delete[] vtype;
  delete[] field2source;
  delete[] field2index;
  delete[] field2grid;
  delete[] field2data;
  delete[] argindex;

  delete[] idregion;

  for (int i = 0; i < ncompute; i++) delete[] id_compute[i];
  memory->sfree(id_compute);

  for (int i = 0; i < nfix; i++) delete[] id_fix[i];
  memory->sfree(id_fix);

  if (vformat) {
    for (int i = 0; i < nfield; i++) delete[] vformat[i];
    delete[] vformat;
  }

  if (format_column_user) {
    for (int i = 0; i < nfield; i++) delete[] format_column_user[i];
    delete[] format_column_user;
  }

  delete[] columns_default;
  delete[] columns;
}

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

void DumpGrid::init_style()
{
  // assemble ITEMS: column string from defaults and user values

  delete[] columns;
  std::string combined;
  int icol = 0;
  for (const auto &item : utils::split_words(columns_default)) {
    if (combined.size()) combined += " ";
    if (keyword_user[icol].size()) combined += keyword_user[icol];
    else combined += item;
    ++icol;
  }
  columns = utils::strdup(combined);

  // format = copy of default or user-specified line format

  delete[] format;
  if (format_line_user) format = utils::strdup(format_line_user);
  else format = utils::strdup(format_default);

  // tokenize the format string and add space at end of each format element
  // if user-specified int/float format exists, use it instead
  // if user-specified column format exists, use it instead
  // lo priority = line, medium priority = int/float, hi priority = column

  auto words = utils::split_words(format);
  if ((int) words.size() < nfield)
    error->all(FLERR,"Dump_modify format line is too short");

  int i=0;
  for (const auto &word : words) {
    delete[] vformat[i];

    if (format_column_user[i])
      vformat[i] = utils::strdup(std::string(format_column_user[i]) + " ");
    else if (vtype[i] == Dump::INT && format_int_user)
      vformat[i] = utils::strdup(std::string(format_int_user) + " ");
    else if (vtype[i] == Dump::DOUBLE && format_float_user)
      vformat[i] = utils::strdup(std::string(format_float_user) + " ");
    else if (vtype[i] == Dump::BIGINT && format_bigint_user)
      vformat[i] = utils::strdup(std::string(format_bigint_user) + " ");
    else vformat[i] = utils::strdup(word + " ");

    // remove trailing blank on last column's format
    if (i == nfield-1) vformat[i][strlen(vformat[i])-1] = '\0';

    ++i;
  }

  // setup boundary string

  domain->boundary_string(boundstr);

  // setup function ptrs

  if (binary && domain->triclinic == 0)
    header_choice = &DumpGrid::header_binary;
  else if (binary && domain->triclinic == 1)
    header_choice = &DumpGrid::header_binary_triclinic;
  else if (!binary && domain->triclinic == 0)
    header_choice = &DumpGrid::header_item;
  else if (!binary && domain->triclinic == 1)
    header_choice = &DumpGrid::header_item_triclinic;

  if (binary) write_choice = &DumpGrid::write_binary;
  else if (buffer_flag == 1) write_choice = &DumpGrid::write_string;
  else write_choice = &DumpGrid::write_lines;

  // find current ptr for each compute and fix

  for (i = 0; i < ncompute; i++) {
    compute[i] = modify->get_compute_by_id(id_compute[i]);
    if (!compute[i])
      error->all(FLERR,"Could not find dump grid compute ID {}",id_compute[i]);
  }

  for (i = 0; i < nfix; i++) {
    fix[i] = modify->get_fix_by_id(id_fix[i]);
    if (!fix[i])
      error->all(FLERR,"Could not find dump grid fix ID {}", id_fix[i]);
  }

  // check that grid sizes for all fields are the same

  Grid2d *grid2d = nullptr;
  Grid3d *grid3d = nullptr;
  int nxtmp,nytmp,nztmp;
  for (int i = 0; i < nfield; i++) {
     if (dimension == 2) {
       if (field2source[i] == COMPUTE)
         grid2d = (Grid2d *) compute[field2index[i]]->get_grid_by_index(field2grid[i]);
       else
        grid2d = (Grid2d *) fix[field2index[i]]->get_grid_by_index(field2grid[i]);
      if (i == 0) grid2d->get_size(nxgrid,nygrid);
      else {
        grid2d->get_size(nxtmp,nytmp);
        if ((nxtmp != nxgrid) || (nytmp != nygrid))
          error->all(FLERR,"Dump grid field grid sizes do not match");
      }

    } else {
      if (field2source[i] == COMPUTE)
        grid3d = (Grid3d *) compute[field2index[i]]->get_grid_by_index(field2grid[i]);
      else
        grid3d = (Grid3d *) fix[field2index[i]]->get_grid_by_index(field2grid[i]);
      if (i == 0) grid3d->get_size(nxgrid,nygrid,nzgrid);
      else {
        grid3d->get_size(nxtmp,nytmp,nztmp);
        if ((nxtmp != nxgrid) || (nytmp != nygrid) || (nztmp != nzgrid))
          error->all(FLERR,"Dump grid field grid sizes do not match");
      }
    }
  }

  // check validity of region

  if (idregion && !domain->get_region_by_id(idregion))
    error->all(FLERR,"Region {} for dump grid does not exist", idregion);

  // open single file, one time only

  if (multifile == 0) openfile();
}

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

void DumpGrid::write_header(bigint ndump)
{
  if (!header_choice) error->all(FLERR, "Must not use 'run pre no' after creating a new dump");

  if (multiproc) (this->*header_choice)(ndump);
  else if (me == 0) (this->*header_choice)(ndump);
}

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

void DumpGrid::header_unit_style_binary()
{
  int len = 0;
  if (unit_flag && !unit_count) {
    ++unit_count;
    len = strlen(update->unit_style);
    fwrite(&len, sizeof(int), 1, fp);
    fwrite(update->unit_style, sizeof(char), len, fp);
  } else {
    fwrite(&len, sizeof(int), 1, fp);
  }
}

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

void DumpGrid::header_columns_binary()
{
  int len = strlen(columns);
  fwrite(&len, sizeof(int), 1, fp);
  fwrite(columns, sizeof(char), len, fp);
}

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

void DumpGrid::header_time_binary()
{
  char flag = time_flag ? 1 : 0;
  fwrite(&flag, sizeof(char), 1, fp);

  if (time_flag) {
    double t = compute_time();
    fwrite(&t, sizeof(double), 1, fp);
  }
}

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

void DumpGrid::header_format_binary()
{
  format_magic_string_binary();
  format_endian_binary();
  format_revision_binary();
}

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

void DumpGrid::header_binary(bigint ndump)
{
  header_format_binary();

  fwrite(&update->ntimestep,sizeof(bigint),1,fp);
  fwrite(&ndump,sizeof(bigint),1,fp);
  fwrite(&domain->triclinic,sizeof(int),1,fp);
  fwrite(&domain->boundary[0][0],6*sizeof(int),1,fp);
  fwrite(&boxxlo,sizeof(double),1,fp);
  fwrite(&boxxhi,sizeof(double),1,fp);
  fwrite(&boxylo,sizeof(double),1,fp);
  fwrite(&boxyhi,sizeof(double),1,fp);
  fwrite(&boxzlo,sizeof(double),1,fp);
  fwrite(&boxzhi,sizeof(double),1,fp);
  fwrite(&domain->dimension,sizeof(int),1,fp);
  fwrite(&nxgrid,sizeof(int),1,fp);
  fwrite(&nygrid,sizeof(int),1,fp);
  fwrite(&nzgrid,sizeof(int),1,fp);
  fwrite(&nfield,sizeof(int),1,fp);

  header_unit_style_binary();
  header_time_binary();
  header_columns_binary();

  if (multiproc) fwrite(&nclusterprocs,sizeof(int),1,fp);
  else fwrite(&nprocs,sizeof(int),1,fp);
}

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

void DumpGrid::header_binary_triclinic(bigint ndump)
{
  header_format_binary();

  fwrite(&update->ntimestep,sizeof(bigint),1,fp);
  fwrite(&ndump,sizeof(bigint),1,fp);
  fwrite(&domain->triclinic,sizeof(int),1,fp);
  fwrite(&domain->boundary[0][0],6*sizeof(int),1,fp);
  fwrite(&boxxlo,sizeof(double),1,fp);
  fwrite(&boxxhi,sizeof(double),1,fp);
  fwrite(&boxylo,sizeof(double),1,fp);
  fwrite(&boxyhi,sizeof(double),1,fp);
  fwrite(&boxzlo,sizeof(double),1,fp);
  fwrite(&boxzhi,sizeof(double),1,fp);
  fwrite(&boxxy,sizeof(double),1,fp);
  fwrite(&boxxz,sizeof(double),1,fp);
  fwrite(&boxyz,sizeof(double),1,fp);
  fwrite(&domain->dimension,sizeof(int),1,fp);
  fwrite(&nxgrid,sizeof(int),1,fp);
  fwrite(&nygrid,sizeof(int),1,fp);
  fwrite(&nzgrid,sizeof(int),1,fp);
  fwrite(&nfield,sizeof(int),1,fp);

  header_unit_style_binary();
  header_time_binary();
  header_columns_binary();

  if (multiproc) fwrite(&nclusterprocs,sizeof(int),1,fp);
  else fwrite(&nprocs,sizeof(int),1,fp);
}

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

void DumpGrid::header_item(bigint /*ndump*/)
{
  if (unit_flag && !unit_count) {
    ++unit_count;
    fmt::print(fp,"ITEM: UNITS\n{}\n",update->unit_style);
  }
  if (time_flag) fmt::print(fp,"ITEM: TIME\n{:.16}\n",compute_time());

  fmt::print(fp,"ITEM: TIMESTEP\n{}\n",update->ntimestep);
  fmt::print(fp,"ITEM: BOX BOUNDS {}\n"
             "{:>1.16e} {:>1.16e}\n"
             "{:>1.16e} {:>1.16e}\n"
             "{:>1.16e} {:>1.16e}\n",
             boundstr,boxxlo,boxxhi,boxylo,boxyhi,boxzlo,boxzhi);
  fmt::print(fp,"ITEM: DIMENSION\n{}\n",domain->dimension);
  fmt::print(fp,"ITEM: GRID SIZE nx ny nz\n{} {} {}\n",nxgrid,nygrid,nzgrid);
  fmt::print(fp,"ITEM: GRID CELLS {}\n",columns);
}

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

void DumpGrid::header_item_triclinic(bigint /*ndump*/)
{
  if (unit_flag && !unit_count) {
    ++unit_count;
    fmt::print(fp,"ITEM: UNITS\n{}\n",update->unit_style);
  }
  if (time_flag) fmt::print(fp,"ITEM: TIME\n{:.16}\n",compute_time());

  fmt::print(fp,"ITEM: TIMESTEP\n{}\n",update->ntimestep);
  fmt::print(fp,"ITEM: BOX BOUNDS xy xz yz {}\n"
             "{:>1.16e} {:>1.16e} {:>1.16e}\n"
             "{:>1.16e} {:>1.16e} {:>1.16e}\n"
             "{:>1.16e} {:>1.16e} {:>1.16e}\n",
             boundstr,boxxlo,boxxhi,boxxy,boxylo,boxyhi,boxxz,boxzlo,boxzhi,boxyz);
  fmt::print(fp,"ITEM: DIMENSION\n{}\n",domain->dimension);
  fmt::print(fp,"ITEM: GRID SIZE nx ny nz\n{} {} {}\n",nxgrid,nygrid,nzgrid);
  fmt::print(fp,"ITEM: GRID CELLS {}\n",columns);
}

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

void DumpGrid::format_magic_string_binary()
{
  // use negative ntimestep as marker for new format
  bigint fmtlen = strlen(MAGIC_STRING);
  bigint marker = -fmtlen;
  fwrite(&marker, sizeof(bigint), 1, fp);
  fwrite(MAGIC_STRING, sizeof(char), fmtlen, fp);
}

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

void DumpGrid::format_endian_binary()
{
  int endian = ENDIAN;
  fwrite(&endian, sizeof(int), 1, fp);
}

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

void DumpGrid::format_revision_binary()
{
  int revision = FORMAT_REVISION;
  fwrite(&revision, sizeof(int), 1, fp);
}

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

int DumpGrid::count()
{
  int i;

  // set current size for portion of grid on each proc
  // may change between dump snapshots due to load balancing

  Grid2d *grid2d = nullptr;
  Grid3d *grid3d = nullptr;

  if (dimension == 2) {
    if (field2source[0] == COMPUTE)
      grid2d = (Grid2d *) compute[field2index[0]]->get_grid_by_index(field2grid[0]);
    else if (field2source[0] == FIX)
      grid2d = (Grid2d *) fix[field2index[0]]->get_grid_by_index(field2grid[0]);
    else error->all(FLERR, "Unsupported grid data source type {}", field2source[0]);
    grid2d->get_bounds_owned(nxlo_in,nxhi_in,nylo_in,nyhi_in);
  } else {
    if (field2source[0] == COMPUTE)
      grid3d = (Grid3d *) compute[field2index[0]]->get_grid_by_index(field2grid[0]);
    else if (field2source[0] == FIX)
      grid3d = (Grid3d *) fix[field2index[0]]->get_grid_by_index(field2grid[0]);
    else error->all(FLERR, "Unsupported grid data source type {}", field2source[0]);
    grid3d->get_bounds_owned(nxlo_in,nxhi_in,nylo_in,nyhi_in,nzlo_in,nzhi_in);
  }

  // invoke Computes for per-grid quantities
  // cannot invoke before first run, otherwise invoke if necessary

  if (ncompute) {
    for (i = 0; i < ncompute; i++) {
      if (!compute[i]->is_initialized())
        error->all(FLERR,"Dump compute ID {} cannot be invoked before initialization by a run",
          compute[i]->id);
      if (!(compute[i]->invoked_flag & Compute::INVOKED_PERGRID)) {
        compute[i]->compute_pergrid();
        compute[i]->invoked_flag |= Compute::INVOKED_PERGRID;
      }
    }
  }

  // return count of grid points I own

  int ngrid;
  if (dimension == 2)
    ngrid = (nxhi_in-nxlo_in+1) * (nyhi_in-nylo_in+1);
  else
    ngrid = (nxhi_in-nxlo_in+1) * (nyhi_in-nylo_in+1) * (nzhi_in-nzlo_in+1);

  return ngrid;
}

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

void DumpGrid::pack(tagint *ids)
{
  for (int n = 0; n < size_one; n++) (this->*pack_choice[n])(n);

  // ids = list of my grid IDs

  if (ids) {
    int m = 0;
    if (dimension == 2) {
        for (int iy = nylo_in; iy <= nyhi_in; iy++)
          for (int ix = nxlo_in; ix <= nxhi_in; ix++)
            ids[m++] = iy*nxgrid + ix + 1;
    } else if (dimension == 3) {
        for (int iz = nzlo_in; iz <= nzhi_in; iz++)
          for (int iy = nylo_in; iy <= nyhi_in; iy++)
            for (int ix = nxlo_in; ix <= nxhi_in; ix++)
              ids[m++] = iz*nygrid*nxgrid + iy*nxgrid + ix + 1;
    }
  }
}

/* ----------------------------------------------------------------------
   convert mybuf of doubles to one big formatted string in sbuf
   return -1 if strlen exceeds an int, since used as arg in MPI calls in Dump
------------------------------------------------------------------------- */

int DumpGrid::convert_string(int n, double *mybuf)
{
  int i,j;

  int offset = 0;
  int m = 0;
  for (i = 0; i < n; i++) {
    if (offset + nfield*ONEFIELD > maxsbuf) {
      if ((bigint) maxsbuf + DELTA > MAXSMALLINT) return -1;
      maxsbuf += DELTA;
      memory->grow(sbuf,maxsbuf,"dump:sbuf");
    }

    for (j = 0; j < nfield; j++) {
      const auto maxsize = maxsbuf - offset;
      if (vtype[j] == Dump::INT)
        offset += snprintf(&sbuf[offset],maxsize,vformat[j],static_cast<int> (mybuf[m]));
      else if (vtype[j] == Dump::DOUBLE)
        offset += snprintf(&sbuf[offset],maxsize,vformat[j],mybuf[m]);
      else if (vtype[j] == Dump::BIGINT)
        offset += snprintf(&sbuf[offset],maxsize,vformat[j], static_cast<bigint> (mybuf[m]));
      m++;
    }
    offset += snprintf(&sbuf[offset],maxsbuf-offset,"\n");
  }

  return offset;
}

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

void DumpGrid::write_data(int n, double *mybuf)
{
  (this->*write_choice)(n,mybuf);
}

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

void DumpGrid::write_binary(int n, double *mybuf)
{
  n *= size_one;
  fwrite(&n,sizeof(int),1,fp);
  fwrite(mybuf,sizeof(double),n,fp);
}

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

void DumpGrid::write_string(int n, double *mybuf)
{
  if (mybuf)
    fwrite(mybuf,sizeof(char),n,fp);
}

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

void DumpGrid::write_lines(int n, double *mybuf)
{
  int i,j;

  int m = 0;
  for (i = 0; i < n; i++) {
    for (j = 0; j < nfield; j++) {
      if (vtype[j] == Dump::INT) fprintf(fp,vformat[j],static_cast<int> (mybuf[m]));
      else if (vtype[j] == Dump::DOUBLE) fprintf(fp,vformat[j],mybuf[m]);
      else if (vtype[j] == Dump::BIGINT) fprintf(fp,vformat[j],static_cast<bigint> (mybuf[m]));
      m++;
    }
    fprintf(fp,"\n");
  }
}

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

int DumpGrid::parse_fields(int narg, char **arg)
{
  // customize by adding to if statement

  for (int iarg = 0; iarg < narg; iarg++) {

    char *id;
    int igrid,idata,index;
    int iflag =
      utils::check_grid_reference((char *) "Dump grid",
                                  arg[iarg],nevery,id,igrid,idata,index,lmp);

    // arg is not a valid Grid reference
    // assume it's an additional dump grid option and return

    if (iflag < 0) return iarg;

    // grid reference is to a compute or fix

    if (iflag == ArgInfo::COMPUTE) {
      auto icompute = lmp->modify->get_compute_by_id(id);
      field2index[iarg] = add_compute(id,icompute);
      field2source[iarg] = COMPUTE;
    } else if (iflag == ArgInfo::FIX) {
      auto ifix = modify->get_fix_by_id(id);
      field2index[iarg] = add_fix(id,ifix);
      field2source[iarg] = FIX;
    }

    delete [] id;
    argindex[iarg] = index;
    vtype[iarg] = Dump::DOUBLE;
    field2grid[iarg] = igrid;
    field2data[iarg] = idata;

    if (dimension == 2) pack_choice[iarg] = &DumpGrid::pack_grid2d;
    else pack_choice[iarg] = &DumpGrid::pack_grid3d;
  }

  return narg;
}

/* ----------------------------------------------------------------------
   add Compute to list of Compute objects used by dump
   return index of where this Compute is in list
   if already in list, do not add, just return index, else add to list
------------------------------------------------------------------------- */

int DumpGrid::add_compute(const std::string &id, Compute *cptr)
{
  int icompute;
  for (icompute = 0; icompute < ncompute; icompute++)
    if (id == id_compute[icompute]) break;
  if (icompute < ncompute) return icompute;

  id_compute = (char **) memory->srealloc(id_compute,(ncompute+1)*sizeof(char *),"dump:id_compute");
  id_compute[ncompute] = utils::strdup(id);
  compute.push_back(cptr);

  ncompute++;
  return ncompute-1;
}

/* ----------------------------------------------------------------------
   add Fix to list of Fix objects used by dump
   return index of where this Fix is in list
   if already in list, do not add, just return index, else add to list
------------------------------------------------------------------------- */

int DumpGrid::add_fix(const std::string &id, Fix *fptr)
{
  int ifix;
  for (ifix = 0; ifix < nfix; ifix++)
    if (id == id_fix[ifix]) break;
  if (ifix < nfix) return ifix;

  id_fix = (char **) memory->srealloc(id_fix,(nfix+1)*sizeof(char *),"dump:id_fix");
  id_fix[nfix] = utils::strdup(id);
  fix.push_back(fptr);

  nfix++;
  return nfix-1;
}

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

int DumpGrid::modify_param(int narg, char **arg)
{
  if (strcmp(arg[0],"region") == 0) {
    if (narg < 2) error->all(FLERR,"Illegal dump_modify command");
    if (strcmp(arg[1],"none") == 0) {
      delete[] idregion;
      idregion = nullptr;
    } else {
      delete[] idregion;
      if (!domain->get_region_by_id(arg[1]))
        error->all(FLERR,"Dump_modify region {} does not exist", arg[1]);
      idregion = utils::strdup(arg[1]);
    }
    return 2;
  }

  if (strcmp(arg[0],"format") == 0) {
    if (narg < 2) error->all(FLERR,"Illegal dump_modify command");

    if (strcmp(arg[1],"none") == 0) {
      // just clear format_column_user allocated by this dump child class
      for (int i = 0; i < nfield; i++) {
        delete[] format_column_user[i];
        format_column_user[i] = nullptr;
      }
      return 2;
    }

    if (narg < 3) error->all(FLERR,"Illegal dump_modify command");

    if (strcmp(arg[1],"int") == 0) {
      delete[] format_int_user;
      format_int_user = utils::strdup(arg[2]);
      delete[] format_bigint_user;
      int n = strlen(format_int_user) + 8;
      format_bigint_user = new char[n];
      // replace "d" in format_int_user with bigint format specifier
      // use of &str[1] removes leading '%' from BIGINT_FORMAT string
      char *ptr = strchr(format_int_user,'d');
      if (ptr == nullptr)
        error->all(FLERR,"Dump_modify int format does not contain d character");
      char str[8];
      snprintf(str,8,"%s",BIGINT_FORMAT);
      *ptr = '\0';
      snprintf(format_bigint_user,n,"%s%s%s",format_int_user,&str[1],ptr+1);
      *ptr = 'd';

    } else if (strcmp(arg[1],"float") == 0) {
      delete[] format_float_user;
      format_float_user = utils::strdup(arg[2]);

    } else {
      int i = utils::inumeric(FLERR,arg[1],false,lmp) - 1;
      if (i < 0 || i >= nfield)
        error->all(FLERR,"Illegal dump_modify command");
      delete[] format_column_user[i];
      format_column_user[i] = utils::strdup(arg[2]);
    }
    return 3;
  }

  return 0;
}

/* ----------------------------------------------------------------------
   return # of bytes of allocated memory in buf, choose, variable arrays
------------------------------------------------------------------------- */

double DumpGrid::memory_usage()
{
  double bytes = Dump::memory_usage();
  return bytes;
}

/* ----------------------------------------------------------------------
   extraction of 2d and 3d grid data
   from either compute or fix
------------------------------------------------------------------------- */

void DumpGrid::pack_grid2d(int n)
{
  int index = argindex[n];

  if (index == 0) {
    double **vec2d;
    if (field2source[n] == COMPUTE)
      vec2d = (double **) compute[field2index[n]]->get_griddata_by_index(field2data[n]);
    else if (field2source[n] == FIX)
      vec2d = (double **) fix[field2index[n]]->get_griddata_by_index(field2data[n]);
    else error->all(FLERR, "Unsupported grid data source type {}", field2source[n]);
    for (int iy = nylo_in; iy <= nyhi_in; iy++)
      for (int ix = nxlo_in; ix <= nxhi_in; ix++) {
        buf[n] = vec2d[iy][ix];
        n += size_one;
      }
  } else {
    double ***array2d;
    if (field2source[n] == COMPUTE)
      array2d = (double ***) compute[field2index[n]]->get_griddata_by_index(field2data[n]);
    else if (field2source[n] == FIX)
      array2d = (double ***) fix[field2index[n]]->get_griddata_by_index(field2data[n]);
    else error->all(FLERR, "Unsupported grid data source type {}", field2source[n]);
    index--;
    for (int iy = nylo_in; iy <= nyhi_in; iy++)
      for (int ix = nxlo_in; ix <= nxhi_in; ix++) {
        buf[n] = array2d[iy][ix][index];
        n += size_one;
      }
  }
}

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

void DumpGrid::pack_grid3d(int n)
{
  int index = argindex[n];

  if (index == 0) {
    double ***vec3d;
    if (field2source[n] == COMPUTE)
      vec3d = (double ***) compute[field2index[n]]->get_griddata_by_index(field2data[n]);
    else if (field2source[n] == FIX)
      vec3d = (double ***) fix[field2index[n]]->get_griddata_by_index(field2data[n]);
    else error->all(FLERR, "Unsupported grid data source type {}", field2source[n]);
    for (int iz = nzlo_in; iz <= nzhi_in; iz++)
      for (int iy = nylo_in; iy <= nyhi_in; iy++)
        for (int ix = nxlo_in; ix <= nxhi_in; ix++) {
          buf[n] = vec3d[iz][iy][ix];
          n += size_one;
        }
  } else {
    double ****array3d;
    if (field2source[n] == COMPUTE)
      array3d = (double ****) compute[field2index[n]]->get_griddata_by_index(field2data[n]);
    else if (field2source[n] == FIX)
      array3d = (double ****) fix[field2index[n]]->get_griddata_by_index(field2data[n]);
    else error->all(FLERR, "Unsupported grid data source type {}", field2source[n]);
    index--;
    for (int iz = nzlo_in; iz <= nzhi_in; iz++)
      for (int iy = nylo_in; iy <= nyhi_in; iy++)
        for (int ix = nxlo_in; ix <= nxhi_in; ix++) {
          buf[n] = array3d[iz][iy][ix][index];
          n += size_one;
        }
  }
}
