/////////////////////////////////////////////////////////////
//                                                         //
// Copyright (c) 2007-2014 by The University of Queensland //
// Centre for Geoscience Computing                         //
// http://earth.uq.edu.au/centre-geoscience-computing      //
//                                                         //
// Primary Business: Brisbane, Queensland, Australia       //
// Licensed under the Open Software License version 3.0    //
// http://www.opensource.org/licenses/osl-3.0.php          //
//                                                         //
/////////////////////////////////////////////////////////////

#include "FullCircMNTable3D.h"

// --- System includes ---
#include <cmath>

// --- IO includes ---
#include <iostream>

using std::endl;

using std::floor;


FullCircMNTable3D::FullCircMNTable3D()
{}

/*!
  Construct FullCircMNTable3D. 

  \param MinPt minimum point 
  \param MaxPt maximum point 
  \param cd cell dimension
  \param ngroups initial number of particle groups
*/
FullCircMNTable3D::FullCircMNTable3D(const Vector3& MinPt,const Vector3& MaxPt,double cd,unsigned int ngroups):
  CircMNTable3D(MinPt, MaxPt, cd, ngroups)
{
  m_shift_y=Vector3(0.0,(m_max_pt-m_min_pt).Y(),0.0);
  m_shift_z=Vector3(0.0,0.0,(m_max_pt-m_min_pt).Z());
  
  // check if grid spacing fits size in circular directions:
  double ny=(MaxPt-MinPt).Y()/m_celldim;
  double nz=(MaxPt-MinPt).Z()/m_celldim;
  // error message if not
  if(ny!=floor(ny)){
    std::cout << "WARNING! grid spacing " << m_celldim << " doesn't fit periodic y-dimension " << (MaxPt-MinPt).Y() << std::endl;
  }
  if(nz!=floor(nz)){
    std::cout << "WARNING! grid spacing " << m_celldim << " doesn't fit periodic z-dimension " << (MaxPt-MinPt).Z() << std::endl;
  }
}
  
/*!
  Destruct FullCircMNTable3D. 
*/
FullCircMNTable3D::~FullCircMNTable3D()
{}
  
/*!
  set circularity of y-dimension to 1
*/
void FullCircMNTable3D::set_y_circ()
{
  m_y_periodic=1;
} 

/*!
  set circularity of z-dimension to 1
*/
void FullCircMNTable3D::set_z_circ()
{
  m_z_periodic=1;
} 


/*!
  get the cell index for a given position

  \param Pos the position
  \return the cell index if Pos is inside the table, -1 otherwise
*/
int FullCircMNTable3D::getIndex(const Vector3& Pos) const
{
  int ret;

  int ix=int(floor((Pos.x()-m_origin.x())/m_celldim));
  int iy=int(floor((Pos.y()-m_origin.y())/m_celldim));
  int iz=int(floor((Pos.z()-m_origin.z())/m_celldim));

  // check if pos is in table excl. padding
  if((ix>=0) && (ix<=m_nx-1) && (iy>=0) && (iy<=m_ny-1) && (iz>=0) && (iz<=m_nz-1)){
    ret=idx(ix,iy,iz);
  } else {
    ret=-1;
  }
  
  return ret;
}

/*!
  Insert sphere. Insert clone into other side of Table.

  \param S the Sphere
  \param gid the group id
*/
bool FullCircMNTable3D::insert(const Sphere& S,unsigned int gid)
{  bool res;

  int id=getIndex(S.Center());
  int xidx=getXIndex(S.Center());
  int yidx=getYIndex(S.Center());
  int zidx=getZIndex(S.Center());
  
  if((id!=-1) && (xidx!=0) && (xidx!=m_nx-1)  && (yidx!=0) && (yidx!=m_ny-1)  && (zidx!=0) && (zidx!=m_nz-1) && (gid<m_ngroups)){ // valid index
    // insert sphere
    m_data[id].insert(S,gid);
    res=true;
    // insert x-clone
    if (xidx==1){
      Sphere SClone=S;
      SClone.shift(m_shift_x);
      int clone_id=getFullIndex(SClone.Center());
      m_data[clone_id].insert(SClone,gid);
    } else if  (xidx==m_nx-2){
      Sphere SClone=S;
      SClone.shift(-1.0*m_shift_x);
      int clone_id=getFullIndex(SClone.Center());
      m_data[clone_id].insert(SClone,gid);
    } 
    // insert y-clone
    if (yidx==1){
      Sphere SClone=S;
      SClone.shift(m_shift_y);
      int clone_id=getFullIndex(SClone.Center());
      m_data[clone_id].insert(SClone,gid);
    } else if  (yidx==m_ny-2){
      Sphere SClone=S;
      SClone.shift(-1.0*m_shift_y);
      int clone_id=getFullIndex(SClone.Center());
      m_data[clone_id].insert(SClone,gid);
    } 
    // insert z-clone
    if (zidx==1){
      Sphere SClone=S;
      SClone.shift(m_shift_z);
      int clone_id=getFullIndex(SClone.Center());
      m_data[clone_id].insert(SClone,gid);
    } else if  (zidx==m_nz-2){
      Sphere SClone=S;
      SClone.shift(-1.0*m_shift_z);
      int clone_id=getFullIndex(SClone.Center());
      m_data[clone_id].insert(SClone,gid);
    } 
  } else {
    res=false;
  }

  return res;
}

