/////////////////////////////////////////////////////////////
//                                                         //
// Copyright (c) 2007-2017 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.apache.org/licenses/LICENSE-2.0              //
//                                                         //
/////////////////////////////////////////////////////////////

#include "MNTCell.h"

// --- STL includes ---
#include <utility>

using std::make_pair;
using std::endl;

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

using std::fabs;

int MNTCell::s_output_style=0; 

/*!
  construct MNTCell

  \param ngroups initial number of particle groups, can be extended later
*/
MNTCell::MNTCell(unsigned int ngroups)
{
  m_data=vector<vector<Sphere> >(ngroups);
}

/*!
  increase the number of groups. If the number of groups is already greater 
  then the given number, do nothing.

  \param ngroups the new number of groups
*/
void MNTCell::SetNGroups(unsigned int ngroups)
{
  if(ngroups>m_data.size()) m_data.resize(ngroups);
}

/*!
  insert sphere into cell

  \param S the sphere
  \param gid the group id

  \warning no check for validity of gid
*/
void MNTCell::insert(const Sphere& S, int gid)
{
  m_data[gid].push_back(S);
}

int MNTCell::NParts() const
{
  int np=0;
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    np+=group_iter->size();
  }

  return np;
}

/*!
  get all spheres within a given distance to a point, irrespective of group

  \param P the point
  \param d max. distance between the point and the surface of the sphere
*/ 
const multimap<double,const Sphere*> MNTCell::getSpheresNear(const Vector3& P,double d) const
{
  multimap<double, const Sphere*> res;

  // for all groups
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      double dist=iter->getDist(P);
      if(dist<=d){
	res.insert(make_pair(dist,&(*iter)));
      }
    }
  }
  
  return res; 
}


/*!
  get all spheres from one group within a given distance to a point

  \param P the point
  \param d max. distance between the point and the surface of the sphere 
  \param gid the group id

  \warning no check for validity of gid
*/
const multimap<double,const Sphere*> MNTCell::getSpheresFromGroupNear(const Vector3& P,double d,int gid) const
{
  multimap<double,const Sphere*> res;

  for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double dist=iter->getDist(P);
    if(dist<=d){
      res.insert(make_pair(dist,&(*iter)));
    }
  }

  return res;
}

/*!
  get all spheres from one group within a given distance to a point

  \param P the point
  \param d max. distance between the point and the surface of the sphere 
  \param gid the group id

  \warning no check for validity of gid
*/
multimap<double,Sphere*> MNTCell::getSpheresFromGroupNearNC(const Vector3& P,double d,int gid) 
{
  multimap<double,Sphere*> res;

  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double dist=iter->getDist(P);
    if(dist<=d){
      res.insert(make_pair(dist,&(*iter)));
    }
  }

  return res;
}

/*!
  get the closest spheres to a given point

  \param p the point
  \param nmax the maximum number of spheres returned
*/
const multimap<double,const Sphere*> MNTCell::getSpheresClosestTo(const Vector3& p,unsigned int nmax) const
{
  multimap<double,const Sphere*> pmap; 
  double max_dist;
  
  // for all groups
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      double dist=iter->getDist(p);
      if(pmap.size()<nmax){ // less then nmax found -> insert
	pmap.insert(make_pair(dist,&(*iter)));
	max_dist=(pmap.rbegin())->first;
      } else if(dist<max_dist){ // closer than nmax-th -> insert
	pmap.erase(max_dist);
	pmap.insert(make_pair(dist,&(*iter)));
	max_dist=(pmap.rbegin())->first;
      }
    }
  }

  return pmap;
}

/*!
  tag the closest spheres to a given point

  \param p the point
  \param dist max. distance to which particles are tagged
  \param gid the group Id
  \param tag the tag
*/
void MNTCell::tagSpheresNear(const Vector3& p,double dist,int gid, int tag) 
{
  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double d=(iter->getDist(p));
    if(d<=dist){
      iter->setTag(tag);
    }
  }
}

/*!
  tag the closest sphere to a given point

  \param p the point
  \param gid the group Id
  \param tag the tag
*/
void MNTCell::tagClosestParticle(const Vector3& p,int gid,int tag)
{
  if(m_data[gid].size()>0){
    vector<Sphere>::iterator closest=m_data[gid].begin();
    double min_dist=(closest->getDist(p));
 
    for(vector<Sphere>::iterator iter=m_data[gid].begin() ;
	iter!=m_data[gid].end();
	iter++){
      double d=(iter->getDist(p));
      if(d<min_dist){
	closest=iter;
	min_dist=d;
      }
    }
    closest->setTag(tag);
  }
}

