/* ----------------------------------------------------------------------
   LIGGGHTS - LAMMPS Improved for General Granular and Granular Heat
   Transfer Simulations

   LIGGGHTS is part of the CFDEMproject
   www.liggghts.com | www.cfdem.com

   Christoph Kloss, christoph.kloss@cfdem.com
   Copyright 2009-2012 JKU Linz
   Copyright 2012-     DCS Computing GmbH, Linz

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

   This software is distributed under the GNU General Public License.

   See the README file in the top-level directory.
------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   Contributing authors:
   Christoph Kloss (JKU Linz, DCS Computing GmbH, Linz)
   Richard Berger (JKU Linz)
------------------------------------------------------------------------- */
#include "global_properties.h"
#include <cmath>
#include <cstring>
#include <algorithm>
#include "update.h"
#include "error.h"
#include "neighbor.h"
#include "force.h"

using namespace std;

namespace MODEL_PARAMS
{
  static const char * COHESION_ENERGY_DENSITY = "cohesionEnergyDensity";
  static const char * CHARACTERISTIC_VELOCITY = "characteristicVelocity";
  static const char * YOUNGS_MODULUS = "youngsModulus";
  static const char * POISSONS_RATIO = "poissonsRatio";
  static const char * COEFFICIENT_RESTITUTION = "coefficientRestitution";
  static const char * COEFFICIENT_RESTITUTION_LOG = "coefficientRestitutionLog";
  static const char * COEFFICIENT_FRICTION = "coefficientFriction";
  static const char * COEFFICIENT_ROLL_FRICTION = "coefficientRollingFriction";
  static const char * COEFFICIENT_ROLL_VISCOUS_DAMPING = "coefficientRollingViscousDamping";
  static const char * FLUID_VISCOSITY = "FluidViscosity";
  static const char * MAXIMUM_RESTITUTION = "MaximumRestitution";
  static const char * CRITITCAL_STOKES = "CriticalStokes";
  static const char * HAMAKER_CONSTANT = "hamakerConstant";
  static const char * MINIMUM_PARTICLE_DISTANCE = "minParticleDist";
  static const char * LIQUID_VOLUME = "liquidVolume";
  static const char * SURFACE_TENSION = "surfaceTension";

  static const char * COEFFICIENT_MAX_ELASTIC_STIFFNESS = "coefficientMaxElasticStiffness";
  static const char * COEFFICIENT_ADHESION_STIFFNESS = "coefficientAdhesionStiffness";
  static const char * COEFFICIENT_PLASTICITY_DEPTH = "coefficientPlasticityDepth";

  /* -----------------------------------------------------------------------
   * Utility functions
   * ----------------------------------------------------------------------- */

  ScalarProperty* createScalarProperty(PropertyRegistry & registry, const char* name, const char * caller)
  {
    ScalarProperty * scalar = new ScalarProperty();
    FixPropertyGlobal * property = registry.getGlobalProperty(name,"property/global","scalar",0,0,caller);
    scalar->data = property->compute_scalar();
    return scalar;
  }

  MatrixProperty* createPerTypePairProperty(PropertyRegistry & registry, const char * name, const char * caller)
  {
    const int max_type = registry.max_type();

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    FixPropertyGlobal *   property = registry.getGlobalProperty(name,"property/global","peratomtypepair",max_type,max_type,caller);

    for(int i = 1; i < max_type+1; i++)
    {
      for(int j = 1; j < max_type+1; j++)
      {
         matrix->data[i][j] = property->compute_array(i-1,j-1);
      }
    }

    return matrix;
  }

  /* -----------------------------------------------------------------------
   * Property Creators
   * ----------------------------------------------------------------------- */

  ScalarProperty* createCharacteristicVelocity(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    ScalarProperty* charVelScalar = createScalarProperty(registry, CHARACTERISTIC_VELOCITY, caller);
    double charVel = charVelScalar->data;
    
    if(sanity_checks)
    {
      if(strcmp(lmp->update->unit_style,"si") == 0  && charVel < 1e-2)
        lmp->error->all(FLERR,"characteristicVelocity >= 1e-2 required for SI units");
    }

    return charVelScalar;
  }

  MatrixProperty* createCohesionEnergyDensity(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
    return createPerTypePairProperty(registry, COHESION_ENERGY_DENSITY, caller);
  }

