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

#include "compute.h"
#include "domain.h"
#include "error.h"
#include "force.h"
#include "group.h"
#include "modify.h"
#include "update.h"

#include <cmath>
#include <cstring>

using namespace LAMMPS_NS;
using namespace FixConst;

enum{ISO,ANISO,TRICLINIC}; // same as fix_nh.cpp

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

FixNPHug::FixNPHug(LAMMPS *lmp, int narg, char **arg) :
  FixNH(lmp, narg, arg), pe(nullptr), id_pe(nullptr)
{
  // Prevent masses from being updated every timestep

  eta_mass_flag = 0;
  omega_mass_flag = 0;
  etap_mass_flag = 0;

  // extend vector of base-class computes

  size_vector += 3;

  // turn off deviatoric flag and remove strain energy from vector

  deviatoric_flag = 0;
  size_vector -= 1;

  // use initial state as reference state

  v0_set = p0_set = e0_set = 0;

  // check pressure settings

  if (p_start[0] != p_stop[0] ||
      p_start[1] != p_stop[1] ||
      p_start[2] != p_stop[2])
    error->all(FLERR,"Pstart and Pstop must have the same value");

  // uniaxial = 0 means hydrostatic compression
  // uniaxial = 1 means uniaxial compression
  //      in x, y, or z (idir = 0, 1, or 2)

  // isotropic hydrostatic compression

  if (pstyle == ISO) {
    uniaxial = 0;

    // anisotropic compression

  } else if (pstyle == ANISO) {

    // anisotropic hydrostatic compression

    if (p_start[0] == p_start[1] &&
        p_start[0] == p_start[2] )
      uniaxial = 0;

    // uniaxial compression

    else if (p_flag[0] == 1 && p_flag[1] == 0
        && p_flag[2] == 0) {
      uniaxial = 1;
      idir = 0;
    } else if (p_flag[0] == 0 && p_flag[1] == 1
           && p_flag[2] == 0) {
      uniaxial = 1;
      idir = 1;
    } else if (p_flag[0] == 0 && p_flag[1] == 0
               && p_flag[2] == 1) {
      uniaxial = 1;
      idir = 2;

    } else error->all(FLERR,"Specified target stress must be uniaxial or hydrostatic");

    // triclinic hydrostatic compression

  } else if (pstyle == TRICLINIC) {

    if (p_start[0] == p_start[1] &&
        p_start[0] == p_start[2] &&
        p_start[3] == 0.0 &&
        p_start[4] == 0.0 &&
        p_start[5] == 0.0 )
      uniaxial = 0;

    else error->all(FLERR,"For triclinic deformation, specified target stress must be hydrostatic");
  }

  if (!tstat_flag)
    error->all(FLERR,"Temperature control must be used with fix nphug");
  if (!pstat_flag)
    error->all(FLERR,"Pressure control must be used with fix nphug");

  // create a new compute temp style
  // id = fix-ID + temp
  // compute group = all since pressure is always global (group all)
  // and thus its KE/temperature contribution should use group all

  id_temp = utils::strdup(std::string(id)+"_temp");
  modify->add_compute(fmt::format("{} all temp",id_temp));
  tcomputeflag = 1;

  // create a new compute pressure style
  // id = fix-ID + press, compute group = all
  // pass id_temp as 4th arg to pressure constructor

  id_press = utils::strdup(std::string(id)+"_press");
  modify->add_compute(fmt::format("{} all pressure {}",id_press,id_temp));
  pcomputeflag = 1;

  // create a new compute potential energy compute

  id_pe = utils::strdup(std::string(id)+"_pe");
  modify->add_compute(fmt::format("{} all pe",id_pe));
  peflag = 1;
}

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

FixNPHug::~FixNPHug()
{
  // temp and press computes handled by base class
  // delete pe compute

  if (peflag) modify->delete_compute(id_pe);
  delete[] id_pe;
}

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

void FixNPHug::init()
{
  // Call base class init()

  FixNH::init();

  // set pe ptr

  pe = modify->get_compute_by_id(id_pe);
  if (!pe) {
    error->all(FLERR, "Potential energy compute ID {} for fix {} does not exist", id_pe, style);
  } else {
    if (pe->peflag == 0)
      error->all(FLERR, "Compute ID {} for fix {} does not compute potential energy", id_pe, style);
  }
}


/* ----------------------------------------------------------------------
   compute initial state before integrator starts
------------------------------------------------------------------------- */

void FixNPHug::setup(int vflag)
{
  FixNH::setup(vflag);

  if (v0_set == 0) {
    v0 = compute_vol();
    v0_set = 1;
  }

  if (p0_set == 0) {
    p0_set = 1;
    if (uniaxial == 1)
      p0 = p_current[idir];
    else
      p0 = (p_current[0]+p_current[1]+p_current[2])/3.0;
  }

  if (e0_set == 0) {
    e0 = compute_etotal();
    e0_set = 1;
  }

  double masstot = group->mass(igroup);
  rho0 = nktv2p*force->mvv2e*masstot/v0;

  t_target = 0.01;
  ke_target = tdof*boltz*t_target;

  pe->addstep(update->ntimestep+1);
}