/*!
  get the sphere within a given group closest to given sphere

  \param s the sphere
  \param gid the group
  \param maximum distance
*/
const Sphere* MNTCell::getClosestSphereFromGroup(const Sphere& s,int gid, double md) const
{
  const Sphere* res=NULL;
  double d=md;

  for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double dist=iter->getDist(s.Center())-s.Radius();
    if(dist<=d){
      res=&(*iter);
      d=dist;
    }
  }
  return res;
}

/*!
  Get the sphere within a given group closest to given sphere, using anisotropic weighted distance.

  \param s the sphere
  \param gid the group
  \param md distance
*/
const Sphere* MNTCell::getClosestSphereFromGroup(const Sphere& s,int gid, double md,
						 double wx, double wy, double wz) const
{
  const Sphere* res=NULL;
  double d=md;

  for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double dist=(iter->Center()-s.Center()).wnorm2(wx,wy,wz);
    if(dist<=d){
      res=&(*iter);
      d=dist;
    }
  }
  return res;
}


/*!
  get the sphere within a given group closest to given sphere

  \param s the sphere
  \param gid the group
  \param maximum distance
*/
Sphere* MNTCell::getClosestSphereFromGroup(const Vector3& p,int gid, double md) 
{
  Sphere* res=NULL;
  double d=md;

  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double dist=(iter->Center()-p).norm();
    if(dist<=d){
      res=&(*iter);
      d=dist;
    }
  }
  return res;
}


/*
  tag all particles within a group

  \param gid the group Id
  \param tag the tag
  \param mask the tag mask (i.e. new_tag = new_tag=(old_tag & (~mask)) | (tag & mask))
*/
void MNTCell::tagSpheresInGroup(int gid,int tag,int mask)
{
  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    int old_tag=iter->Tag();
    int new_tag = (old_tag & (~mask)) | (tag & mask);
    iter->setTag(new_tag);
  }
}


/*!
  get all spheres from a group

  \param gid the goup Id
*/
const vector<const Sphere*> MNTCell::getAllSpheresFromGroup(int gid) const
{
  vector<const Sphere*> res;

  for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    res.push_back(&(*iter));
  }

  return res;
}

/*!
  get all spheres from a group

  \param gid the goup Id
*/
vector<Sphere*> MNTCell::getAllSpheresFromGroupNC(int gid) 
{
  vector<Sphere*> res;

  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    res.push_back(&(*iter));
  }

  return res;
}

/*!
  get particles close to geometric object

  \param G the geometric object
  \param dist the maximum distance
  \param gid the group Id
*/
const vector<Sphere*> MNTCell::getSpheresNearObject(const AGeometricObject* G,double dist,unsigned int gid) 
{
  vector<Sphere*> res;
  
  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    double d=G->getDist(iter->Center())-iter->Radius();
    if(d<=dist){
      res.push_back(&(*iter));
    }
  }
 
  return res;
}

/*!
  get particles inside a volume

  \param V the volume
  \param gid the group Id
*/
const vector<Sphere*> MNTCell::getSpheresInVolume(const AVolume* V,unsigned int gid)
{
  vector<Sphere*> res;

  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    bool isin=V->isIn(iter->Center());
    if(isin){
      res.push_back(&(*iter));
    }
  }

  return res;
}

/*!
  get the sum of the area/volume of all particles in a group, assuming 2D

  \param gid the group id
*/
double MNTCell::getSumVolume2D(unsigned int gid)
{
  double res=0.0;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      double rad=iter->Radius();
      res+=3.1415926*rad*rad; // A=pi*r^2
    }
  }

  return res;
}

/*!
  get the number of particles in a group

  \param gid the group id
*/
int MNTCell::getNrParticles(int gid)
{
  return m_data[gid].size();
}

/*!
  remove particle with given id

  \param id the particle id
  \param gid the group Id
*/
void MNTCell::remove(int id, int gid)
{

}


/*!
  remove particles inside a volume
  
  \param Vol the volume
  \param full remove particles fully inside volume or center inside
*/
void MNTCell::removeInVolume(const AVolume* Vol,int gid,bool full)
{
  vector<Sphere> tmp;
  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    if(!Vol->isIn(iter->Center())){
      tmp.push_back(*iter);
    }
  }
  m_data[gid].swap(tmp);
} 

