#include "PerPairQuantity.h"
#include "PerAtomQuantity.h"
#include "KernelFunction.h"
#include "FE_Mesh.h"
#include "Utility.h"
#include "Quadrature.h"

using ATC::HeartBeat;
using std::pair;
using std::map;

namespace ATC {

//==========================================================
PairMap::PairMap(LammpsInterface * lammpsInterface, int groupbit ):
  lammpsInterface_(lammpsInterface),
  groupbit_(groupbit),
  nPairs_(0), nBonds_(0)
{
};
PairMap::~PairMap(void)
{
};
//==========================================================
PairMapNeighbor::PairMapNeighbor(LammpsInterface * lammpsInterface, int groupbit):
  PairMap(lammpsInterface,groupbit)
{
};

void PairMapNeighbor::reset(void) const 
{
  int inum = lammpsInterface_->neighbor_list_inum();
  int *ilist = lammpsInterface_->neighbor_list_ilist();
  int *numneigh = lammpsInterface_->neighbor_list_numneigh();
  int **firstneigh = lammpsInterface_->neighbor_list_firstneigh();
  const int * mask = lammpsInterface_->atom_mask();

  pairMap_.clear(); 
  int pairIndex = nBonds_;
  std::pair< int,int > pair_ij;
  for (int i = 0; i < inum; i++) { 
    int lammps_i = ilist[i];
    if (mask[lammps_i] & groupbit_) {
      for (int j = 0; j < numneigh[lammps_i]; j++) {
        int lammps_j = firstneigh[lammps_i][j];
        lammpsInterface_->neighbor_remap(lammps_j);
        pair_ij.first  = lammps_i; // alpha 
        pair_ij.second = lammps_j; // beta
        pairMap_[pair_ij] = pairIndex;
        pairIndex++;
      }
    }
  }
  nPairs_ = pairIndex;
  needReset_ = false;
}

//==========================================================
PairMapBond::PairMapBond(LammpsInterface * lammpsInterface, int groupbit):
  PairMap(lammpsInterface,groupbit)
{
};

//==========================================================
PairMapBoth::PairMapBoth(LammpsInterface * lammpsInterface, int groupbit):
  PairMapNeighbor(lammpsInterface,groupbit)
{
};
//==========================================================
DensePerPairMatrix::DensePerPairMatrix(LammpsInterface * lammpsInterface, 
  const PairMap & pairMap,
  int nCols):
  lammpsInterface_(lammpsInterface),
  pairMap_(pairMap),
  nCols_(nCols)
{
};

//==========================================================
PairVirial::PairVirial(LammpsInterface * lammpsInterface, 
   const PairMap & pairMap, int nCols):
  DensePerPairMatrix(lammpsInterface,pairMap,nCols)
{
};
//==========================================================
PairVirialEulerian::PairVirialEulerian(LammpsInterface * lammpsInterface, 
   const PairMap & pairMap):
  PairVirial(lammpsInterface,pairMap,6)
{
};


void PairVirialEulerian::reset(void) const 
{
  int nPairs = pairMap_.size();
  quantity_.reset(nPairs,nCols_);

  double **xatom = lammpsInterface_->xatom();
  for (ATOM_PAIR apair = pairMap_.start();
       ! pairMap_.finished(); apair=pairMap_++){
    int lammps_a = (apair.first).first ;
    int lammps_b = (apair.first).second;
    int pairIndex = apair.second;
    double * xa = xatom[lammps_a];
    double * xb = xatom[lammps_b];
    double delx = xa[0] - xb[0];
    double dely = xa[1] - xb[1];
    double delz = xa[2] - xb[2];
    double rsq = delx*delx + dely*dely + delz*delz;
    double fforce = 0;
    lammpsInterface_->pair_force(apair,rsq,fforce); 
    quantity_(pairIndex,0)=-delx*delx*fforce;
    quantity_(pairIndex,1)=-dely*dely*fforce;
    quantity_(pairIndex,2)=-delz*delz*fforce;
    quantity_(pairIndex,3)=-delx*dely*fforce;
    quantity_(pairIndex,4)=-delx*delz*fforce;
    quantity_(pairIndex,5)=-dely*delz*fforce;
  }
}
//==========================================================
PairVirialLagrangian::PairVirialLagrangian(LammpsInterface * lammpsInterface, 
   const PairMap & pairMap, 
   double ** xRef):
// const PerAtomQuantity<double> * xRef):
  PairVirial(lammpsInterface,pairMap,9),
  xRef_(xRef)
{
  
};


void PairVirialLagrangian::reset(void) const 
{
  int nPairs = pairMap_.size();
  quantity_.reset(nPairs,nCols_);

  double **xatom = lammpsInterface_->xatom(); 

  double ** xref = xRef_; 

  for (ATOM_PAIR apair = pairMap_.start();
       ! pairMap_.finished(); apair=pairMap_++){
    int lammps_a = (apair.first).first ;
    int lammps_b = (apair.first).second;
    int pairIndex = apair.second;
    double * xa = xatom[lammps_a];
    double * xb = xatom[lammps_b];
    double delx = xa[0] - xb[0];
    double dely = xa[1] - xb[1];
    double delz = xa[2] - xb[2];
    double * Xa = xref[lammps_a];
    double * Xb = xref[lammps_b];
    double delX = Xa[0] - Xb[0]; 
    double delY = Xa[1] - Xb[1]; 
    double delZ = Xa[2] - Xb[2]; 
    double rsq = delx*delx + dely*dely + delz*delz;
    double fforce = 0;
    lammpsInterface_->pair_force(apair,rsq,fforce); 
    quantity_(pairIndex,0)=-delx*fforce*delX;
    quantity_(pairIndex,1)=-delx*fforce*delY;
    quantity_(pairIndex,2)=-delx*fforce*delZ;
    quantity_(pairIndex,3)=-dely*fforce*delX;
    quantity_(pairIndex,4)=-dely*fforce*delY;
    quantity_(pairIndex,5)=-dely*fforce*delZ;
    quantity_(pairIndex,6)=-delz*fforce*delX;
    quantity_(pairIndex,7)=-delz*fforce*delY;
    quantity_(pairIndex,8)=-delz*fforce*delZ;
  }
}
//==========================================================
PairPotentialHeatFlux::PairPotentialHeatFlux(LammpsInterface * lammpsInterface, 
   const PairMap & pairMap):
  DensePerPairMatrix(lammpsInterface,pairMap,3)
{
};
//==========================================================
PairPotentialHeatFluxEulerian::PairPotentialHeatFluxEulerian(LammpsInterface * lammpsInterface, 
   const PairMap & pairMap): 
  PairPotentialHeatFlux(lammpsInterface,pairMap)
{
  
};

void PairPotentialHeatFluxEulerian::reset(void) const 
{
  int nPairs = pairMap_.size();
  quantity_.reset(nPairs,nCols_);

  double **xatom = lammpsInterface_->xatom(); 
  double **vatom = lammpsInterface_->vatom(); 
  for (ATOM_PAIR apair = pairMap_.start();
       ! pairMap_.finished(); apair=pairMap_++){
    int lammps_a = (apair.first).first ;
    int lammps_b = (apair.first).second;
    int pairIndex = apair.second;
    double * xa = xatom[lammps_a];
    double * xb = xatom[lammps_b];
    double delx = xa[0] - xb[0];
    double dely = xa[1] - xb[1];
    double delz = xa[2] - xb[2];
    double rsq = delx*delx + dely*dely + delz*delz;
    double fforce = 0;
    lammpsInterface_->pair_force(apair,rsq,fforce); 
    double* v = vatom[lammps_a];
    fforce *=delx*v[0] + dely*v[1] + delz*v[2];
    quantity_(pairIndex,0)=fforce*delx;
    quantity_(pairIndex,1)=fforce*dely;
    quantity_(pairIndex,2)=fforce*delz;
  }
}
//==========================================================
PairPotentialHeatFluxLagrangian::PairPotentialHeatFluxLagrangian(LammpsInterface * lammpsInterface, 
   const PairMap & pairMap, double ** xRef):
  PairPotentialHeatFlux(lammpsInterface,pairMap),
  xRef_(xRef)
{
  
};

void PairPotentialHeatFluxLagrangian::reset(void) const 
{
  int nPairs = pairMap_.size();
  quantity_.reset(nPairs,nCols_);

  double **xatom = lammpsInterface_->xatom(); 
  double **vatom = lammpsInterface_->vatom(); 

  for (ATOM_PAIR apair = pairMap_.start();
       ! pairMap_.finished(); apair=pairMap_++){
    int lammps_a = (apair.first).first ;
    int lammps_b = (apair.first).second;
    int pairIndex = apair.second;
    double * xa = xatom[lammps_a];
    double * xb = xatom[lammps_b];
    double delx = xa[0] - xb[0];
    double dely = xa[1] - xb[1];
    double delz = xa[2] - xb[2];
    double * Xa = xRef_[lammps_a];
    double * Xb = xRef_[lammps_b];
    double delX = Xa[0] - Xb[0]; 
    double delY = Xa[1] - Xb[1]; 
    double delZ = Xa[2] - Xb[2]; 
    double rsq = delx*delx + dely*dely + delz*delz;
    double fforce = 0;
    lammpsInterface_->pair_force(apair,rsq,fforce); 
    double* v = vatom[lammps_a];
    fforce *=delx*v[0] + dely*v[1] + delz*v[2];
    quantity_(pairIndex,0)=fforce*delX;
    quantity_(pairIndex,1)=fforce*delY;
    quantity_(pairIndex,2)=fforce*delZ;
  }
}
//==========================================================
SparsePerPairMatrix::SparsePerPairMatrix(LammpsInterface * lammpsInterface, 
  const PairMap & pairMap):
  lammpsInterface_(lammpsInterface),
  pairMap_(pairMap)
{
};
//==========================================================
BondMatrix::BondMatrix(LammpsInterface * lammpsInterface, 
  const PairMap & pairMap, double ** x, const FE_Mesh * feMesh):
  SparsePerPairMatrix(lammpsInterface,pairMap), x_(x), feMesh_(feMesh)
{
};
//==========================================================
BondMatrixKernel::BondMatrixKernel(LammpsInterface * lammpsInterface, 
  const PairMap & pairMap, 
  double ** x,
  const FE_Mesh * feMesh,
  const KernelFunction * kernelFunction):
  BondMatrix(lammpsInterface,pairMap,x,feMesh),
  kernelFunction_(kernelFunction)
{
  if (kernelFunction_ == NULL)
    throw ATC_Error("No AtC kernel function initialized");
};
void BondMatrixKernel::reset(void) const
{
  int nPairs = pairMap_.size(); // needs to come after quantity for reset
  int nNodes = feMesh_->num_nodes_unique(); 
  quantity_.reset(nNodes,nPairs);
  double lam1,lam2;
  std::pair< int,int > pair_jk;
  int heartbeatFreq = (nNodes <= 10 ? 1 : (int) nNodes / 10);
  HeartBeat beat("computing bond matrix ",heartbeatFreq);
  beat.start();
  DENS_VEC xa(3),xI(3),xaI(3),xb(3),xbI(3),xba(3);
  double invVol = kernelFunction_->inv_vol();
  for (int I = 0; I < nNodes; I++) {
    beat.next();
    xI = feMesh_->nodal_coordinates(I);
    if (!kernelFunction_->node_contributes(xI)) { continue; }
    for (ATOM_PAIR apair = pairMap_.start();
       ! pairMap_.finished(); apair=pairMap_++){
      int lammps_a = (apair.first).first ;
      int lammps_b = (apair.first).second;
      xa.copy(x_[lammps_a],3);
      xaI = xa - xI;
      lammpsInterface_->periodicity_correction(xaI.ptr());
      xb.copy(x_[lammps_b],3);
      xba = xb - xa;
      xbI = xba + xaI;
      kernelFunction_->bond_intercepts(xaI,xbI,lam1,lam2);
      if (lam1 < lam2) {
        double bondValue = invVol*(kernelFunction_->bond(xaI,xbI,lam1,lam2)); 
        int pairIndex = apair.second;
        quantity_.add(I,pairIndex,bondValue);
      } // if lam1 < lam2
    } // pair map
  } // end nodes loop
  quantity_.compress();
  beat.finish();
}
//==========================================================
BondMatrixPartitionOfUnity::BondMatrixPartitionOfUnity(LammpsInterface * lammpsInterface, 
  const PairMap & pairMap, double ** x,  const FE_Mesh * feMesh, 
  const DIAG_MAN * invVols):
  BondMatrix(lammpsInterface,pairMap,x,feMesh),
  invVols_(invVols)
{
  ATC::Quadrature::instance()->set_line_quadrature(lineNgauss_,lineXg_,lineWg_);
  double lam1 = 0.0, lam2 = 1.0;
  double del_lambda = 0.5*(lam2 - lam1);
  double avg_lambda = 0.5*(lam2 + lam1);
  for (int i = 0; i < lineNgauss_; i++) {
    double lambda = del_lambda*lineXg_[i] +avg_lambda;
    lineXg_[i] = lambda;
    lineWg_[i] *= 0.5;
  }
};
void BondMatrixPartitionOfUnity::reset(void) const
{
  int nNodes = feMesh_->num_nodes_unique(); 
  int nPairs = pairMap_.size(); 
  quantity_.reset(nNodes,nPairs);
  int nodes_per_element = feMesh_->num_nodes_per_element(); 
  Array<int> node_list(nodes_per_element); 
  DENS_VEC shp(nodes_per_element);
  std::pair< int,int > pair_jk;
  int heartbeatFreq = (int) nPairs / 10;
  HeartBeat beat("computing bond matrix ",heartbeatFreq);
  beat.start();
  DENS_VEC xa(3),xb(3),xab(3),xlambda(3);
  for (ATOM_PAIR apair = pairMap_.start();
     ! pairMap_.finished(); apair=pairMap_++){
    beat.next();
    int lammps_a = (apair.first).first ;
    int lammps_b = (apair.first).second;
    int pairIndex = apair.second;
    xa.copy(x_[lammps_a],3);
    xb.copy(x_[lammps_b],3);
    xab = xa - xb;
    for (int i = 0; i < lineNgauss_; i++) {
      double lambda = lineXg_[i];
      xlambda = lambda*xab + xb;
      lammpsInterface_->periodicity_correction(xlambda.ptr());
      feMesh_->shape_functions(xlambda,shp,node_list);
      // accumulate to nodes whose support overlaps the integration point
      for (int I = 0; I < nodes_per_element; I++) {
        int Inode = node_list(I);
        double inv_vol = (invVols_->quantity())(Inode,Inode);
        double val = inv_vol*shp(I)*lineWg_[i];
        quantity_.add(Inode,pairIndex,val);
      }
    }
  }
  quantity_.compress();
  beat.finish();
}
//==========================================================
}