/* ----------------------------------------------------------------------
   compute target temperature and kinetic energy
-----------------------------------------------------------------------*/

void FixNPHug::compute_temp_target()
{
  t_target = t_current + compute_hugoniot();
  ke_target = tdof * boltz * t_target;
  pe->addstep(update->ntimestep+1);
}

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

double FixNPHug::compute_etotal()
{
  if (!pe) return 0.0;

  double epot,ekin,etot;
  epot = pe->compute_scalar();
  ekin = temperature->compute_scalar();
  ekin *= 0.5 * tdof * force->boltz;
  etot = epot+ekin;
  return etot;
}

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

double FixNPHug::compute_vol()
{
  if (domain->dimension == 3)
    return domain->xprd * domain->yprd * domain->zprd;
  else
    return domain->xprd * domain->yprd;
}

/* ----------------------------------------------------------------------
   Computes the deviation of the current point
   from the Hugoniot in temperature units.
------------------------------------------------------------------------- */

double FixNPHug::compute_hugoniot()
{
  if (!temperature) return 0.0;

  double v,e,p;
  double dhugo;

  e = compute_etotal();

  temperature->compute_vector();


  if (uniaxial == 1) {
    pressure->compute_vector();
    p = pressure->vector[idir];
  } else
    p = pressure->compute_scalar();

  v = compute_vol();

  dhugo = (0.5 * (p + p0 ) * ( v0 - v)) /
    force->nktv2p + e0 - e;

  dhugo /= tdof * boltz;

  return dhugo;
}

/* ----------------------------------------------------------------------
   Compute shock velocity is distance/time units
------------------------------------------------------------------------- */

double FixNPHug::compute_us()
{
  if (!temperature) return 0.0;

  double v,p;
  double eps,us;

  temperature->compute_vector();

  if (uniaxial == 1) {
    pressure->compute_vector();
    p = pressure->vector[idir];
  } else
    p = pressure->compute_scalar();

  v = compute_vol();

  // Us^2 = (p-p0)/(rho0*eps)

  eps = 1.0 - v/v0;
  if (eps < 1.0e-10) us = 0.0;
  else if (p < p0) us = 0.0;
  else us = sqrt((p-p0)/(rho0*eps));

  return us;
}

/* ----------------------------------------------------------------------
   Compute particle velocity is distance/time units
------------------------------------------------------------------------- */

double FixNPHug::compute_up()
{
  double v;
  double eps,us,up;

  v = compute_vol();
  us = compute_us();

  // u = eps*Us

  eps = 1.0 - v/v0;
  up = us*eps;

  return up;
}

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

// look for index in local class
// if index not found, look in base class

double FixNPHug::compute_vector(int n)
{
  int ilen;

  // n = 0: Hugoniot energy difference (temperature units)

  ilen = 1;
  if (n < ilen) return compute_hugoniot();
  n -= ilen;

  // n = 1: Shock velocity

  ilen = 1;
  if (n < ilen) return compute_us();
  n -= ilen;

  // n = 2: Particle velocity

  ilen = 1;
  if (n < ilen) return compute_up();
  n -= ilen;

  // index not found, look in base class

  return FixNH::compute_vector(n);
}

/* ----------------------------------------------------------------------
   pack restart data
------------------------------------------------------------------------- */

int FixNPHug::pack_restart_data(double *list)
{
  int n = 0;

  list[n++] = e0;
  list[n++] = v0;
  list[n++] = p0;

  // call the base class function

  n += FixNH::pack_restart_data(list+n);

  return n;
}

/* ----------------------------------------------------------------------
   calculate the number of data to be packed
------------------------------------------------------------------------- */

int FixNPHug::size_restart_global()
{
  int nsize = 3;

  // call the base class function

  nsize += FixNH::size_restart_global();

  return nsize;
}

/* ----------------------------------------------------------------------
   use state info from restart file to restart the Fix
------------------------------------------------------------------------- */

void FixNPHug::restart(char *buf)
{
  int n = 0;
  auto list = (double *) buf;
  e0 = list[n++];
  v0 = list[n++];
  p0 = list[n++];

  e0_set = 1;
  v0_set = 1;
  p0_set = 1;

  // call the base class function

  buf += n*sizeof(double);
  FixNH::restart(buf);

}

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

int FixNPHug::modify_param(int narg, char **arg)
{
  if (strcmp(arg[0],"e0") == 0) {
    if (narg < 2) error->all(FLERR,"Illegal fix nphug command");
    e0 = utils::numeric(FLERR,arg[1],false,lmp);
    e0_set = 1;
    return 2;
  } else if (strcmp(arg[0],"v0") == 0) {
    if (narg < 2) error->all(FLERR,"Illegal fix nphug command");
    v0 = utils::numeric(FLERR,arg[1],false,lmp);
    v0_set = 1;
    return 2;
  } else if (strcmp(arg[0],"p0") == 0) {
    if (narg < 2) error->all(FLERR,"Illegal fix nphug command");
    p0 = utils::numeric(FLERR,arg[1],false,lmp);
    p0_set = 1;
    return 2;
  }

  return 0;
}