/*!
  remove particles with a given tag

  \param tag the particle tag
  \param mask the tag mask
  \param gid the group Id
*/
void MNTCell::removeTagged(int gid,int tag, int mask)
{
  vector<Sphere> tmp;
  for(vector<Sphere>::iterator iter=m_data[gid].begin();
      iter!=m_data[gid].end();
      iter++){
    if((iter->Tag() & mask) != (tag & mask)){
      tmp.push_back(*iter);
    }
  }
  m_data[gid]=tmp;
}

/*!
  change particle IDs so that they are continous 

  \param id0 lowest ID to be used
  \return lowest free ID, i.e. highest used ID+1
*/
int MNTCell::renumberParticlesContinuous(int id0)
{
  int id=id0;

  for(vector<vector<Sphere> >::iterator giter=m_data.begin();
      giter!=m_data.end();
      giter++){
    for(vector<Sphere>::iterator iter=giter->begin();
	iter!=giter->end();
	iter++){
      iter->setId(id);
      id++;
    }
  }
  return id;
}

/*!
  get the sum of the area/volume of all particles in a group, assuming 3D

  \param gid the group id
*/
double MNTCell::getSumVolume3D(unsigned int gid)
{
  double res=0.0;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      double rad=iter->Radius();
      res+=(4.0/3.0)*3.1415926*rad*rad*rad; // V=4/3*pi*r^3
    }
  }

  return res;
}

/*!
  get Ids of particles in cell
*/
vector<int> MNTCell::getIdList()
{
  vector<int> res;
  
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      res.push_back(iter->Id());
    }
  }
  return res;
}


/*!
  intra-cell bonds

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
*/
vector<pair<int,int> > MNTCell::getBonds(int gid,double tol)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if(fabs(d-r0)<(r0*tol)){
	    if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	  }
	}
      }
    }
  }
  
  return res;
}

/*!
  intra-cell bonds between particles with the same (arbitrary) tag 

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
*/
vector<pair<int,int> > MNTCell::getBondsSame(int gid,double tol)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if((fabs(d-r0)<(r0*tol)) && (iter->Tag()==iter2->Tag())){
	    if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	  }
	}
      }
    }
  }
  
  return res;
}

/*!
  intra-cell bonds with positions

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
*/
vector<BondWithPos> MNTCell::getBondsWithPos(int gid,double tol)
{
  vector<BondWithPos> res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if(d-r0<(r0*tol)){
	    if(iter->Id()<iter2->Id()){
	      BondWithPos bwp;
	      bwp.first=iter->Id();
	      bwp.second=iter2->Id();
	      bwp.firstPos=iter->Center();
	      bwp.secondPos=iter2->Center();
	      res.push_back(bwp);
	    } else {
	      BondWithPos bwp;
	      bwp.first=iter2->Id();
	      bwp.second=iter->Id();
	      bwp.firstPos=iter2->Center();
	      bwp.secondPos=iter->Center();
	      res.push_back(bwp);
	    }
	  }
	}
      }
    }
  }
  
  return res;
}

/*!
  intra-cell bonds between particles with the same (arbitrary) tag 

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
*/
vector<BondWithPos> MNTCell::getBondsWithPos(int gid,double tol ,const MNTCell& C)
{
  vector<BondWithPos> res;
  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	iter2!=C.m_data[gid].end();
	iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if(d-r0<(r0*tol)){
	  if(iter->Id()<iter2->Id()){
	    BondWithPos bwp;
	    bwp.first=iter->Id();
	    bwp.second=iter2->Id();
	    bwp.firstPos=iter->Center();
	    bwp.secondPos=iter2->Center();
	    res.push_back(bwp);
	  } else {
	    BondWithPos bwp;
	    bwp.first=iter2->Id();
	    bwp.second=iter->Id();
	    bwp.firstPos=iter2->Center();
	    bwp.secondPos=iter->Center();
	    res.push_back(bwp);
	  }
	}
      }
      
    }
  }
  
  return res;
}


/*!
  intra-cell bonds between particles with a different (arbitrary) tag 

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
*/
vector<pair<int,int> > MNTCell::getBondsDiff(int gid,double tol)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if((fabs(d-r0)<(r0*tol)) && (iter->Tag()!=iter2->Tag())){
	    if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	  }
	}
      }
    }
  }
  
  return res;
}

/*!
  intra-cell bonds between particles with 2 defined tags

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param tag1 1st particle tag
  \param tag2 2nd particle tag
*/
vector<pair<int,int> > MNTCell::getBondsTagged(int gid,double tol, int tag1, int tag2)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if((fabs(d-r0)<(r0*tol)) && 
	     (((iter->Tag()==tag1) && (iter2->Tag()==tag2)) || 
	      ((iter->Tag()==tag2)&&(iter2->Tag()==tag1))))
	    {
	      if(iter->Id()<iter2->Id()){
		res.push_back(make_pair(iter->Id(),iter2->Id()));
	      } else {
		res.push_back(make_pair(iter2->Id(),iter->Id()));
	      }
	    }
	}
      }
    }
  }
  
  return res;
}

