/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator 

   Original Version:
   http://lammps.sandia.gov, Sandia National Laboratories
   Steve Plimpton, sjplimp@sandia.gov 

   See the README file in the top-level LAMMPS directory. 

   ----------------------------------------------------------------------- 

   USER-CUDA Package and associated modifications:
   https://sourceforge.net/projects/lammpscuda/ 

   Christian Trott, christian.trott@tu-ilmenau.de
   Lars Winterfeld, lars.winterfeld@tu-ilmenau.de
   Theoretical Physics II, University of Technology Ilmenau, Germany 

   See the README file in the USER-CUDA directory. 

   This software is distributed under the GNU General Public License.
------------------------------------------------------------------------- */
#define Pi F_F(3.1415926535897932384626433832795)
#define PI Pi
#define PI2 F_F(0.5)*Pi
#define PI4 F_F(0.25)*Pi



__device__ void twobody(int iparam, F_FLOAT rsq, F_FLOAT &fforce,
          int eflag, ENERGY_FLOAT &eng)
{
  F_FLOAT r,rp,rq,rainv,expsrainv;

  r = sqrt(rsq);
  rp = pow(r,-params_sw[iparam].powerp);
  rq = pow(r,-params_sw[iparam].powerq);
  rainv = 1.0 / (r - params_sw[iparam].cut);
  expsrainv = exp(params_sw[iparam].sigma * rainv);
  fforce = (params_sw[iparam].c1*rp - params_sw[iparam].c2*rq +
      (params_sw[iparam].c3*rp -params_sw[iparam].c4*rq) * rainv*rainv*r) * expsrainv / rsq;
  if (eflag) eng += (params_sw[iparam].c5*rp - params_sw[iparam].c6*rq) * expsrainv;
}

__device__ void threebody(int paramij, int paramik, int paramijk,
           F_FLOAT4& delr1,
           F_FLOAT4& delr2,
           F_FLOAT3& fj, F_FLOAT3& fk, int eflag,ENERGY_FLOAT &eng)
{
  F_FLOAT r1,rinvsq1,rainv1,gsrainv1,gsrainvsq1,expgsrainv1;
  F_FLOAT r2,rinvsq2,rainv2,gsrainv2,gsrainvsq2,expgsrainv2;
  F_FLOAT rinv12,cs,delcs,delcssq,facexp,facrad,frad1,frad2;
  F_FLOAT facang,facang12,csfacang,csfac1,csfac2;

  r1 = sqrt(delr1.w);
  rinvsq1 = F_F(1.0)/delr1.w;
  rainv1 = F_F(1.0)/(r1 - params_sw[paramij].cut);
  gsrainv1 = params_sw[paramij].sigma_gamma * rainv1;
  gsrainvsq1 = gsrainv1*rainv1/r1;
  expgsrainv1 = exp(gsrainv1);

  r2 = sqrt(delr2.w);
  rinvsq2 = F_F(1.0)/delr2.w;
  rainv2 = F_F(1.0)/(r2 - params_sw[paramik].cut);
  gsrainv2 = params_sw[paramik].sigma_gamma * rainv2;
  gsrainvsq2 = gsrainv2*rainv2/r2;
  expgsrainv2 = exp(gsrainv2);

  rinv12 = F_F(1.0)/(r1*r2);
  cs = (delr1.x*delr2.x + delr1.y*delr2.y + delr1.z*delr2.z) * rinv12;
  delcs = cs - params_sw[paramijk].costheta;
  delcssq = delcs*delcs;

  facexp = expgsrainv1*expgsrainv2;

  // facrad = sqrt(paramij->lambda_epsilon*paramik->lambda_epsilon) *
  //          facexp*delcssq;

  facrad = params_sw[paramijk].lambda_epsilon * facexp*delcssq;
  frad1 = facrad*gsrainvsq1;
  frad2 = facrad*gsrainvsq2;
  facang = params_sw[paramijk].lambda_epsilon2 * facexp*delcs;
  facang12 = rinv12*facang;
  csfacang = cs*facang;
  csfac1 = rinvsq1*csfacang;

  fj.x = delr1.x*(frad1+csfac1)-delr2.x*facang12;
  fj.y = delr1.y*(frad1+csfac1)-delr2.y*facang12;
  fj.z = delr1.z*(frad1+csfac1)-delr2.z*facang12;

  csfac2 = rinvsq2*csfacang;

  fk.x = delr2.x*(frad2+csfac2)-delr1.x*facang12;
  fk.y = delr2.y*(frad2+csfac2)-delr1.y*facang12;
  fk.z = delr2.z*(frad2+csfac2)-delr1.z*facang12;

  if (eflag) eng+= F_F(2.0)*facrad;
}

