/* ----------------------------------------------------------------------
   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 RIMLARGER 1.000001
#define RIMSMALLER 0.999999
#define SMALL 1e-5

extern __shared__ int shared[];

template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_PackComm_Kernel(int* sendlist, int n, int maxlistlength, int iswap, X_FLOAT dx, X_FLOAT dy, X_FLOAT dz, void* buffer)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;
  int* list = sendlist + iswap * maxlistlength;

  if(i < n) {
    int j = list[i];

    if(j > _nmax) _flag[0] = 1;

    int k = 0;

    if(data_mask & X_MASK) {
      ((X_FLOAT*) buffer)[i + k * n] = _x[j] + dx;
      k++;
      ((X_FLOAT*) buffer)[i + k * n] = _x[j + _nmax] + dy;
      k++;
      ((X_FLOAT*) buffer)[i + k * n] = _x[j + 2 * _nmax] + dz;
      k++;
    }

    if(data_mask & V_MASK) {
      ((X_FLOAT*) buffer)[i + k * n] = _v[j];
      k++;
      ((X_FLOAT*) buffer)[i + k * n] = _v[j + _nmax];
      k++;
      ((X_FLOAT*) buffer)[i + k * n] = _v[j + 2 * _nmax];
      k++;
    }

    if(data_mask & OMEGA_MASK) {
      ((X_FLOAT*) buffer)[i + k * n] = _omega[j];
      k++;
      ((X_FLOAT*) buffer)[i + k * n] = _omega[j + _nmax];
      k++;
      ((X_FLOAT*) buffer)[i + k * n] = _omega[j + 2 * _nmax];
      k++;
    }

    if(data_mask & RADIUS_MASK)((X_FLOAT*) buffer)[i + k * n] = _radius[j];

    k++;

    if(data_mask & RMASS_MASK)((X_FLOAT*) buffer)[i + k * n] = _rmass[j];

    k++;
  }
}

template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_PackComm_Self_Kernel(int* sendlist, int n, int maxlistlength, int iswap, X_FLOAT dx, X_FLOAT dy, X_FLOAT dz, int first)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  int* list = sendlist + iswap * maxlistlength;

  if(i < n) {
    int j = i;
    j = list[i];

    if(data_mask & X_MASK) {
      _x[i + first] = _x[j] + dx;
      _x[i + first + _nmax] = _x[j + _nmax] + dy;
      _x[i + first + 2 * _nmax] = _x[j + 2 * _nmax] + dz;
    }

    if(data_mask & V_MASK) {
      _v[i + first] = _v[j];
      _v[i + first + _nmax] = _v[j + _nmax];
      _v[i + first + 2 * _nmax] = _v[j + 2 * _nmax];
    }

    if(data_mask & OMEGA_MASK) {
      _omega[i + first] = _omega[j];
      _omega[i + first + _nmax] = _omega[j + _nmax];
      _omega[i + first + 2 * _nmax] = _omega[j + 2 * _nmax];
    }

    if(data_mask & RADIUS_MASK) _radius[i + first] = _radius[j];

    if(data_mask & RMASS_MASK) _rmass[i + first] = _rmass[j];
  }
}


template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_UnpackComm_Kernel(int n, int first, void* buffer)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  if(i < n) {
    int k = 0;

    if(data_mask & X_MASK) {
      _x[i + first] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
      _x[i + first + _nmax] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
      _x[i + first + 2 * _nmax] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
    }

    if(data_mask & V_MASK) {
      _v[i + first] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
      _v[i + first + _nmax] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
      _v[i + first + 2 * _nmax] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
    }

    if(data_mask & OMEGA_MASK) {
      _omega[i + first] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
      _omega[i + first + _nmax] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
      _omega[i + first + 2 * _nmax] = ((X_FLOAT*) buffer)[i + k * n];
      k++;
    }

    if(data_mask & RADIUS_MASK) _radius[i + first] = ((X_FLOAT*) buffer)[i + k * n];

    k++;

    if(data_mask & RMASS_MASK) _rmass[i + first] = ((X_FLOAT*) buffer)[i + k * n];

    k++;
  }
}


__global__ void Cuda_AtomVecCuda_PackExchangeList_Kernel(int n, int dim)
{
  double* buf = (double*) _buffer;
  buf = &buf[1];

  //X_FLOAT lo=slablo[iswap];
  //X_FLOAT hi=slabhi[iswap];

  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;
  bool add = false;

  if(i < _nlocal) {
    double xdim_tmp = static_cast <double>(_x[i + dim * _nmax]);

    if(xdim_tmp < _sublo[dim] || xdim_tmp >= _subhi[dim]) {
      add = true;
    }
  }

  shared[threadIdx.x] = add ? 1 : 0;
  __syncthreads();
  int nsend = 0;

  if(threadIdx.x == 0) {
    for(int k = 0; k < blockDim.x; k++) {
      if(shared[k]) {
        nsend++;
        shared[k] = nsend;
      }
    }

    shared[blockDim.x] = atomicAdd((int*) _buffer, nsend);
  }

  __syncthreads();

  nsend = shared[blockDim.x] + shared[threadIdx.x] - 1;

  if(add && nsend + 1 < n)
    buf[nsend] = i;
}

template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_PackExchange_Kernel(int nsend, int* copylist)
{
  double* buf = (double*) _buffer;
  int k = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  if(k >= nsend) return;

  buf = &buf[1 + k];

  int i = static_cast <int>(buf[0]);
  int j = copylist[k];

  int m = 1;

  if(data_mask & X_MASK) {
    buf[(m++)*nsend] = static_cast <double>(_x[i]);
    buf[(m++)*nsend] = static_cast <double>(_x[i + _nmax]);
    buf[(m++)*nsend] = static_cast <double>(_x[i + 2 * _nmax]);
  }

  if(data_mask & V_MASK) {
    buf[(m++)*nsend] = _v[i];
    buf[(m++)*nsend] = _v[i + _nmax];
    buf[(m++)*nsend] = _v[i + 2 * _nmax];
  }

  if(data_mask & TAG_MASK) 		buf[(m++)*nsend] = _tag[i];

  if(data_mask & TYPE_MASK) 	buf[(m++)*nsend] = _type[i];

  if(data_mask & MASK_MASK) 	buf[(m++)*nsend] = _mask[i];

  if(data_mask & IMAGE_MASK) 	buf[(m++)*nsend] = _image[i];

  if(data_mask & Q_MASK) 		buf[(m++)*nsend] = _q[i];

  if(data_mask & MOLECULE_MASK) buf[(m++)*nsend] = _molecule[i];

  if(data_mask & RADIUS_MASK) 	buf[(m++)*nsend] = _radius[i];

  if(data_mask & DENSITY_MASK) 	buf[(m++)*nsend] = _density[i];

  if(data_mask & RMASS_MASK) 	buf[(m++)*nsend] = _rmass[i];

  if(data_mask & OMEGA_MASK) {
    buf[(m++)*nsend] = _omega[i];
    buf[(m++)*nsend] = _omega[i + _nmax];
    buf[(m++)*nsend] = _omega[i + 2 * _nmax];
  }

  /*  if(data_mask & NSPECIAL_MASK)
    {
    	buf[(m++)*nsend] = _nspecial[i];
    	buf[(m++)*nsend] = _nspecial[i+_nmax];
    	buf[(m++)*nsend] = _nspecial[i+2* _nmax];
    }*/

  if(i >= _nlocal) return;

  if(data_mask & X_MASK) {
    _x[i] = _x[j];
    _x[i + _nmax] = _x[j + _nmax];
    _x[i + 2 * _nmax] = _x[j + 2 * _nmax];
  }

  if(data_mask & V_MASK) {
    _v[i] = _v[j];
    _v[i + _nmax] = _v[j + _nmax];
    _v[i + 2 * _nmax] = _v[j + 2 * _nmax];
  }

  if(data_mask & TAG_MASK)		_tag[i] 	= _tag[j];

  if(data_mask & TYPE_MASK)		_type[i] 	= _type[j];

  if(data_mask & MASK_MASK)		_mask[i] 	= _mask[j];

  if(data_mask & IMAGE_MASK)	_image[i] 	= _image[j];

  if(data_mask & Q_MASK) 		_q[i] 		= _q[j];

  if(data_mask & MOLECULE_MASK) _molecule[i] = _molecule[j];

  if(data_mask & RADIUS_MASK) 	_radius[i] 	= _radius[j];

  if(data_mask & DENSITY_MASK) 	_density[i] = _density[j];

  if(data_mask & RMASS_MASK) 	_rmass[i] 	= _rmass[j];

  if(data_mask & OMEGA_MASK) {
    _omega[i] = _omega[j];
    _omega[i + _nmax] = _omega[j + _nmax];
    _omega[i + 2 * _nmax] = _omega[j + 2 * _nmax];
  }

  /* if(data_mask & NSPECIAL_MASK)
  {
  _nspecial[i] = _nspecial[j];
  _nspecial[i+_nmax] = _nspecial[j+_nmax];
  _nspecial[i+2* _nmax] = _nspecial[j+2* _nmax];
  }*/
}