/*!
  Insert sphere if it doesn't collide with other spheres. Insert clone into other side of Table.

  \param S the Sphere
  \param gid the group id
*/
bool FullCircMNTable3D::insertChecked(const Sphere& S,unsigned int gid,double tol)
{
  bool res;
  bool orig_insertable=false;
  bool xclone_insertable=false;
  bool yclone_insertable=false;
  bool zclone_insertable=false;
  bool needs_xcloning=false;	
  bool needs_ycloning=false;	
  bool needs_zcloning=false;	
  int id=getIndex(S.Center());
  int xidx=getXIndex(S.Center());
  int yidx=getYIndex(S.Center());
  int zidx=getZIndex(S.Center());

  Sphere XClone=S;
  Sphere YClone=S;
  Sphere ZClone=S;
	
  tol+=s_small_value;
  if((id!=-1) && (xidx!=0) && (xidx!=m_nx-1)  && (yidx!=0) && (yidx!=m_ny-1)  && (zidx!=0) && (zidx!=m_nz-1)  && (gid<m_ngroups)){ // original position is inside "core" of the table
    // check if original is insertable
    multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(S.Center(),S.Radius()-tol,gid);
    orig_insertable=(close_spheres.size()==0); // insertable if there are no spheres too close
    // check if clone is insertable
    // -- x --
    if (xidx==1){ // original in x-min slice of the table -> shift clone to +x  
      needs_xcloning=true;
      XClone.shift(m_shift_x);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(XClone.Center(),XClone.Radius()-tol,gid);
      xclone_insertable=(close_spheres.size()==0);
    } else if  (xidx==m_nx-2){ // original in x-max slice of the table -> shift clone to -x  
      needs_xcloning=true;
      XClone.shift(-1.0*m_shift_x);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(XClone.Center(),XClone.Radius()-tol,gid);
      xclone_insertable=(close_spheres.size()==0);
    } 
    // -- y --
    if (yidx==1){ // original in y-min slice of the table -> shift clone to +y  
      needs_ycloning=true;
      YClone.shift(m_shift_y);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(YClone.Center(),YClone.Radius()-tol,gid);
      yclone_insertable=(close_spheres.size()==0);
    } else if  (yidx==m_ny-2){ // original in y-max slice of the table -> shift clone to -y  
      needs_ycloning=true;
      YClone.shift(-1.0*m_shift_y);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(YClone.Center(),YClone.Radius()-tol,gid);
      yclone_insertable=(close_spheres.size()==0);
    } 
    // -- z --
    if (zidx==1){ // original in z-min slice of the table -> shift clone to +z  
      needs_zcloning=true;
      ZClone.shift(m_shift_z);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(ZClone.Center(),ZClone.Radius()-tol,gid);
      zclone_insertable=(close_spheres.size()==0);
    } else if  (zidx==m_nz-2){ // original in z-max slice of the table -> shift clone to -z  
      needs_zcloning=true;
      ZClone.shift(-1.0*m_shift_z);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(ZClone.Center(),ZClone.Radius()-tol,gid);
      zclone_insertable=(close_spheres.size()==0);
    } 
    
    // original not in end slices -> no cloning
  } 

  if(orig_insertable && 
	(xclone_insertable || (!needs_xcloning)) &&
	(yclone_insertable || (!needs_ycloning)) &&
	(zclone_insertable || (!needs_zcloning))){ // if origin is insertable and all clones are either insertable or not needed
	m_data[id].insert(S,gid); // insert original
	if (needs_xcloning) { // if needed, insert xclone
		int clone_id=getFullIndex(XClone.Center());
		m_data[clone_id].insert(XClone,gid);
	}
	if (needs_ycloning) { // if needed, insert clone
		int clone_id=getFullIndex(YClone.Center());
		m_data[clone_id].insert(YClone,gid);
	}
	if (needs_zcloning) { // if needed, insert clone
		int clone_id=getFullIndex(ZClone.Center());
		m_data[clone_id].insert(ZClone,gid);
	}
	res=true;
  } else {
    res=false;
  }
  return res;
}