__device__ void threebody_fj(int paramij, int paramik, int paramijk,
           F_FLOAT4& delr1,
           F_FLOAT4& delr2,
           F_FLOAT3& fj)
{
  F_FLOAT r1,rinvsq1,rainv1,gsrainv1,gsrainvsq1,expgsrainv1;
  F_FLOAT r2,rainv2,gsrainv2,expgsrainv2;
  F_FLOAT rinv12,cs,delcs,delcssq,facexp,facrad,frad1;
  F_FLOAT facang,facang12,csfacang,csfac1;

  r1 = sqrt(delr1.w);
  rinvsq1 = F_F(1.0)/delr1.w;
  rainv1 = F_F(1.0)/(r1 - params_sw[paramij].cut);
  gsrainv1 = params_sw[paramij].sigma_gamma * rainv1;
  gsrainvsq1 = gsrainv1*rainv1/r1;
  expgsrainv1 = exp(gsrainv1);

  r2 = sqrt(delr2.w);
  rainv2 = F_F(1.0)/(r2 - params_sw[paramik].cut);
  gsrainv2 = params_sw[paramik].sigma_gamma * rainv2;
  expgsrainv2 = exp(gsrainv2);

  rinv12 = F_F(1.0)/(r1*r2);
  cs = (delr1.x*delr2.x + delr1.y*delr2.y + delr1.z*delr2.z) * rinv12;
  delcs = cs - params_sw[paramijk].costheta;
  delcssq = delcs*delcs;

  facexp = expgsrainv1*expgsrainv2;

  // facrad = sqrt(paramij->lambda_epsilon*paramik->lambda_epsilon) *
  //          facexp*delcssq;

  facrad = params_sw[paramijk].lambda_epsilon * facexp*delcssq;
  frad1 = facrad*gsrainvsq1;
  facang = params_sw[paramijk].lambda_epsilon2 * facexp*delcs;
  facang12 = rinv12*facang;
  csfacang = cs*facang;
  csfac1 = rinvsq1*csfacang;

  fj.x = delr1.x*(frad1+csfac1)-delr2.x*facang12;
  fj.y = delr1.y*(frad1+csfac1)-delr2.y*facang12;
  fj.z = delr1.z*(frad1+csfac1)-delr2.z*facang12;
}