template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_UnpackExchange_Kernel(int dim, int nsend, int* copylist)
{
  double* buf = (double*) _buffer;
  int k = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  if(k >= nsend) return;

  buf = &buf[1 + k];
  int i = -1;
  double xdim_tmp = buf[(1 + dim) * nsend];

  if(xdim_tmp >= _sublo[dim] - SMALL && xdim_tmp < _subhi[dim] + SMALL) {
    i = atomicAdd(_flag, 1) + _nlocal;

    int m = 1;

    if(data_mask & X_MASK) {
      _x[i] = buf[(m++) * nsend];
      _x[i + _nmax] = buf[(m++) * nsend];
      _x[i + 2 * _nmax] = buf[(m++) * nsend];
    }

    if(data_mask & V_MASK) {
      _v[i] = buf[(m++) * nsend];
      _v[i + _nmax] = buf[(m++) * nsend];
      _v[i + 2 * _nmax] = buf[(m++) * nsend];
    }

    if(data_mask & TAG_MASK) 	_tag[i] = buf[(m++) * nsend];

    if(data_mask & TYPE_MASK) 	_type[i] = buf[(m++) * nsend];

    if(data_mask & MASK_MASK) 	_mask[i] = buf[(m++) * nsend];

    if(data_mask & IMAGE_MASK) _image[i] = buf[(m++) * nsend];

    if(data_mask & Q_MASK) _q[i] = buf[(m++) * nsend];

    if(data_mask & MOLECULE_MASK) _molecule[i] = buf[(m++) * nsend];

    if(data_mask & RADIUS_MASK) _radius[i] = buf[(m++) * nsend];

    if(data_mask & DENSITY_MASK) _density[i] = buf[(m++) * nsend];

    if(data_mask & RMASS_MASK) _rmass[i] = buf[(m++) * nsend];

    if(data_mask & OMEGA_MASK) {
      _omega[i] = buf[(m++) * nsend];
      _omega[i + _nmax] = buf[(m++) * nsend];
      _omega[i + 2 * _nmax] = buf[(m++) * nsend];
    }

    /*  if(data_mask & NSPECIAL_MASK)
      {
       _nspecial[i] = buf[(m++)*nsend];
       _nspecial[i+_nmax] = buf[(m++)*nsend];
       _nspecial[i+2*_nmax] = buf[(m++)*nsend];
      }*/
  }

  copylist[k] = i;
}

