/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   http://lammps.sandia.gov, Sandia National Laboratories
   Steve Plimpton, sjplimp@sandia.gov

   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: Christian Negre (LANL)
------------------------------------------------------------------------- */

#include "fix_latte.h"
#include <cstdio>
#include <cstring>
#include "atom.h"
#include "comm.h"
#include "update.h"
#include "neighbor.h"
#include "domain.h"
#include "force.h"
#include "neigh_request.h"
#include "neigh_list.h"
#include "modify.h"
#include "compute.h"
#include "memory.h"
#include "error.h"

using namespace LAMMPS_NS;
using namespace FixConst;

extern "C" {
  void latte(int *, int *, double *, int *, int *,
             double *, double *, double *, double *,
             double *, double *, double *, int *,
             double *, double *, double *, double *, int * , bool *);
  int latte_abiversion();
}

// the ABIVERSION number here must be kept consistent
// with its counterpart in the LATTE library and the
// prototype above. We want to catch mismatches with
// a meaningful error messages, as they can cause
// difficult to debug crashes or memory corruption.

#define LATTE_ABIVERSION 20180622
#define INVOKED_PERATOM 8

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

FixLatte::FixLatte(LAMMPS *lmp, int narg, char **arg) :
  Fix(lmp, narg, arg)
{
  if (strcmp(update->unit_style,"metal") != 0)
    error->all(FLERR,"Must use units metal with fix latte command");

  if (comm->nprocs != 1)
    error->all(FLERR,"Fix latte currently runs only in serial");

  if (LATTE_ABIVERSION != latte_abiversion())
    error->all(FLERR,"LAMMPS is linked against incompatible LATTE library");

  if (narg != 4) error->all(FLERR,"Illegal fix latte command");

  scalar_flag = 1;
  global_freq = 1;
  extscalar = 1;
  virial_flag = 1;
  thermo_virial = 1;

  // store ID of compute pe/atom used to generate Coulomb potential for LATTE
  // NULL means LATTE will compute Coulombic potential

  coulomb = 0;
  id_pe = NULL;

  if (strcmp(arg[3],"NULL") != 0) {
    coulomb = 1;
    error->all(FLERR,"Fix latte does not yet support a LAMMPS calculation "
               "of a Coulomb potential");

    int n = strlen(arg[3]) + 1;
    id_pe = new char[n];
    strcpy(id_pe,arg[3]);

    int ipe = modify->find_compute(id_pe);
    if (ipe < 0) error->all(FLERR,"Could not find fix latte compute ID");
    if (modify->compute[ipe]->peatomflag == 0)
      error->all(FLERR,"Fix latte compute ID does not compute pe/atom");
  }

  // initializations

  nmax = 0;
  qpotential = NULL;
  flatte = NULL;

  latte_energy = 0.0;
}

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

FixLatte::~FixLatte()
{
  delete [] id_pe;
  memory->destroy(qpotential);
  memory->destroy(flatte);
}

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

int FixLatte::setmask()
{
  int mask = 0;
  //mask |= INITIAL_INTEGRATE;
  //mask |= FINAL_INTEGRATE;
  mask |= PRE_REVERSE;
  mask |= POST_FORCE;
  mask |= MIN_POST_FORCE;
  mask |= THERMO_ENERGY;
  return mask;
}

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

void FixLatte::init()
{
  // error checks

  if (domain->dimension == 2)
    error->all(FLERR,"Fix latte requires 3d problem");

  if (coulomb) {
    if (atom->q_flag == 0 || force->pair == NULL || force->kspace == NULL)
      error->all(FLERR,"Fix latte cannot compute Coulomb potential");

    int ipe = modify->find_compute(id_pe);
    if (ipe < 0) error->all(FLERR,"Could not find fix latte compute ID");
    c_pe = modify->compute[ipe];
  }

  // must be fully periodic or fully non-periodic

  if (domain->nonperiodic == 0) pbcflag = 1;
  else if (!domain->xperiodic && !domain->yperiodic && !domain->zperiodic)
    pbcflag = 0;
  else error->all(FLERR,"Fix latte requires 3d simulation");

  // create qpotential & flatte if needed
  // for now, assume nlocal will never change

  if (coulomb && qpotential == NULL) {
    memory->create(qpotential,atom->nlocal,"latte:qpotential");
    memory->create(flatte,atom->nlocal,3,"latte:flatte");
  }

  /*
  // warn if any integrate fix comes after this one
  // is it actually necessary for q(n) update to come after x,v update ??

  int after = 0;
  int flag = 0;
  for (int i = 0; i < modify->nfix; i++) {
    if (strcmp(id,modify->fix[i]->id) == 0) after = 1;
    else if ((modify->fmask[i] & INITIAL_INTEGRATE) && after) flag = 1;
  }
  if (flag && comm->me == 0)
    error->warning(FLERR,"Fix latte should come after all other "
                   "integration fixes");
  */

  /*
  // need a full neighbor list
  // could we use a half list?
  // perpetual list, built whenever re-neighboring occurs

  int irequest = neighbor->request(this,instance_me);
  neighbor->requests[irequest]->pair = 0;
  neighbor->requests[irequest]->fix = 1;
  neighbor->requests[irequest]->half = 0;
  neighbor->requests[irequest]->full = 1;
  */
}

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

