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


__global__ void PairGranHookeCuda_Kernel(int eflag, int vflag, int eflag_atom, int vflag_atom, int** firstneight, int* binned_id
    , F_CFLOAT kn, F_CFLOAT gamman, F_CFLOAT gammat, F_CFLOAT xmu)
{
  ENERGY_CFLOAT evdwl = ENERGY_F(0.0);

  ENERGY_CFLOAT* sharedE;
  ENERGY_CFLOAT* sharedV;

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

  if(vflag || vflag_atom) {
    sharedV += threadIdx.x;
    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 ii = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  MYEMUDBG(if(ii == 0) printf("# CUDA: PairGranHookeCuda_Kernel: -- no binning --\n");)

    X_CFLOAT xtmp, ytmp, ztmp;

  X_CFLOAT4 myxtype;
  V_CFLOAT4 myvradius, ovradius;
  F_CFLOAT fxtmp, fytmp, fztmp, torquextmp, torqueytmp, torqueztmp;
  F_CFLOAT delx, dely, delz;
  F_CFLOAT radi, radj, radsum, r, rsqinv;
  F_CFLOAT vr1, vr2, vr3, vnnr, vn1, vn2, vn3, vt1, vt2, vt3;
  F_CFLOAT wr1, wr2, wr3;
  F_CFLOAT vtr1, vtr2, vtr3, vrel;
  F_CFLOAT meff, damp, ccel, tor1, tor2, tor3;
  F_CFLOAT fn, fs, ft, fs1, fs2, fs3;

  int jnum = 0;
  int i, j;
  int* jlist;

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

    myxtype = fetchXType(i);
    myvradius = fetchVRadius(i);

    xtmp = myxtype.x;
    ytmp = myxtype.y;
    ztmp = myxtype.z;
    radi = myvradius.w;

    fxtmp = F_F(0.0);
    fytmp = F_F(0.0);
    fztmp = F_F(0.0);
    torquextmp = F_F(0.0);
    torqueytmp = F_F(0.0);
    torqueztmp = F_F(0.0);

    jnum = _numneigh[i];

    jlist = &_neighbors[i];
  }

  __syncthreads();

  for(int jj = 0; jj < jnum; jj++) {
    if(ii < _inum)
      if(jj < jnum) {
        j = jlist[jj * _nlocal];

        myxtype = fetchXType(j);
        ovradius = fetchVRadius(j);

        delx = xtmp - myxtype.x;
        dely = ytmp - myxtype.y;
        delz = ztmp - myxtype.z;

        radj = ovradius.w;
        radsum = radi + radj;

        const F_CFLOAT rsq = delx * delx + dely * dely + delz * delz;

        if(rsq < radsum * radsum) {
          const F_CFLOAT rinv = _RSQRT_(rsq);
          r = F_F(1.0) / rinv;
          rsqinv = F_F(1.0) / rsq;

          // relative translational velocity

          vr1 = myvradius.x - ovradius.x;
          vr2 = myvradius.y - ovradius.y;
          vr3 = myvradius.z - ovradius.z;

          // normal component

          vnnr = vr1 * delx + vr2 * dely + vr3 * delz;
          vn1 = delx * vnnr * rsqinv;
          vn2 = dely * vnnr * rsqinv;
          vn3 = delz * vnnr * rsqinv;

          // tangential component

          vt1 = vr1 - vn1;
          vt2 = vr2 - vn2;
          vt3 = vr3 - vn3;

          // relative rotational velocity
          V_CFLOAT4 omegarmass_i = fetchOmegaRmass(i);
          V_CFLOAT4 omegarmass_j = fetchOmegaRmass(j);

          wr1 = (radi * omegarmass_i.x + radj * omegarmass_j.x) * rinv;
          wr2 = (radi * omegarmass_i.y + radj * omegarmass_j.y) * rinv;
          wr3 = (radi * omegarmass_i.z + radj * omegarmass_j.z) * rinv;

          meff = omegarmass_i.w * omegarmass_j.w / (omegarmass_i.w + omegarmass_j.w);

          if(_mask[i] & _freeze_group_bit) meff = omegarmass_j.w;

          if(_mask[j] & _freeze_group_bit) meff = omegarmass_i.w;

          damp = meff * gamman * vnnr * rsqinv;
          ccel = kn * (radsum - r) * rinv - damp;

          vtr1 = vt1 - (delz * wr2 - dely * wr3);
          vtr2 = vt2 - (delx * wr3 - delz * wr1);
          vtr3 = vt3 - (dely * wr1 - delx * wr2);
          vrel = vtr1 * vtr1 + vtr2 * vtr2 + vtr3 * vtr3;
          vrel = _SQRT_(vrel);

          fn = xmu * fabs(ccel * r);
          fs = meff * gammat * vrel;
          ft = (vrel != F_F(0.0)) ? MIN(fn, fs) / vrel : F_F(0.0);

          fs1 = -ft * vtr1;
          fs2 = -ft * vtr2;
          fs3 = -ft * vtr3;

          F_CFLOAT dxfp, dyfp, dzfp;
          fxtmp += dxfp = delx * ccel + fs1;
          fytmp += dyfp = dely * ccel + fs2;
          fztmp += dzfp = delz * ccel + fs3;

          tor1 = rinv * (dely * fs3 - delz * fs2);
          tor2 = rinv * (delz * fs1 - delx * fs3);
          tor3 = rinv * (delx * fs2 - dely * fs1);

          torquextmp -= radi * tor1;
          torqueytmp -= radi * tor2;
          torqueztmp -= radi * tor3;

          if(vflag) {
            sharedV[0 * blockDim.x] += delx * dxfp;
            sharedV[1 * blockDim.x] += dely * dyfp;
            sharedV[2 * blockDim.x] += delz * dzfp;
            sharedV[3 * blockDim.x] += delx * dyfp;
            sharedV[4 * blockDim.x] += delx * dzfp;
            sharedV[5 * blockDim.x] += dely * dzfp;
          }

        }
      }
  }

  __syncthreads();

  if(ii < _inum) {
    F_CFLOAT* my_f = _f + i;
    *my_f += fxtmp;
    my_f += _nmax;
    *my_f += fytmp;
    my_f += _nmax;
    *my_f += fztmp;
    F_CFLOAT* my_torque = _torque + i;
    *my_torque += torquextmp;
    my_torque += _nmax;
    *my_torque += torqueytmp;
    my_torque += _nmax;
    *my_torque += torqueztmp;
  }

  __syncthreads();

  if(eflag) sharedE[0] = evdwl;

  if(eflag_atom && i < _nlocal) _eatom[i] += 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(vflag || eflag) PairVirialCompute_A_Kernel(eflag, vflag, 0);
}