template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_PackBorder_Kernel(int* sendlist, int n, int maxlistlength, int iswap, X_FLOAT dx, X_FLOAT dy, X_FLOAT dz)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;
  int* list = sendlist + iswap * maxlistlength;

  if(i < n) {
    int j = list[i];
    int m = 0;

    if(data_mask & X_MASK) {
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _x[j] + dx;
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _x[j + _nmax] + dy;
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _x[j + 2 * _nmax] + dz;
    }

    if(data_mask & V_MASK) {
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _v[j];
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _v[j + _nmax];
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _v[j + 2 * _nmax];
    }

    if(data_mask & TAG_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _tag[j];

    if(data_mask & TYPE_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _type[j];

    if(data_mask & MASK_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _mask[j];

    if(data_mask & Q_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _q[j];

    if(data_mask & MOLECULE_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _molecule[j];

    if(data_mask & RADIUS_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _radius[i];

    if(data_mask & DENSITY_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _density[i];

    if(data_mask & RMASS_MASK)((X_FLOAT*) _buffer)[i + (m++)*n] = _rmass[i];

    if(data_mask & OMEGA_MASK) {
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _omega[i];
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _omega[i + _nmax];
      ((X_FLOAT*) _buffer)[i + (m++)*n] = _omega[i + 2 * _nmax];
    }
  }
}



template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_PackBorder_Self_Kernel(int* sendlist, int n, int maxlistlength, int iswap, X_FLOAT dx, X_FLOAT dy, X_FLOAT dz, int first)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;
  int* list = sendlist + iswap * maxlistlength;

  if(i < n) {
    int j = list[i];

    if(data_mask & X_MASK) {
      _x[i + first] = _x[j] + dx;
      _x[i + first + _nmax] = _x[j + _nmax] + dy;
      _x[i + first + 2 * _nmax] = _x[j + 2 * _nmax] + dz;
    }

    if(data_mask & V_MASK) {
      _v[i + first] = _v[j];
      _v[i + first + _nmax] = _v[j + _nmax];
      _v[i + first + 2 * _nmax] =  _v[j + 2 * _nmax];
    }

    if(data_mask & TAG_MASK) _tag[i + first] = _tag[j];

    if(data_mask & TYPE_MASK) _type[i + first] = _type[j];

    if(data_mask & MASK_MASK) _mask[i + first] = _mask[j];

    if(data_mask & Q_MASK) _q[i + first] = _q[j];

    if(data_mask & MOLECULE_MASK) _molecule[i + first] = _molecule[j];

    if(data_mask & RADIUS_MASK) _radius[i + first] = _radius[j];

    if(data_mask & DENSITY_MASK) _density[i + first] = _density[j];

    if(data_mask & RMASS_MASK) _rmass[i + first] = _rmass[j];

    if(data_mask & OMEGA_MASK) {
      _omega[i + first] = _omega[j];
      _omega[i + first + _nmax] = _omega[j + _nmax];
      _omega[i + first + 2 * _nmax] =  _omega[j + 2 * _nmax];
    }
  }
}

template <const unsigned int data_mask>
__global__ void Cuda_AtomVecCuda_UnpackBorder_Kernel(int n, int first)
{
  int i = (blockIdx.x * gridDim.y + blockIdx.y) * blockDim.x + threadIdx.x;

  if(i < n) {
    if(i + first < _nmax) {
      int m = 0;

      if(data_mask & X_MASK) {
        _x[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];
        _x[i + first + _nmax] = ((X_FLOAT*) _buffer)[i + (m++) * n];
        _x[i + first + 2 * _nmax] = ((X_FLOAT*) _buffer)[i + (m++) * n];
      }

      if(data_mask & V_MASK) {
        _v[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];
        _v[i + first + _nmax] = ((X_FLOAT*) _buffer)[i + (m++) * n];
        _v[i + first + 2 * _nmax] = ((X_FLOAT*) _buffer)[i + (m++) * n];
      }

      if(data_mask & TAG_MASK) _tag[i + first] = static_cast<int>(((X_FLOAT*) _buffer)[i + (m++) * n]);

      if(data_mask & TYPE_MASK) _type[i + first] = static_cast<int>(((X_FLOAT*) _buffer)[i + (m++) * n]);

      if(data_mask & MASK_MASK) _mask[i + first] = static_cast<int>(((X_FLOAT*) _buffer)[i + (m++) * n]);

      if(data_mask & Q_MASK) _q[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];

      if(data_mask & MOLECULE_MASK) _molecule[i + first] = static_cast<int>(((X_FLOAT*) _buffer)[i + (m++) * n]);

      if(data_mask & RADIUS_MASK) _radius[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];

      if(data_mask & DENSITY_MASK) _density[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];

      if(data_mask & RMASS_MASK) _rmass[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];

      if(data_mask & OMEGA_MASK) {
        _omega[i + first] = ((X_FLOAT*) _buffer)[i + (m++) * n];
        _omega[i + first + _nmax] = ((X_FLOAT*) _buffer)[i + (m++) * n];
        _omega[i + first + 2 * _nmax] = ((X_FLOAT*) _buffer)[i + (m++) * n];
      }
    } else {
      _flag[0] = 1;
    }
  }
}