__global__ void Pair_SW_Kernel_TpA_RIJ()//F_FLOAT4* _glob_r_ij,int* _glob_numneigh_red,int* _glob_neighbors_red,int* _glob_neightype_red)
{
  int ii = (blockIdx.x*gridDim.y+blockIdx.y)*blockDim.x+threadIdx.x;
  if( ii >= _nall ) return;

  X_FLOAT4 myxtype;
  F_FLOAT4 delij;
  F_FLOAT xtmp,ytmp,ztmp;
  int itype,jnum,i,j;
  int* jlist;
  int neigh_red = 0;
  i = ii;//_ilist[ii];
  myxtype = fetchXType(i);

  xtmp=myxtype.x;
  ytmp=myxtype.y;
  ztmp=myxtype.z;
  itype=map[(static_cast <int> (myxtype.w))];

  jnum = _numneigh[i];
  jlist = &_neighbors[i];

  __syncthreads();
  for (int jj = 0; jj < jnum; jj++)
  {
    if(jj<jnum)
    {

      j = jlist[jj*_nall];
      j &= NEIGHMASK;
      myxtype = fetchXType(j);
      delij.x = xtmp - myxtype.x;
      delij.y = ytmp - myxtype.y;
      delij.z = ztmp - myxtype.z;
      int jtype = map[(static_cast <int> (myxtype.w))];
      int iparam_ij = elem2param[(itype*nelements+jtype)*nelements+jtype];
      delij.w = vec3_dot(delij,delij);
      if (delij.w < params_sw[iparam_ij].cutsq)
      {
        _glob_neighbors_red[i+neigh_red*_nall]=j;
        _glob_neightype_red[i+neigh_red*_nall]=jtype;
        _glob_r_ij[i+neigh_red*_nall]=delij;
        neigh_red++;
      }
    }
  }
  _glob_numneigh_red[i]=neigh_red;
}


 template <int eflag, int vflagm>
 __global__ void Pair_SW_Kernel_TpA(int eflag_atom,int vflag_atom)//,F_FLOAT* _glob_zeta_ij,F_FLOAT4* _glob_r_ij,int* _glob_numneigh_red,int* _glob_neighbors_red,int* _glob_neightype_red)
{
  ENERGY_FLOAT evdwl = ENERGY_F(0.0);

  ENERGY_FLOAT* sharedE = &sharedmem[threadIdx.x];
  ENERGY_FLOAT* sharedV = &sharedmem[threadIdx.x];

  F_FLOAT* shared_F_F = (F_FLOAT*) sharedmem;
  if((eflag||eflag_atom)&&(vflagm||vflag_atom)) shared_F_F = (F_FLOAT*) &sharedmem[7*blockDim.x];
  else
  if(eflag) shared_F_F = (F_FLOAT*) &sharedmem[blockDim.x];
  else
  if(vflagm) shared_F_F = (F_FLOAT*) &sharedmem[6*blockDim.x];
  shared_F_F+=threadIdx.x;

  if(eflag_atom||eflag)
  {
    sharedE[0] = ENERGY_F(0.0);
    sharedV += blockDim.x;
  }

  if(vflagm||vflag_atom)
  {
    sharedV[0*blockDim.x] = ENERGY_F(0.0);
    sharedV[1*blockDim.x] = ENERGY_F(0.0);
    sharedV[2*blockDim.x] = ENERGY_F(0.0);
    sharedV[3*blockDim.x] = ENERGY_F(0.0);
    sharedV[4*blockDim.x] = ENERGY_F(0.0);
    sharedV[5*blockDim.x] = ENERGY_F(0.0);
  }

  int jnum_red=0;
#define fxtmp shared_F_F[0]
#define fytmp shared_F_F[blockDim.x]
#define fztmp shared_F_F[2*blockDim.x]
//#define jnum_red (static_cast <int> (shared_F_F[3*blockDim.x]))

  int ii = (blockIdx.x*gridDim.y+blockIdx.y)*blockDim.x+threadIdx.x;
  X_FLOAT4 myxtype_i,myxtype_j,myxtype_k;
  F_FLOAT4 delij,delik,deljk;
  F_FLOAT fpair;

  int itype,i,j;
  int* jlist_red;

  if(ii < _inum)
  {
    i = _ilist[ii];

    if(vflagm)
    myxtype_i=fetchXType(i);
    //itype=map[(static_cast <int> (myxtype_i.w))];
    itype=map[_type[i]];


    fxtmp = F_F(0.0);
    fytmp = F_F(0.0);
    fztmp = F_F(0.0);


    //shared_F_F[3*blockDim.x] = _glob_numneigh_red[i];
    jnum_red = _glob_numneigh_red[i];
    jlist_red = &_glob_neighbors_red[i];
  }
  __syncthreads();
#pragma unroll 1
  for (int jj = 0; jj < jnum_red; jj++)
  {
    if(i < _nlocal)
    {
      fpair=F_F(0.0);
      j = jlist_red[jj*_nall];
      j &= NEIGHMASK;

      if(vflagm)
      myxtype_j = fetchXType(j);

      int jtype = _glob_neightype_red[i+jj*_nall];
      delij = _glob_r_ij[i+jj*_nall];

      volatile int iparam_ij = elem2param[(itype*nelements+jtype)*nelements+jtype];
      volatile int iparam_ji = elem2param[(jtype*nelements+itype)*nelements+itype];

      if (delij.w<params_sw[iparam_ij].cutsq)
      {
        F_FLOAT dxfp,dyfp,dzfp;
        twobody(iparam_ij,delij.w,fpair,eflag,evdwl);
        fxtmp += dxfp = delij.x*fpair;
        fytmp += dyfp = delij.y*fpair;
        fztmp += dzfp = delij.z*fpair;
        if(vflagm)
        {
          sharedV[0 * blockDim.x]+= delij.x*dxfp;
          sharedV[1 * blockDim.x]+= delij.y*dyfp;
          sharedV[2 * blockDim.x]+= delij.z*dzfp;
          sharedV[3 * blockDim.x]+= delij.x*dyfp;
          sharedV[4 * blockDim.x]+= delij.x*dzfp;
          sharedV[5 * blockDim.x]+= delij.y*dzfp;
        }






        vec3_scale(F_F(-1.0),delij,delij);

#pragma unroll 1
        for (int kk = jj+1; kk < jnum_red; kk++) {
          int k = jlist_red[kk*_nall];
          k &= NEIGHMASK;

          if(vflagm)
          myxtype_k = fetchXType(k);

          delik = _glob_r_ij[i+kk*_nall];

          int ktype = _glob_neightype_red[i+kk*_nall];
          int iparam_ik = elem2param[(itype*nelements+ktype)*nelements+ktype];
          int iparam_ijk = elem2param[(itype*nelements+jtype)*nelements+ktype];
          vec3_scale(F_F(-1.0),delik,delik);
          if (delik.w <= params_sw[iparam_ijk].cutsq)
          {
            F_FLOAT3 fj,fk;
            threebody(iparam_ij,iparam_ik,iparam_ijk,
              delij,delik,fj,fk,eflag,evdwl);
            fxtmp -= fj.x + fk.x;
            fytmp -= fj.y + fk.y;
            fztmp -= fj.z + fk.z;

            if(vflagm)
            {
              sharedV[0 * blockDim.x]-= ENERGY_F(2.0)*myxtype_i.x*(fj.x+fk.x);
              sharedV[1 * blockDim.x]-= ENERGY_F(2.0)*myxtype_i.y*(fj.y+fk.y);
              sharedV[2 * blockDim.x]-= ENERGY_F(2.0)*myxtype_i.z*(fj.z+fk.z);
              sharedV[3 * blockDim.x]-= ENERGY_F(2.0)*myxtype_i.x*(fj.y+fk.y);
              sharedV[4 * blockDim.x]-= ENERGY_F(2.0)*myxtype_i.x*(fj.z+fk.z);
              sharedV[5 * blockDim.x]-= ENERGY_F(2.0)*myxtype_i.y*(fj.z+fk.z);

              sharedV[0 * blockDim.x]+= ENERGY_F(2.0)*myxtype_j.x*fj.x;
              sharedV[1 * blockDim.x]+= ENERGY_F(2.0)*myxtype_j.y*fj.y;
              sharedV[2 * blockDim.x]+= ENERGY_F(2.0)*myxtype_j.z*fj.z;
              sharedV[3 * blockDim.x]+= ENERGY_F(2.0)*myxtype_j.x*fj.y;
              sharedV[4 * blockDim.x]+= ENERGY_F(2.0)*myxtype_j.x*fj.z;
              sharedV[5 * blockDim.x]+= ENERGY_F(2.0)*myxtype_j.y*fj.z;

              sharedV[0 * blockDim.x]+= ENERGY_F(2.0)*myxtype_k.x*fk.x;
              sharedV[1 * blockDim.x]+= ENERGY_F(2.0)*myxtype_k.y*fk.y;
              sharedV[2 * blockDim.x]+= ENERGY_F(2.0)*myxtype_k.z*fk.z;
              sharedV[3 * blockDim.x]+= ENERGY_F(2.0)*myxtype_k.x*fk.y;
              sharedV[4 * blockDim.x]+= ENERGY_F(2.0)*myxtype_k.x*fk.z;
              sharedV[5 * blockDim.x]+= ENERGY_F(2.0)*myxtype_k.y*fk.z;
            }
          }
        }

        int j_jnum_red = _glob_numneigh_red[j];
        int* j_jlist_red = &_glob_neighbors_red[j];

        int j_ii = 0;
//#pragma unroll 1
        for (int j_kk = 0; j_kk < j_jnum_red; j_kk++) {
          if(j_jlist_red[j_kk*_nall]==i) j_ii = j_kk;
        }

#pragma unroll 1
        for (int kk = 0; kk < j_jnum_red; kk++)
        {
          if (j_ii == kk) continue;
          int k = j_jlist_red[kk*_nall];
          k &= NEIGHMASK;
          deljk = _glob_r_ij[j+kk*_nall];
          vec3_scale(F_F(-1.0),deljk,deljk);
          int ktype = _glob_neightype_red[j+kk*_nall];

          int iparam_ji = elem2param[(jtype*nelements+itype)*nelements+itype];
          int iparam_jk = elem2param[(jtype*nelements+ktype)*nelements+ktype];
          int iparam_jik = elem2param[(jtype*nelements+itype)*nelements+ktype];


          vec3_scale(F_F(-1.0),delij,delij);
          if (deljk.w <= params_sw[iparam_jik].cutsq)
          {
            F_FLOAT3 fj;

            threebody_fj(iparam_ji,iparam_jk,iparam_jik,
              delij,deljk,fj);
            fxtmp += fj.x;
            fytmp += fj.y;
            fztmp += fj.z;

          }
          vec3_scale(F_F(-1.0),delij,delij);
        }
      }
    }

  }
  __syncthreads();
  if(ii < _inum)
  {
      F_FLOAT* my_f;
      if(_collect_forces_later)
      {
        ENERGY_FLOAT* buffer = (ENERGY_FLOAT*) _buffer;
        if(eflag)
        {
          buffer=&buffer[1 * gridDim.x * gridDim.y];
        }
        if(vflagm)
        {
          buffer=&buffer[6 * gridDim.x * gridDim.y];
        }
        my_f = (F_FLOAT*) buffer;
        my_f += i;
        *my_f = fxtmp; my_f += _nmax;
        *my_f = fytmp; my_f += _nmax;
        *my_f = fztmp;
      }
      else
      {
        my_f = _f + i;
        *my_f += fxtmp; my_f += _nmax;
        *my_f += fytmp; my_f += _nmax;
        *my_f += fztmp;
      }
   }
   __syncthreads();

   if(eflag)
   {
     sharedE[0] = evdwl;
   }
   if(eflag_atom && i<_nlocal)
   {
     _eatom[i] = ENERGY_F(0.5) * evdwl;
   }

   if(vflag_atom && i<_nlocal)
   {
     _vatom[i]         = ENERGY_F(0.5) * sharedV[0 * blockDim.x];
     _vatom[i+_nmax]   = ENERGY_F(0.5) * sharedV[1 * blockDim.x];
     _vatom[i+2*_nmax] = ENERGY_F(0.5) * sharedV[2 * blockDim.x];
     _vatom[i+3*_nmax] = ENERGY_F(0.5) * sharedV[3 * blockDim.x];
     _vatom[i+4*_nmax] = ENERGY_F(0.5) * sharedV[4 * blockDim.x];
     _vatom[i+5*_nmax] = ENERGY_F(0.5) * sharedV[5 * blockDim.x];
   }
   if(vflagm&&eflag) PairVirialCompute_A_Kernel_Template<1,1>();
   else if(eflag) PairVirialCompute_A_Kernel_Template<1,0>();
   else if(vflagm) PairVirialCompute_A_Kernel_Template<0,1>();
#undef fxtmp
#undef fytmp
#undef fztmp
//#undef jnum_red
}