  VectorProperty * createYoungsModulus(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    VectorProperty * vec = new VectorProperty(max_type+1);
    FixPropertyGlobal * Y = registry.getGlobalProperty(YOUNGS_MODULUS,"property/global","peratomtype",max_type,0,caller);

    for(int i=1; i < max_type+1; i++)
    {
      const double Yi = Y->compute_vector(i-1);

      // error checks on Y
      if(sanity_checks)
      {
        if(strcmp(lmp->update->unit_style,"si") == 0  && Yi < 5e6)
          lmp->error->all(FLERR,"youngsModulus >= 5e6 required for SI units");
        if(strcmp(lmp->update->unit_style,"cgs") == 0 && Yi < 5e5)
          lmp->error->all(FLERR,"youngsModulus >= 5e5 required for CGS units");
      }

      vec->data[i] = Yi;
    }

    return vec;
  }

  VectorProperty * createPoissonsRatio(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    VectorProperty * vec = new VectorProperty(max_type+1);
    FixPropertyGlobal * v = registry.getGlobalProperty(POISSONS_RATIO,"property/global","peratomtype",max_type,0,caller);

    for(int i=1; i < max_type+1; i++)
    {
      const double vi = v->compute_vector(i-1);

      // error checks on v
      if(sanity_checks)
      {
        if(vi < 0. || vi > 0.5)
          lmp->error->all(FLERR,"0 <= poissonsRatio <= 0.5 required");
      }

      vec->data[i] = vi;
    }

    return vec;
  }

  MatrixProperty * createYeff(PropertyRegistry & registry, const char * caller, bool)
  {
    const int max_type = registry.max_type();

    registry.registerProperty(YOUNGS_MODULUS, &createYoungsModulus);
    registry.registerProperty(POISSONS_RATIO, &createPoissonsRatio);

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    VectorProperty * youngsModulus = registry.getVectorProperty(YOUNGS_MODULUS);
    VectorProperty * poissonRatio = registry.getVectorProperty(POISSONS_RATIO);
    double * Y = youngsModulus->data;
    double * v = poissonRatio->data;

    for(int i=1;i< max_type+1; i++)
    {
      for(int j=1;j<max_type+1;j++)
      {
        const double Yi=Y[i];
        const double Yj=Y[j];
        const double vi=v[i];
        const double vj=v[j];
        matrix->data[i][j] = 1./((1.-pow(vi,2.))/Yi+(1.-pow(vj,2.))/Yj);
      }
    }

    return matrix;
  }

  MatrixProperty * createGeff(PropertyRegistry & registry, const char * caller, bool)
  {
    const int max_type = registry.max_type();

    registry.registerProperty(YOUNGS_MODULUS, &createYoungsModulus);
    registry.registerProperty(POISSONS_RATIO, &createPoissonsRatio);

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    VectorProperty * youngsModulus = registry.getVectorProperty(YOUNGS_MODULUS);
    VectorProperty * poissonRatio = registry.getVectorProperty(POISSONS_RATIO);
    double * Y = youngsModulus->data;
    double * v = poissonRatio->data;

    for(int i=1;i< max_type+1; i++)
    {
      for(int j=1;j<max_type+1;j++)
      {
        const double Yi=Y[i];
        const double Yj=Y[j];
        const double vi=v[i];
        const double vj=v[j];

        matrix->data[i][j] = 1./(2.*(2.-vi)*(1.+vi)/Yi+2.*(2.-vj)*(1.+vj)/Yj);
      }
    }

    return matrix;
  }

  MatrixProperty * createCoeffRest(PropertyRegistry & registry, const char * caller, bool sanity_checks)
   {
     LAMMPS * lmp = registry.getLAMMPS();
     const int max_type = registry.max_type();

     MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
     FixPropertyGlobal * coeffRest = registry.getGlobalProperty(COEFFICIENT_RESTITUTION,"property/global","peratomtypepair",max_type,max_type,caller);

     for(int i=1;i< max_type+1; i++)
     {
       for(int j=1;j<max_type+1;j++)
       {
         const double cor = coeffRest->compute_array(i-1,j-1);

         if(sanity_checks)
         {
           if(cor <= 0.05 || cor > 1) {
             lmp->error->all(FLERR,"0.05 < coefficientRestitution <= 1 required");
           }
         }

         matrix->data[i][j] = cor;
       }
     }

     return matrix;
   }