bool FullCircMNTable3D::checkInsertable(const Sphere& S,unsigned int gid)
{
  bool orig_insertable=false;
  bool clone_insertable=false;
  bool needs_cloning=false;	 	
  int id=this->getIndex(S.Center());
  int xidx=getXIndex(S.Center());
  int yidx=getYIndex(S.Center());
  int zidx=getZIndex(S.Center());
  Sphere SClone=S;
  
  if((id!=-1) && (xidx!=0) && (xidx!=m_nx-1)  && (yidx!=0) && (yidx!=m_ny-1)  && (zidx!=0) && (zidx!=m_nz-1)  && (gid<m_ngroups)){ // original position is inside "core" of the table
    // check if original is insertable
    multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(S.Center(),S.Radius()-s_small_value,gid);
    orig_insertable=(close_spheres.size()==0);
    // check if clone is insertable
    //  -- x --
    if (xidx==1){ // original in x-min slice of the table -> shift clone to +x  
      needs_cloning=true;
      SClone.shift(m_shift_x);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(SClone.Center(),SClone.Radius()-s_small_value,gid);
      clone_insertable=(close_spheres.size()==0);
    } else if  (xidx==m_nx-2){ // original in x-max slice of the table -> shift clone to -x  
      needs_cloning=true;
      SClone.shift(-1.0*m_shift_x);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(SClone.Center(),SClone.Radius()-s_small_value,gid);
      clone_insertable=(close_spheres.size()==0);
    } 
    //  -- y --
    if (yidx==1){ // original in y-min slice of the table -> shift clone to +y  
      needs_cloning=true;
      SClone.shift(m_shift_y);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(SClone.Center(),SClone.Radius()-s_small_value,gid);
      clone_insertable=(close_spheres.size()==0);
    } else if  (yidx==m_ny-2){ // original in y-max slice of the table -> shift clone to -y  
      needs_cloning=true;
      SClone.shift(-1.0*m_shift_y);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(SClone.Center(),SClone.Radius()-s_small_value,gid);
      clone_insertable=(close_spheres.size()==0);
    } 
    //  -- z --
    if (zidx==1){ // original in z-min slice of the table -> shift clone to +z  
      needs_cloning=true;
      SClone.shift(m_shift_z);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(SClone.Center(),SClone.Radius()-s_small_value,gid);
      clone_insertable=(close_spheres.size()==0);
    } else if  (zidx==m_nz-2){ // original in z-max slice of the table -> shift clone to -z  
      needs_cloning=true;
      SClone.shift(-1.0*m_shift_z);
      multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(SClone.Center(),SClone.Radius()-s_small_value,gid);
      clone_insertable=(close_spheres.size()==0);
    } 
    // original not in end slices -> no cloning
  } 
  
  return (orig_insertable && (clone_insertable || (!needs_cloning))); // original is insertable and clone is either insertable or not needed
}


/*!
  Generate bonds between particles of a group. Takes cloned particles into account.

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag bond tag
*/
void FullCircMNTable3D::generateBonds(int gid,double tol,int btag)
{
  std::cout << "FullCircMNTable3D::generateBonds( " << gid << " , " << tol << " , " << btag << " )" << std::endl;
  // loop over all inner cells 
  for(int i=0;i<m_nx-1;i++){
    for(int j=0;j<m_ny-1;j++){
      for(int k=0;k<m_nz-1;k++){
	int id=idx(i,j,k);
	// loop over "upper" neighbors of each cell
	for(int ii=-1;ii<=1;ii++){
	  for(int jj=-1;jj<=1;jj++){
	    for(int kk=-1;kk<=1;kk++){
	      int id2=idx(i+ii,j+jj,k+kk);
	      vector<pair<int,int> > bonds;
	      if((ii+jj+kk)==0){ // intra-cell, not for boundary 
		// std::cout << id << " - " << id << std::endl;
		bonds=m_data[id].getBonds(gid,tol);
	      } else if(id2>id){ // inter-cell
		//std::cout << id << " - " << id2 << std::endl;
		bonds=m_data[id].getBonds(gid,tol,m_data[id2]);
	      }
	      for(vector<pair<int,int> >::iterator iter=bonds.begin();
		  iter!=bonds.end();
		  iter++){
		//std::cout << iter->first << " | " << iter->second << "   "; 
		if(iter->second > iter->first){
		  m_bonds[btag].insert(*iter);
		}
	      }
	      //std::cout << std::endl;
	    }
	  }
	}
	//std::cout << std::endl;
      }
    }
  }
}
  
// ostream& FullCircMNTable3D::operator << (ostream&,const FullCircMNTable3D&)
// {}