void FixLatte::init_list(int /*id*/, NeighList * /*ptr*/)
{
  // list = ptr;
}

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

void FixLatte::setup(int vflag)
{
  newsystem = 1;
  post_force(vflag);
  newsystem = 0;
}

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

void FixLatte::min_setup(int vflag)
{
  newsystem = 1;
  post_force(vflag);
  newsystem = 0;
}

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

void FixLatte::setup_pre_reverse(int eflag, int vflag)
{
  pre_reverse(eflag,vflag);
}

/* ----------------------------------------------------------------------
   integrate electronic degrees of freedom
------------------------------------------------------------------------- */

void FixLatte::initial_integrate(int /*vflag*/) {}

/* ----------------------------------------------------------------------
   store eflag, so can use it in post_force to tally per-atom energies
------------------------------------------------------------------------- */

void FixLatte::pre_reverse(int eflag, int /*vflag*/)
{
  eflag_caller = eflag;
}

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

void FixLatte::post_force(int vflag)
{
  int eflag = eflag_caller;
  ev_init(eflag,vflag);

  // compute Coulombic potential = pe[i]/q[i]
  // invoke compute pe/atom
  // wrap with clear/add and trigger pe/atom calculation every step

  if (coulomb) {
    modify->clearstep_compute();

    if (!(c_pe->invoked_flag & INVOKED_PERATOM)) {
      c_pe->compute_peratom();
      c_pe->invoked_flag |= INVOKED_PERATOM;
    }

    modify->addstep_compute(update->ntimestep+1);

    double *pe = c_pe->vector_atom;
    double *q = atom->q;
    int nlocal = atom->nlocal;

    for (int i = 0; i < nlocal; i++)
      if (q[i]) qpotential[i] = pe[i]/q[i];
      else qpotential[i] = 0.0;
  }

  // hardwire these unsupported flags for now

  int coulombflag = 0;
  // pe_peratom = 0;
  // virial_global = 1;              // set via vflag_global at some point
  // virial_peratom = 0;
  neighflag = 0;

  // set flags used by LATTE
  // NOTE: LATTE does not compute per-atom energies or virials

  int flags[6];

  flags[0] = pbcflag;         // 1 for fully periodic, 0 for fully non-periodic
  flags[1] = coulombflag;     // 1 for LAMMPS computes Coulombics, 0 for LATTE
  flags[2] = eflag_atom;      // 1 to return per-atom energies, 0 for no
  flags[3] = vflag_global && thermo_virial;    // 1 to return global/per-atom
  flags[4] = vflag_atom && thermo_virial;      //   virial, 0 for no
  flags[5] = neighflag;       // 1 to pass neighbor list to LATTE, 0 for no

  // setup LATTE arguments

  int natoms = atom->nlocal;
  double *coords = &atom->x[0][0];
  int *type = atom->type;
  int ntypes = atom->ntypes;
  double *mass = &atom->mass[1];
  double *boxlo = domain->boxlo;
  double *boxhi = domain->boxhi;
  double *forces;
  bool latteerror = 0;
  if (coulomb) forces = &flatte[0][0];
  else forces = &atom->f[0][0];
  int maxiter = -1;

  latte(flags,&natoms,coords,type,&ntypes,mass,boxlo,boxhi,&domain->xy,
        &domain->xz,&domain->yz,forces,&maxiter,&latte_energy,
        &atom->v[0][0],&update->dt,virial,&newsystem,&latteerror);

  if (latteerror) error->all(FLERR,"Internal LATTE problem");

  // sum LATTE forces to LAMMPS forces
  // e.g. LAMMPS may compute Coulombics at some point

  if (coulomb) {
    double **f = atom->f;
    int nlocal = atom->nlocal;
    for (int i = 0; i < nlocal; i++) {
      f[i][0] += flatte[i][0];
      f[i][1] += flatte[i][1];
      f[i][2] += flatte[i][2];
    }
  }
}

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

void FixLatte::min_post_force(int vflag)
{
  post_force(vflag);
}

/* ----------------------------------------------------------------------
   integrate electronic degrees of freedom
------------------------------------------------------------------------- */

void FixLatte::final_integrate() {}

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

void FixLatte::reset_dt()
{
  //dtv = update->dt;
  //dtf = 0.5 * update->dt * force->ftm2v;
}

/* ----------------------------------------------------------------------
   DFTB energy from LATTE
------------------------------------------------------------------------- */

double FixLatte::compute_scalar()
{
  return latte_energy;
}

/* ----------------------------------------------------------------------
   memory usage of local arrays
------------------------------------------------------------------------- */

double FixLatte::memory_usage()
{
  double bytes = 0.0;
  if (coulomb) bytes += nmax * sizeof(double);
  if (coulomb) bytes += nmax*3 * sizeof(double);
  return bytes;
}