  MatrixProperty * createCoeffRestLog(PropertyRegistry & registry, const char * caller, bool)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    registry.registerProperty(COEFFICIENT_RESTITUTION, &createCoeffRest);

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    MatrixProperty * coeffRestProp = registry.getMatrixProperty(COEFFICIENT_RESTITUTION);
    double ** coeffRest = coeffRestProp->data;

    for(int i=1;i< max_type+1; i++)
    {
      for(int j=1;j<max_type+1;j++)
      {
        matrix->data[i][j] = log(coeffRest[i][j]);
      }
    }

    return matrix;
  }

  MatrixProperty * createBetaEff(PropertyRegistry & registry, const char * caller, bool)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    registry.registerProperty(COEFFICIENT_RESTITUTION_LOG, &createCoeffRestLog);

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    MatrixProperty * coeffRestLogProp = registry.getMatrixProperty(COEFFICIENT_RESTITUTION_LOG);
    double ** coeffRestLog = coeffRestLogProp->data;

    for(int i=1;i< max_type+1; i++)
    {
      for(int j=1;j<max_type+1;j++)
      {
        matrix->data[i][j] = coeffRestLog[i][j] / sqrt(pow(coeffRestLog[i][j],2.)+pow(M_PI,2.));
      }
    }

    return matrix;
  }

  MatrixProperty* createCoeffFrict(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, COEFFICIENT_FRICTION, caller);
  }

  MatrixProperty* createCoeffRollFrict(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, COEFFICIENT_ROLL_FRICTION, caller);
  }

  MatrixProperty* createCoeffRollVisc(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, COEFFICIENT_ROLL_VISCOUS_DAMPING, caller);
  }

  MatrixProperty * createCoeffMu(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    FixPropertyGlobal * coeffMu = registry.getGlobalProperty(FLUID_VISCOSITY,"property/global","peratomtypepair",max_type,max_type,caller);

    for(int i=1;i< max_type+1; i++)
    {
     for(int j=1;j<max_type+1;j++)
     {
       const double mu = coeffMu->compute_array(i-1,j-1);

       if(sanity_checks)
       {
         if(mu <= 0.)
          lmp->error->all(FLERR,"coeffMu > 0 required");
       }

       matrix->data[i][j] = mu;
     }
    }

    return matrix;
  }

  MatrixProperty * createCoeffRestMax(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
   LAMMPS * lmp = registry.getLAMMPS();
   const int max_type = registry.max_type();

   MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
   FixPropertyGlobal * coeffRestMax = registry.getGlobalProperty(MAXIMUM_RESTITUTION,"property/global","peratomtypepair",max_type,max_type,caller);

   for(int i=1;i< max_type+1; i++)
   {
     for(int j=1;j<max_type+1;j++)
     {
       const double restMax = coeffRestMax->compute_array(i-1,j-1);

       if(sanity_checks)
       {
         if(restMax <= 0. || restMax > 1.0)
           lmp->error->all(FLERR,"0 < MaximumRestitution <= 1 required");
       }

       matrix->data[i][j] = restMax;
     }
   }

   return matrix;
  }

  MatrixProperty * createCoeffStc(PropertyRegistry & registry, const char * caller, bool sanity_checks)
  {
   LAMMPS * lmp = registry.getLAMMPS();
   const int max_type = registry.max_type();

   MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
   FixPropertyGlobal * coeffStc = registry.getGlobalProperty(CRITITCAL_STOKES,"property/global","peratomtypepair",max_type,max_type,caller);

   for(int i=1;i< max_type+1; i++)
   {
     for(int j=1;j<max_type+1;j++)
     {
       const double stc= coeffStc->compute_array(i-1,j-1);

       if(sanity_checks)
       {
         if(stc <= 0.)
           lmp->error->all(FLERR,"CriticalStokes > 0 required");
       }

       matrix->data[i][j] = stc;
     }
   }

   return matrix;
  }

  // ------------------------------ HAMAKER --------------------------------
  MatrixProperty* createHamakerConstant(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, HAMAKER_CONSTANT, caller);
  }

  MatrixProperty* createHamakerMinimumParticleDistance(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, MINIMUM_PARTICLE_DISTANCE, caller);
  }

  MatrixProperty * createHamakerMaxEff(PropertyRegistry & registry, const char * caller, bool)
  {
   LAMMPS * lmp = registry.getLAMMPS();
   double skin = lmp->neighbor->skin;
   const int max_type = registry.max_type();

   MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
   MatrixProperty * hCutEffProp = registry.getMatrixProperty(MINIMUM_PARTICLE_DISTANCE);
   double ** hCutEff = hCutEffProp->data;
   double maxhMaxEff = -1.0;

   for(int i=1;i< max_type+1; i++)
   {
     for(int j=1;j<max_type+1;j++)
     {
       const double hMaxEff= hCutEff[i][j]*100; // force f(hMax)/f(hCut) = 0.0001
       matrix->data[i][j] = hMaxEff;
       maxhMaxEff = max(maxhMaxEff,hMaxEff); // get maximum hMaxEff for skin check
     }
   }

   // check skin
   if(skin < maxhMaxEff) {
     if(lmp->screen) fprintf(lmp->screen,"Maximum cutoff distance (~ minParticleDist) = %f. Skin = %f\n",maxhMaxEff,skin);
     lmp->error->all(FLERR,"Skin is too small for Hamaker model.\n");
   }

   return matrix;
  }

  ScalarProperty* createLiquidVolume(PropertyRegistry & registry, const char * caller, bool)
  {
    return createScalarProperty(registry, LIQUID_VOLUME, caller);
  }

  ScalarProperty* createSurfaceTension(PropertyRegistry & registry, const char * caller, bool)
  {
    return createScalarProperty(registry, SURFACE_TENSION, caller);
  }

  MatrixProperty* createKn(PropertyRegistry & registry, const char * caller, bool)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    FixPropertyGlobal * k_n1 = registry.getGlobalProperty("kn","property/global","peratomtypepair",max_type,max_type,caller);
    const double cg = lmp->force->cg();

    for(int i=1;i< max_type+1; i++)
    {
     for(int j=1;j<max_type+1;j++)
     {
       const double k_n = cg*k_n1->compute_array(i-1,j-1);
       matrix->data[i][j] = k_n;
     }
    }

    return matrix;
  }

  MatrixProperty* createKt(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, "kt", caller);
  }

  MatrixProperty* createGamman(PropertyRegistry & registry, const char * caller, bool)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    FixPropertyGlobal * gamma_n1 = registry.getGlobalProperty("gamman","property/global","peratomtypepair",max_type,max_type,caller);
    const double cg = lmp->force->cg();

    for(int i=1;i< max_type+1; i++)
    {
      for(int j=1;j<max_type+1;j++)
      {
        const double k_n = (1./cg)*gamma_n1->compute_array(i-1,j-1);
        matrix->data[i][j] = k_n;
      }
    }

    return matrix;
  }

  MatrixProperty* createGammat(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, "gammat", caller);
  }

  MatrixProperty* createGammanAbs(PropertyRegistry & registry, const char * caller, bool)
  {
    LAMMPS * lmp = registry.getLAMMPS();
    const int max_type = registry.max_type();

    MatrixProperty * matrix = new MatrixProperty(max_type+1, max_type+1);
    FixPropertyGlobal * gamma_n1 = registry.getGlobalProperty("gamman_abs","property/global","peratomtypepair",max_type,max_type,caller);
    const double cg = lmp->force->cg();

    for(int i=1;i< max_type+1; i++)
    {
      for(int j=1;j<max_type+1;j++)
      {
        const double k_n = cg*cg*gamma_n1->compute_array(i-1,j-1);
        matrix->data[i][j] = k_n;
      }
    }

    return matrix;
  }

  MatrixProperty* createGammatAbs(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, "gammat_abs", caller);
  }

  MatrixProperty* createCoeffMaxElasticStiffness(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, COEFFICIENT_MAX_ELASTIC_STIFFNESS, caller);
  }

  MatrixProperty* createCoeffAdhesionStiffness(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, COEFFICIENT_ADHESION_STIFFNESS, caller);
  }

  MatrixProperty* createCoeffPlasticityDepth(PropertyRegistry & registry, const char * caller, bool)
  {
    return createPerTypePairProperty(registry, COEFFICIENT_PLASTICITY_DEPTH, caller);
  }
}