/*!
  intra-cell bonds between particles with the same, defined tag

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param ptag the particle tag
  \param mask the mask for the particle tag
*/
vector<pair<int,int> > MNTCell::getBonds(int gid,double tol, int ptag, int mask)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if((fabs(d-r0)<(r0*tol)) && 
	     (iter->Tag()==ptag) &&
	     (iter2->Tag()==ptag))
	    {
	      if(iter->Id()<iter2->Id()){
		res.push_back(make_pair(iter->Id(),iter2->Id()));
	      } else {
		res.push_back(make_pair(iter2->Id(),iter->Id()));
	      }
	    }
	}
      }
    }
  }
  
  return res;
}

/*!
  inter-cell bonds

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param C the other cell
*/
vector<pair<int,int> > MNTCell::getBonds(int gid,double tol,const MNTCell& C)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	  iter2!=C.m_data[gid].end();
	  iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if(fabs(d-r0)<(r0*tol)){
	  if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	}
      }
    }
  }
  
  return res;
}


/*!
  inter-cell bonds between particles with the same (arbitrary) tag 

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param C the other cell
*/
vector<pair<int,int> > MNTCell::getBondsSame(int gid,double tol,const MNTCell& C)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	  iter2!=C.m_data[gid].end();
	  iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if((fabs(d-r0)<(r0*tol)) && (iter->Tag()==iter2->Tag())){
	  if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	}
      }
    }
  }
  
  return res;
}


/*!
  inter-cell bonds between particles with a different (arbitrary) tag 

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param C the other cell
*/
vector<pair<int,int> > MNTCell::getBondsDiff(int gid,double tol,const MNTCell& C)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	  iter2!=C.m_data[gid].end();
	  iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if((fabs(d-r0)<(r0*tol)) && (iter->Tag()!=iter2->Tag())){
	  if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	}
      }
    }
  }
  
  return res;
}


/*!
  inter-cell bonds between particles with the same, defined tag

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param C the other cell
  \param ptag the particle tag
  \param mask the mask for the particle tag
*/
vector<pair<int,int> > MNTCell::getBonds(int gid,double tol,const MNTCell& C, int ptag, int mask)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	  iter2!=C.m_data[gid].end();
	  iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if((fabs(d-r0)<(r0*tol)) && 
	   (iter->Tag()==ptag) &&
	   (iter2->Tag()==ptag))
	  {
	    if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	  }
      }
    }
  }
  
  return res;
}

/*!
  inter-cell bonds between particles with 2 defined tags
 
  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param C the other cell
  \param tag1 1st particle tag
  \param tag2 2nd particle tag
*/
vector<pair<int,int> > MNTCell::getBondsTagged(int gid,double tol,const MNTCell& C, int tag1, int tag2)
{
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	  iter2!=C.m_data[gid].end();
	  iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if((fabs(d-r0)<(r0*tol)) && 
	   (((iter->Tag()==tag1) && (iter2->Tag()==tag2)) || 
	    ((iter->Tag()==tag2)&&(iter2->Tag()==tag1))))
	  {
	    if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	  }
      }
    }
  }
  
  return res;
}

/*!
  intra-cell bonds between particles with 2 defined tags

  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param tag1 1st particle tag
  \param tag2 2nd particle tag
*/
vector<pair<int,int> > MNTCell::getBondsTaggedMasked(int gid,double tol ,int tag1 ,int mask1,int tag2 ,int mask2)
{  
  vector<pair<int,int> > res;

  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=iter;
	iter2!=m_data[gid].end();
	iter2++){
	if(iter!=iter2){
	  double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	  double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	  if((fabs(d-r0)<(r0*tol)) && 
	     ((((iter->Tag() & mask1)== (tag1 & mask1)) && ((iter2->Tag() & mask2) == (tag2 & mask2))) || 
	      (((iter->Tag() & mask2)== (tag2 & mask2)) && ((iter2->Tag() & mask1) == (tag1 & mask1)))))
	    {
	      if(iter->Id()<iter2->Id()){
		res.push_back(make_pair(iter->Id(),iter2->Id()));
	      } else {
		res.push_back(make_pair(iter2->Id(),iter->Id()));
	      }
	    }
	}
      }
    }
  }
  
  return res;
}

