/* ----------------------------------------------------------------------
   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.
------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   Contributing author: Axel Kohlmeyer (Temple U)
------------------------------------------------------------------------- */

#include "reader_xyz.h"

#include "atom.h"
#include "error.h"
#include "exceptions.h"
#include "label_map.h"
#include "memory.h"
#include "tokenizer.h"

using namespace LAMMPS_NS;

static constexpr int MAXLINE = 1024;    // max line length in dump file

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

ReaderXYZ::ReaderXYZ(LAMMPS *lmp) : Reader(lmp)
{
  line = new char[MAXLINE];
  fieldindex = nullptr;
  nstep = 0;
}

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

ReaderXYZ::~ReaderXYZ()
{
  delete[] line;
  memory->destroy(fieldindex);
}

/* ----------------------------------------------------------------------
   read and return time stamp from dump file
   if first read reaches end-of-file, return 1 so caller can open next file
   only called by proc 0
------------------------------------------------------------------------- */

int ReaderXYZ::read_time(bigint &ntimestep)
{
  char *eof = fgets(line, MAXLINE, fp);
  if (eof == nullptr) return 1;

  // first line has to have the number of atoms
  // truncate the string to the first whitespace,
  //   so force->bnumeric() does not hiccup

  for (int i = 0; (i < MAXLINE) && (eof[i] != '\0'); ++i) {
    if (eof[i] == '\n' || eof[i] == '\r' || eof[i] == ' ' || eof[i] == '\t') {
      eof[i] = '\0';
      break;
    }
  }
  natoms = utils::bnumeric(FLERR, line, false, lmp);
  if (natoms < 1) error->one(FLERR, "Dump file is incorrectly formatted");

  // skip over comment/title line

  read_lines(1);

  // fake time step numbers

  ntimestep = nstep;

  // count this frame

  ++nstep;
  return 0;
}

/* ----------------------------------------------------------------------
   skip snapshot from timestamp onward
   only called by proc 0
------------------------------------------------------------------------- */

void ReaderXYZ::skip()
{
  // invoke read_lines() in chunks no larger than MAXSMALLINT

  bigint nchunk;
  bigint nremain = natoms;
  while (nremain) {
    nchunk = MIN(nremain, MAXSMALLINT);
    read_lines((int) nchunk);
    nremain -= nchunk;
  }
}

/* ----------------------------------------------------------------------
   read remaining header info:
     return natoms
     box bounds, triclinic (inferred), fieldflag (1 if any fields not found),
     xyz flags = from input scaleflag & wrapflag
   if fieldflag set:
     match Nfield fields to per-atom column labels
     allocate and set fieldindex = which column each field maps to
     fieldtype = X,VX,IZ etc
     fieldlabel = user-specified label or nullptr if use fieldtype default
   xyz flag = scaledflag if has fieldlabel name, else set by x,xs,xu,xsu
   only called by proc 0
------------------------------------------------------------------------- */

bigint ReaderXYZ::read_header(double /*box*/[3][3], int &boxinfo, int & /*triclinic*/,
                              int fieldinfo, int nfield, int *fieldtype, char ** /*fieldlabel*/,
                              int scaleflag, int wrapflag, int &fieldflag, int &xflag, int &yflag,
                              int &zflag)
{

  nid = 0;

  // signal that we have no box info at all

  boxinfo = 0;

  // if no field info requested, just return

  if (!fieldinfo) return natoms;

  memory->create(fieldindex, nfield, "read_dump:fieldindex");

  // for xyz we know nothing about the style of coordinates,
  // so caller has to set the proper flags

  xflag = 2 * scaleflag + wrapflag + 1;
  yflag = 2 * scaleflag + wrapflag + 1;
  zflag = 2 * scaleflag + wrapflag + 1;

  // copy fieldtype list for supported fields

  fieldflag = 0;
  for (int i = 0; i < nfield; i++) {
    if ((fieldtype[i] == X) || (fieldtype[i] == Y) || (fieldtype[i] == Z) || (fieldtype[i] == ID) ||
        (fieldtype[i] == TYPE)) {
      fieldindex[i] = fieldtype[i];
    } else {
      fieldflag = 1;
    }
  }

  return natoms;
}

/* ----------------------------------------------------------------------
   read N atom lines from dump file
   stores appropriate values in fields array
   return 0 if success, 1 if error
   only called by proc 0
------------------------------------------------------------------------- */

void ReaderXYZ::read_atoms(int n, int nfield, double **fields)
{
  int i, m;
  char *eof;
  int mytype;
  double myx, myy, myz;

  try {
    for (i = 0; i < n; i++) {
      eof = fgets(line, MAXLINE, fp);
      if (eof == nullptr) error->one(FLERR, "Unexpected end of dump file");

      ++nid;

      auto values = ValueTokenizer(line);
      auto label = values.next_string();

      // must have a complete atom type label map to parse xyz files with string labels
      if (!utils::is_integer(label)) {
        if (!atom->labelmapflag || !atom->lmap->is_complete(Atom::ATOM))
          error->one(FLERR, "Must define atom labelmap to parse XYZ files with strings for types");
      }
      mytype = utils::expand_type_int(FLERR, label, Atom::ATOM, lmp);
      myx = values.next_double();
      myy = values.next_double();
      myz = values.next_double();

      for (m = 0; m < nfield; m++) {
        switch (fieldindex[m]) {
          case X:
            fields[i][m] = myx;
            break;
          case Y:
            fields[i][m] = myy;
            break;
          case Z:
            fields[i][m] = myz;
            break;
          case ID:
            fields[i][m] = nid;
            break;
          case TYPE:
            fields[i][m] = mytype;
            break;
        }
      }
    }
  } catch (TokenizerException &e) {
    error->one(FLERR, "Error reading xyz file: {}", e.what());
  } catch (LAMMPSAbortException &e) {
    throw e;
  }
}

/* ----------------------------------------------------------------------
   read N lines from dump file
   only last one is saved in line
   only called by proc 0
------------------------------------------------------------------------- */

void ReaderXYZ::read_lines(int n)
{
  char *eof = nullptr;
  if (n <= 0) return;
  for (int i = 0; i < n; i++) eof = fgets(line, MAXLINE, fp);
  if (eof == nullptr) error->one(FLERR, "Unexpected end of dump file");
}