/*!
  inter-cell bonds between particles with 2 defined tags
 
  \param gid group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param C the other cell
  \param tag1 1st particle tag
  \param tag2 2nd particle tag
*/
vector<pair<int,int> > MNTCell::getBondsTaggedMasked(int gid,double tol,const MNTCell& C,int tag1,int mask1,int tag2,int mask2)
{
  vector<pair<int,int> > res;
  
  // check if gid is valid
  if(gid<m_data.size()){
    for(vector<Sphere>::const_iterator iter=m_data[gid].begin();
	iter!=m_data[gid].end();
	iter++){
      for(vector<Sphere>::const_iterator iter2=C.m_data[gid].begin();
	  iter2!=C.m_data[gid].end();
	  iter2++){
	double d=(iter->Center()-iter2->Center()).norm(); // dist between centers
	double r0=iter->Radius()+iter2->Radius(); // sum of radii -> equilibrium distance
	if((fabs(d-r0)<(r0*tol)) && 
	   ((((iter->Tag() & mask1)== (tag1 & mask1)) && ((iter2->Tag() & mask2) == (tag2 & mask2))) || 
	    (((iter->Tag() & mask2)== (tag2 & mask2)) && ((iter2->Tag() & mask1) == (tag1 & mask1)))))
	  {
	    if(iter->Id()<iter2->Id()){
	      res.push_back(make_pair(iter->Id(),iter2->Id()));
	    } else {
	      res.push_back(make_pair(iter2->Id(),iter->Id()));
	    }
	  }
      }
    }
  }
  
  return res;
}

/*!
  Set output style

  \param style the output style: 0 = debug, 1 = .geo
*/
void MNTCell::SetOutputStyle(int style)
{
  MNTCell::s_output_style=style;
}

ostream& operator << (ostream& ost ,const MNTCell& C)
{
  if(MNTCell::s_output_style==0){
    Sphere::SetOutputStyle(0);
    int gcount=0;
    // for all groups
    for(vector<vector<Sphere> >::const_iterator group_iter=C.m_data.begin();
	group_iter!=C.m_data.end();
	group_iter++){
      ost << "-- Group " << gcount << " -- " << endl;
      gcount++;
      // for all particles in the group
      for(vector<Sphere>::const_iterator iter=group_iter->begin();
	  iter!=group_iter->end();
	  iter++){
	ost << " [ " << *iter << " ] ";
      }
      ost << endl;
    } 
  } else  if(MNTCell::s_output_style==1){
    Sphere::SetOutputStyle(1);
    // for all groups
    for(vector<vector<Sphere> >::const_iterator group_iter=C.m_data.begin();
	group_iter!=C.m_data.end();
	group_iter++){
      // for all particles in the group
      for(vector<Sphere>::const_iterator iter=group_iter->begin();
	  iter!=group_iter->end();
	  iter++){
	ost << *iter << endl;
      }
    }
  }

  return ost;
}

void MNTCell::writePositions(ostream& ost)
{
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      ost << iter->Center() << " ";
    }
  }
}

void MNTCell::writeRadii(ostream& ost)
{
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      ost << iter->Radius() << " ";
    }
  }
}

void MNTCell::writeIDs(ostream& ost)
{
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      ost << iter->Id() << " ";
    }
  }
}

void MNTCell::writeTags(ostream& ost)
{
  for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
      group_iter!=m_data.end();
      group_iter++){
    // for all particles in the group
    for(vector<Sphere>::const_iterator iter=group_iter->begin();
	iter!=group_iter->end();
	iter++){
      ost << iter->Tag() << " ";
    }
  }
}

unsigned int MNTCell::writeParticlesInBlock(ostream& ost, const Vector3& min_pt, const Vector3& max_pt)
{
	Sphere::SetOutputStyle(1);
	unsigned int count=0;
    // for all groups
    for(vector<vector<Sphere> >::const_iterator group_iter=m_data.begin();
		group_iter!=m_data.end();
		group_iter++){
		// for all particles in the group
		for(vector<Sphere>::const_iterator iter=group_iter->begin();
			iter!=group_iter->end();
			iter++){
			Vector3 pos=iter->Center();
			if((pos.X()>=min_pt.x()) && (pos.X()<max_pt.x()) &&
				(pos.Y()>=min_pt.y()) && (pos.Y()<max_pt.y()) &&
				(pos.Z()>=min_pt.z()) && (pos.Z()<max_pt.z())){	
				ost << *iter << endl;
				count++;
			}					
		}
    }	
	return count;
}

