/////////////////////////////////////////////////////////////
//                                                         //
// 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 "MNTable3D.h"

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

using namespace std;
using std::make_pair;

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

using std::floor;
using std::endl;
using std::ceil;

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

using std::ifstream;

int MNTable3D::s_output_style=0; 
double MNTable3D::s_small_value=1e-7;

MNTable3D::MNTable3D():m_write_prec(10)
{}

/*!
  construct MNTable3D

  \param MinPt minimum point (z component ignored)
  \param MaxPt maximum point (z component ignored)
  \param cd cell dimension
  \param ngroups initial number of particle groups
*/
MNTable3D::MNTable3D(const Vector3& MinPt,const Vector3& MaxPt,double cd,unsigned int ngroups)
{
  m_celldim=cd;
  m_ngroups=ngroups;
  m_min_pt=MinPt;

  // get number of cells
  Vector3 req_size=MaxPt-MinPt; // requested size
  m_nx=int(ceil(req_size.X()/m_celldim))+2;
  m_ny=int(ceil(req_size.Y()/m_celldim))+2;
  m_nz=int(ceil(req_size.Z()/m_celldim))+2;
  std::cout << "nx,ny,nz: " << m_nx << " , " << m_ny << " , " << m_nz << std::endl;

  // add padding
  m_origin=MinPt-Vector3(m_celldim,m_celldim,m_celldim);
  m_max_pt=m_min_pt+Vector3(m_celldim*(m_nx-2),m_celldim*(m_ny-2),m_celldim*(m_nz-2));

  
  // allocate cell array
  m_data=new MNTCell[m_nx*m_ny*m_nz];
  for(int i=0;i<m_nx*m_ny*m_nz;i++) {
    m_data[i].SetNGroups(m_ngroups);
  }
  set_x_circ();
  set_y_circ();
  set_z_circ();

  // setup default bounding box handling
  m_bbx_tracking=false; // bounding box tracking off
  m_has_tight_bbx=false; // tight bounding box has never been calculated
  m_write_tight_bbx=false; // MinPt / MaxPt are written to the geo file
  m_min_tbbx=MaxPt;
  m_max_tbbx=MinPt; // dummy bbx corners
  
  // default output precision
  m_write_prec=10;
  
  // no blocked writing
  m_is_writing_blocks=false;
}

/*!
  set circularity of x-dimension to 0
*/
void MNTable3D::set_x_circ()
{
  m_x_periodic=0;
} 

/*!
  set circularity of y-dimension to 0
*/
void MNTable3D::set_y_circ()
{
  m_y_periodic=0;
} 

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

/*!
  destruct MNTable3D
*/
MNTable3D::~MNTable3D()
{
  if(m_data!=NULL) delete[] m_data;
}

/*!
  Load particle from raw file: x y z r

  \param filename the name of the input file
  \param scale factor by which all data are scaled before insertion
  \param tol allowed overlap
*/
bool MNTable3D::insertFromRawFile(const string& filename,double scale,double tol)
{
  ifstream infile(filename.c_str());
  double x,y,z,r;
  int count=0;

  while(!infile.eof()){
    infile >> x >> y >> z >> r;
    
    bool can_insert=insertChecked(Sphere(Vector3(x*scale,y*scale,z*scale),r*scale),0,tol);
    if(!can_insert){
      std::cout << "couldn't insert particle : " << Vector3(x*scale,y*scale,z*scale) << " r= " << r*scale << std::endl;
    } else {
      count++;
    }
  }
  std::cout << "inserted particle count:" << count << std::endl;

  infile.close();

  return true;
}

/*!
  Increase the number of particle groups. Do nothing if the number is already large enough

  \param ngroups the new number of particle groups
*/
void MNTable3D::GrowNGroups(unsigned int ngroups)
{
  if(ngroups>m_ngroups){
    m_ngroups=ngroups;
    for(int i=0;i<m_nx*m_ny*m_nz;i++) {
      m_data[i].SetNGroups(m_ngroups);
    }
  }
}

/*!
  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 MNTable3D::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

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

  int id=this->getIndex(S.Center());

  if((id!=-1) && (gid<m_ngroups)){
    m_data[id].insert(S,gid);
    res=true;
    if(m_bbx_tracking){ // if bounding box tracking is on, update bbx
	Vector3 pmin=S.Center()-Vector3(S.Radius(),S.Radius(),S.Radius());
	Vector3 pmax=S.Center()+Vector3(S.Radius(),S.Radius(),S.Radius());
	m_min_tbbx.X()=std::min(m_min_tbbx.X(),pmin.X());
	m_min_tbbx.Y()=std::min(m_min_tbbx.Y(),pmin.Y());
	m_min_tbbx.Z()=std::min(m_min_tbbx.Z(),pmin.Z());
	m_max_tbbx.X()=std::max(m_max_tbbx.X(),pmax.X());
	m_max_tbbx.Y()=std::max(m_max_tbbx.Y(),pmax.Y());
	m_max_tbbx.Z()=std::max(m_max_tbbx.Z(),pmax.Z());
      }
  } else {
    res=false;
  }

  return res;
}


/*!
  insert sphere if it doesn't collide with other spheres

  \param S the Sphere
  \param gid the group id
*/
bool  MNTable3D::insertChecked(const Sphere& S,unsigned int gid,double tol)
{
  bool res;

  int id=this->getIndex(S.Center());

  tol+=s_small_value;
  if((id!=-1) && (gid<m_ngroups)){
    multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(S.Center(),S.Radius()-tol,gid);
    if(close_spheres.size()==0){
      m_data[id].insert(S,gid);
      res=true;
      if(m_bbx_tracking){ // if bounding box tracking is on, update bbx
	Vector3 pmin=S.Center()-Vector3(S.Radius(),S.Radius(),S.Radius());
	Vector3 pmax=S.Center()+Vector3(S.Radius(),S.Radius(),S.Radius());
	m_min_tbbx.X()=std::min(m_min_tbbx.X(),pmin.X());
	m_min_tbbx.Y()=std::min(m_min_tbbx.Y(),pmin.Y());
	m_min_tbbx.Z()=std::min(m_min_tbbx.Z(),pmin.Z());
	m_max_tbbx.X()=std::max(m_max_tbbx.X(),pmax.X());
	m_max_tbbx.Y()=std::max(m_max_tbbx.Y(),pmax.Y());
	m_max_tbbx.Z()=std::max(m_max_tbbx.Z(),pmax.Z());
      }
    } else res=false;
  } else {
    res=false;
  }

  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*> MNTable3D::getSpheresClosestTo(const Vector3& p,unsigned int nmax) const
{
  multimap<double,const Sphere*> res;

  for(int i=-1;i<=1;i++){
    for(int j=-1;j<=1;j++){
      for(int k=-1;k<=1;k++){
	Vector3 pos=p+Vector3(double(i)*m_celldim,double(j)*m_celldim,double(k)*m_celldim);
	int idx=this->getIndex(pos);
	if(idx!=-1){
	  multimap<double,const Sphere*> smap=m_data[idx].getSpheresClosestTo(p,nmax);
	  res.insert(smap.begin(),smap.end());
	}
      }
    }
  }
  
  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*> MNTable3D::getSpheresFromGroupNear(const Vector3& P,double d,int gid) const
{
  multimap<double,const Sphere*> res;
  
  for(int i=-1;i<=1;i++){
    for(int j=-1;j<=1;j++){
       for(int k=-1;k<=1;k++){
	 Vector3 np=P+Vector3(double(i)*m_celldim,double(j)*m_celldim,double(k)*m_celldim);
	 int idx=this->getIndex(np);
	 if(idx!=-1){
	   multimap<double,const Sphere*> v=m_data[idx].getSpheresFromGroupNear(P,d,gid);
	   res.insert(v.begin(),v.end());
	 }
      }
    }
  }

  return res;
}

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

  \param s the sphere
  \param gid the group
*/
const Sphere* MNTable3D::getClosestSphereFromGroup(const Sphere& s,int gid) const
{
  const Sphere* res=NULL;
  int max_dim=m_nx>m_ny ? m_nx : m_ny;
  max_dim=m_nz>max_dim ? m_nz : max_dim;
  int found=max_dim;
  double dist=max_dim*m_celldim;

  // search within the same cell 
  int idx=getIndex(s.Center());
  const Sphere* v=m_data[idx].getClosestSphereFromGroup(s,gid,2.0*max_dim);
  if(v!=NULL){
    double d=(v->Center()-s.Center()).norm()-s.Radius();
    found=1;
    if(d<dist){
      res=v;
      dist=d;
    }
  }
  
  // search outside origin cell 
  int range=1;
  while((range<max_dim) && (range<found+1)){
    for(int i=-range;i<=range;i++){
      for(int j=-range;j<=range;j++){
	for(int k=-range;k<=range;k++){
	  Vector3 np=s.Center()+Vector3(double(i)*m_celldim,double(j)*m_celldim,double(k)*m_celldim);
	  int idx=getIndex(np);
	  if(idx!=-1){
	    const Sphere* v=m_data[idx].getClosestSphereFromGroup(s,gid,dist);
	    if(v!=NULL){
	      double d=(v->Center()-s.Center()).norm()-s.Radius();
	      found=range+1;
	      if(d<dist){
		res=v;
		dist=d;
	      }
	    }
	  }
	}
      }
    }
    range++;

  }
  return res;
}

/*!
  convenience function which calls getClosestSphereFromGroup and returns the
  tag of the found sphere or -1 if none is found 

  \param s the sphere
  \param gid the group
*/
int MNTable3D::getTagOfClosestSphereFromGroup(const Sphere& s,int gid) const
{
  int res;

  const Sphere* sp=getClosestSphereFromGroup(s,gid);
  if(sp!=NULL){
    res=sp->Tag();
  }else{
    res=-1;
  }

  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 wx weighting of the x-component of the distance 
  \param wy weighting of the y-component of the distance 
  \param wz weighting of the z-component of the distance 
*/
const Sphere* MNTable3D::getClosestSphereFromGroup(const Sphere& s,int gid,double wx, double wy, double wz) const
{
  const Sphere* res=NULL;
  int max_dim=m_nx>m_ny ? m_nx : m_ny;
  max_dim=m_nz>max_dim ? m_nz : max_dim;
  int found=max_dim;
  double dist=max_dim*m_celldim;

  // search within the same cell 
  int idx=getIndex(s.Center());
  const Sphere* v=m_data[idx].getClosestSphereFromGroup(s,gid,2.0*max_dim,wx,wy,wz);
  if(v!=NULL){
    double d=(v->Center()-s.Center()).wnorm2(wx,wy,wz);
    found=1;
    if(d<dist){
      res=v;
      dist=d;
    }
  }

  // search outside origin cell 
  int range=1;
  while((range<max_dim) && (range<found+1)){
    for(int i=-range;i<=range;i++){
      for(int j=-range;j<=range;j++){
	for(int k=-range;k<=range;k++){
	  Vector3 np=s.Center()+Vector3(double(i)*m_celldim,double(j)*m_celldim,double(k)*m_celldim);
	  int idx=getIndex(np);
	  if(idx!=-1){
	    const Sphere* v=m_data[idx].getClosestSphereFromGroup(s,gid,dist,wx,wy,wz);
	    if(v!=NULL){
	      double d=(v->Center()-s.Center()).wnorm2(wx,wy,wz);
	      if(d<dist){
		res=v;
		dist=d;
	      } 
	      if(found==max_dim) { // 1st find -> need to limit search range
		found=range+1;
	      }
	    }
	  }
	}
      }
    }
    range++;

  }
  return res;
}

/*!
  Convenience function which calls getClosestSphereFromGroup, using anisotropic 
  weighted distance and returns the tag of the found sphere or -1 if none is found. 

  \param s the sphere
  \param gid the group
  \param wx weighting of the x-component of the distance 
  \param wy weighting of the y-component of the distance 
  \param wz weighting of the z-component of the distance 
*/
int MNTable3D::getTagOfClosestSphereFromGroup(const Sphere& s,int gid, double wx, double wy, double wz) const
{
  int res;

  const Sphere* sp=getClosestSphereFromGroup(s,gid,wx,wy,wz);
  if(sp!=NULL){
    res=sp->Tag();
  }else{
    res=-1;
  }

  return res;
}

/*!
  check if sphere is insertable

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

  int id=this->getIndex(S.Center());
  if((id!=-1) && (gid<m_ngroups)){
    multimap<double,const Sphere*> close_spheres=getSpheresFromGroupNear(S.Center(),S.Radius()-s_small_value,gid);
    if(close_spheres.size()==0){ 
      res=true;
    } else {
      res=false;
//       for(map<double,const Sphere*>::const_iterator iter=close_spheres.begin();
// 	  iter!=close_spheres.end();
// 	  iter++){
// 	std::cout << iter->first << "  |  " << *(iter->second) << std::endl; 
//       }
    }
  } else {
    res=false;
  }

  return res;
}

/*
 get the sum of the area/volume of all particles in a group

  \param gid the group id
*/
double MNTable3D::getSumVolume(int gid)
{
  double res=0.0;

  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	res+=m_data[idx(i,j,k)].getSumVolume3D(gid);
      }
    }
  }

  return res;
}

/*!
  generate bonds between particles of a group

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag bond tag
*/
void MNTable3D::generateBonds(int gid,double tol,int btag)
{
  std::cout << "MNTable3D::generateBonds( " << gid << " , " << tol << " , " << btag << " )" << std::endl;  // loop over all cells 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;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 
		bonds=m_data[id].getBonds(gid,tol);
	      } else if(id2>id){ // inter-cell
		bonds=m_data[id].getBonds(gid,tol,m_data[id2]);
	      } 
	      for(vector<pair<int,int> >::iterator iter=bonds.begin();
		  iter!=bonds.end();
		  iter++){	    
		m_bonds[btag].insert(*iter);
	      }
	    }
	  }
	}
      }
    }
  }
}

 /*!
  generate bonds between particles with given tags of a group

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag bond tag
  \param tag1
  \param tag2
*/
void MNTable3D::generateBondsTagged(int gid,double tol,int btag,int tag1,int tag2)
{
  std::cout << "MNTable3D::generateBondsTagged( " << gid << " , " << tol << " , " << btag << " , " << tag1 << " , " << tag2 << " )" << std::endl;  // loop over all cells 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;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 
		bonds=m_data[id].getBondsTagged(gid,tol,tag1,tag2);
	      } else if(id2>id){ // inter-cell
		bonds=m_data[id].getBondsTagged(gid,tol,m_data[id2],tag1,tag2);
	      } 
	      for(vector<pair<int,int> >::iterator iter=bonds.begin();
		  iter!=bonds.end();
		  iter++){	    
		m_bonds[btag].insert(*iter);
	      }
	    }
	  }
	}
      }
    }
  }
}

 /*!
  generate bonds between particles with identical particle tag of a group with a given
  probablility (0..1)

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param prob probablility of bond generation
  \param btag bond tag
  \param ptag particle tag
  \param mask
*/
void MNTable3D::generateRandomBonds(int gid,double tol, double prob, int btag,int ptag,int mask)
{
  std::cout << "MNTable3D::generateRandomBonds( " << gid << " , " << tol << " , " << prob << " , " << btag << " , " << ptag << " , " << mask << " )" << std::endl;  
  // loop over all cells 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;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((id==id2) && (i!=0) && (j!=0)&& (k!=0)  ){ // intra-cell, not for boundary 
		bonds=m_data[id].getBonds(gid,tol,ptag,mask);
	      } else if(id2>id){ // inter-cell
		bonds=m_data[id].getBonds(gid,tol,m_data[id2],ptag,mask);
	      } 
	      for(vector<pair<int,int> >::iterator iter=bonds.begin();
		  iter!=bonds.end();
		  iter++){	    
		double rn=((double)(rand())/(double)(RAND_MAX));
		if(rn<prob) m_bonds[btag].insert(*iter);
	      }
	    }
	  }
	}
      }
    }
  }
}

/*!
  Generate bonds between clusters of particles of a group, i.e. bonds with one tag between particles with the same ID and bonds with another tag between particles with different ID.

  \param gid the group ID
  \param tol max. difference between bond length and equilibrium dist.
  \param btag1 tag for bonds within clusters (same particle tag)
  \param btag2 tag for bonds betweem clusters (different particle tag)
*/
void MNTable3D::generateClusterBonds(int gid,double tol,int btag1, int btag2)
{
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;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> > same_bonds;
	      vector<pair<int,int> > diff_bonds;
	      if((id==id2) && (i!=0) && (j!=0) && (k!=0)){ // intra-cell, not for boundary
		same_bonds=m_data[id].getBondsSame(gid,tol);
		diff_bonds=m_data[id].getBondsDiff(gid,tol);
	      } else if(id2>id){ // inter-cell
		same_bonds=m_data[id].getBondsSame(gid,tol,m_data[id2]);
		diff_bonds=m_data[id].getBondsDiff(gid,tol,m_data[id2]);
	      } 
	      // insert intra-cluster bonds
	      for(vector<pair<int,int> >::iterator iter=same_bonds.begin();
		  iter!=same_bonds.end();
		  iter++){	    
		if(m_bonds[btag1].find(*iter)==m_bonds[btag1].end()){
		  m_bonds[btag1].insert(*iter);
		}
	      }
	      // insert inter-cluster bonds
	      for(vector<pair<int,int> >::iterator iter=diff_bonds.begin();
		  iter!=diff_bonds.end();
		  iter++){	    
		if(m_bonds[btag2].find(*iter)==m_bonds[btag2].end()){
		  m_bonds[btag2].insert(*iter);
		}
	      } 
	    }
	  }
	}
      }
    }
  }
}

/*!
  generate bonds taking a list of joint surfaces into account, i.e. bonds which cross one of the surface get tagged differently (according to the tag of the joint)

  \param Joints the set of joint surfaces
  \param gid the particle group id
  \param tol the bond generation tolerance, i.e. bonds up to length (equilibrium length*tol) get generated
  \param tag the tag for the bonds not crossing any surface
*/
void MNTable3D::generateBondsWithJointSet(const TriPatchSet& Joints,int gid,double tol,int tag)
{
  std::cout << "MNTable3D::generateBondsWithJointSet( " << tag  <<  " )" << std::endl;  
  // loop over all cells 
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;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<BondWithPos> bonds;
	      if(((ii+jj+kk)==0)){ // intra-cell, not for boundary 
		bonds=m_data[id].getBondsWithPos(gid,tol);
	      } else if(id2>id){ // inter-cell
		bonds=m_data[id].getBondsWithPos(gid,tol,m_data[id2]);
	      } 
	      for(vector<BondWithPos>::iterator iter=bonds.begin();
		  iter!=bonds.end();
		  iter++){	    
		// check if bond crosses surface
		int jtag=Joints.isCrossing(iter->firstPos,iter->secondPos) ;
		if(jtag==-1){
		  m_bonds[tag].insert(make_pair(iter->first,iter->second));
		} else {
		  m_bonds[jtag].insert(make_pair(iter->first,iter->second));
		}
	      }
	    }
	  }
	}
      }
    }
  }
}

/*!
  insert bond between particles with given ID

  \param id1 id of 1st particle
  \param id2 id of 2nd particle
  \param btag  bond tag
*/
void MNTable3D::insertBond(int id1,int id2,int btag)
{
  if(id1<id2){
    m_bonds[btag].insert(make_pair(id1,id2));
  } else {
    m_bonds[btag].insert(make_pair(id2,id1));
  }
}

/*!
  remove all currently stored bonds
*/
void MNTable3D::removeBonds()
{
/* 	for(map<int,set<pair<int,int> > >::const_iterator iter=T.m_bonds.begin();
 * 		iter!=T.m_bonds.end();
 * 		iter++){
 * 		iter->clear();
 * 	}
 */
	m_bonds.clear();
}

/*!
  get vector of pointers to all spheres of a group

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

  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	vector<const Sphere*> cr=m_data[idx(i,j,k)].getAllSpheresFromGroup(gid);
	res.insert(res.end(),cr.begin(),cr.end());
      } 
    }
  }

  return res;
}

boost::python::list MNTable3D::getSphereListFromGroup(int gid) const
{
   boost::python::list l;
   vector<const Sphere*> sphere_vector; 

   sphere_vector = getAllSpheresFromGroup(gid);

   for (vector<const Sphere*>::iterator iter=sphere_vector.begin(); iter!= sphere_vector.end(); iter++) {
      l.append(*(*(iter)));
   }

   return l;
}

boost::python::list MNTable3D::getSphereListDist(const Vector3& pos, double dist, int gid) const
{
   boost::python::list l;
   multimap<double,const Sphere*> sphere_map = getSpheresFromGroupNear(pos,dist,gid);

   for (map<double,const Sphere*>::iterator iter=sphere_map.begin(); 
	iter!= sphere_map.end(); 
	iter++) {
     l.append(*(iter->second));
   }

   return l;
}

/*!
  Set output style

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

/*!
  Output the content of a MNTable3D to an ostream. The output format depends on the value of MNTable3D::s_output_style (see  MNTable3D::SetOutputStyle). If it is set to 0, output suitable for debugging is generated, if it is set to 1 output in the esys .geo format is generated. If MNTable3D::s_output_style is set to 2, the output format is VTK-XML.

  \param ost the output stream
  \param T the table
*/
ostream& operator << (ostream& ost,const MNTable3D& T)
{
  if(MNTable3D::s_output_style==0){ // debug style
    // don't print padding
    MNTCell::SetOutputStyle(0);
    for(int i=0;i<T.m_nx;i++){
      for(int j=1;j<T.m_ny-1;j++){
	for(int k=1;k<T.m_nz-1;k++){
	  ost << "=== Cell " << i << " , " << j << " , " << k << " === " << T.idx(i,j,k) << " ===" << endl;
	  ost << T.m_data[T.idx(i,j,k)];
	} 
      }
    }
  } else if (MNTable3D::s_output_style==1){ // geo style
    int nparts=0;
    for(int i=1;i<T.m_nx-1;i++){
      for(int j=1;j<T.m_ny-1;j++){
	for(int k=1;k<T.m_nz-1;k++){
	  nparts+=T.m_data[T.idx(i,j,k)].NParts();
	}
      }
    }
    // header
    ost << "LSMGeometry 1.2" << endl;
    if(T.m_write_tight_bbx & T.m_has_tight_bbx){
	ost << "BoundingBox " << T.m_min_tbbx << " " << T.m_max_tbbx << endl;
    } else { 	    
        ost << "BoundingBox " << T.m_min_pt << " " << T.m_max_pt << endl;
    }
    ost << "PeriodicBoundaries "  << T.m_x_periodic << " " << T.m_y_periodic << " " << T.m_z_periodic << endl;
    ost << "Dimension 3D" << endl;
    // particles
    ost << "BeginParticles" << endl;
    ost << "Simple" << endl;
    ost << nparts << endl;
    MNTCell::SetOutputStyle(1);
     for(int i=1;i<T.m_nx-1;i++){
      for(int j=1;j<T.m_ny-1;j++){
	for(int k=1;k<T.m_nz-1;k++){
	  ost <<  T.m_data[T.idx(i,j,k)];
	}
      }
     }
     ost << "EndParticles" << endl;
     // bonds
     ost << "BeginConnect" << endl;
     // sum bonds
     int nbonds=0;
     for(map<int,set<pair<int,int> > >::const_iterator iter=T.m_bonds.begin();
	 iter!=T.m_bonds.end();
	 iter++){
       nbonds+=iter->second.size();
     }
     ost << nbonds << endl;
     for(map<int,set<pair<int,int> > >::const_iterator iter=T.m_bonds.begin();
	 iter!=T.m_bonds.end();
	 iter++){
       for(set<pair<int,int> >::const_iterator v_iter=iter->second.begin();
	   v_iter!=iter->second.end();
	   v_iter++){
	 if(v_iter->first<=v_iter->second){
	   ost << v_iter->first << " " << v_iter->second << " " << iter->first << endl;
	 } else {
	   ost << v_iter->second << " " << v_iter->first << " " << iter->first << endl;
	 }
       }
     }
     ost << "EndConnect" << endl;
  } else if (MNTable3D::s_output_style==2){ // vtk-xml
    T.WriteAsVtkXml(ost);
  }

  return ost;
}

/*!
  tag all particles close to a plane

  \param P the plane
  \param dist max. distance
  \param tag the tag
  \param gid group Id
*/
void MNTable3D::tagParticlesAlongPlane(const Plane& P,double dist,int tag,unsigned int gid)
{
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	vector<Sphere*> vs=m_data[idx(i,j,k)].getSpheresNearObject(&P,dist,gid);
	for(vector<Sphere*>::iterator iter=vs.begin();
	    iter!=vs.end();
	    iter++){
	  (*iter)->setTag(tag);
	}
      }
    }
  }
}

/*!
  tag all particles inside a specified volume

  \param V the volume
  \param tag the tag
  \param gid group Id
*/
void MNTable3D::tagParticlesInVolume(const AVolume& V,int tag,unsigned int gid)
{
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	vector<Sphere*> vs=m_data[idx(i,j,k)].getSpheresInVolume(&V,gid);
	for(vector<Sphere*>::iterator iter=vs.begin();
	    iter!=vs.end();
	    iter++){
	  (*iter)->setTag(tag);
	}
      }
    }
  }
}

/*!
  tag all particles close to a plane

  \param P the plane
  \param dist max. distance
  \param the tag mask
  \param tag the tag
  \param gid group Id
*/
void MNTable3D::tagParticlesAlongPlaneWithMask(const Plane& P,double dist,int tag,int mask,unsigned int gid)
{
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	vector<Sphere*> vs=m_data[idx(i,j,k)].getSpheresNearObject(&P,dist,gid);
	for(vector<Sphere*>::iterator iter=vs.begin();
	    iter!=vs.end();
	    iter++){
	  int oldtag=(*iter)->Tag();
	  int newtag=(tag & mask) | (oldtag & ~mask);
	  (*iter)->setTag(newtag);
	}
      }
    }
  }
}

/*!
  tag all particles close to a joint set

  \param T the joint set
  \param dist max. distance
  \param the tag mask
  \param tag the tag
  \param gid group Id
*/
void MNTable3D::tagParticlesAlongJoints(const  TriPatchSet& T,double dist,int tag, int mask, unsigned int gid)
{
   for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	for(vector<Triangle3D>::const_iterator iter=T.triangles_begin();
	    iter!=T.triangles_end();
	    iter++){
	  vector<Sphere*> vs=m_data[idx(i,j,k)].getSpheresNearObject(&(*iter),dist,gid);
	  for(vector<Sphere*>::iterator iter=vs.begin();
	      iter!=vs.end();
	      iter++){
	    int oldtag=(*iter)->Tag();
	    int newtag=(tag & mask) | (oldtag & ~mask);
	    (*iter)->setTag(newtag);
	  }
	}
      }
    }
  }
}

/*!
  tag all particles fully within a sphere

  \param S the sphere
  \param tag the tag
  \param gid group Id
*/
void MNTable3D::tagParticlesInSphere(const Sphere &S,int tag,unsigned int gid)
{
  int range=int(ceil(S.Radius()/m_celldim));
  for(int i=-1*range;i<=range;i++){
    for(int j=-1*range;j<=range;j++){
       for(int k=-1*range;k<=range;k++){
	 Vector3 np=S.Center()+Vector3(double(i)*m_celldim,double(j)*m_celldim,double(k)*m_celldim);
	 int idx=this->getIndex(np);
	 if(idx!=-1){
	   multimap<double,Sphere*> v=m_data[idx].getSpheresFromGroupNearNC(S.Center(),S.Radius(),gid);
	   for(multimap<double,Sphere*>::iterator iter=v.begin();
	       iter!=v.end();
	       iter++){
	     double outer_dist=(iter->first+(iter->second)->Radius());
	     if(outer_dist<=S.Radius()) (iter->second)->setTag(tag);
	   }
	 }
      }
    }
  }
}


/*!
  tag the spheres in a group A according to the tag of the closest spheres from a another group B

  \param gid1 the group Id A
  \param gid1 the group Id B
*/

void  MNTable3D::tagParticlesToClosest(int gid1, int gid2)
{
   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++){
	vector<Sphere*> v=m_data[idx(i,j,k)].getAllSpheresFromGroupNC(gid1);
	for(vector<Sphere*>::iterator iter=v.begin();
	    iter!=v.end();
	    iter++){
	  int t=getTagOfClosestSphereFromGroup(*(*iter),gid2);
	  (*iter)->setTag(t);
	}
      }
    }
  }
}

/*!
  Tag the spheres in a group A according to the tag of the closest spheres from a another group B, using an anisotropic distance measure

  \param gid1 the group Id A
  \param gid1 the group Id B
  \param wx weighting of the x-component of the distance 
  \param wy weighting of the y-component of the distance 
  \param wz weighting of the z-component of the distance 
*/

void  MNTable3D::tagParticlesToClosestAnisotropic(int gid1, int gid2,
						  double wx, double wy,double wz)
{
  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++){
	vector<Sphere*> v=m_data[idx(i,j,k)].getAllSpheresFromGroupNC(gid1);
	for(vector<Sphere*>::iterator iter=v.begin();
	    iter!=v.end();
	    iter++){
	  int t=getTagOfClosestSphereFromGroup(*(*iter),gid2,wx,wy,wz);
	  (*iter)->setTag(t);
	}
      }
    }
  }
}


/*
  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 MNTable3D::tagParticlesInGroup(int gid, int tag, int mask)
{
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	m_data[idx(i,j,k)].tagSpheresInGroup(gid,tag,mask);
      }
    }
  }
}

/*!
  change particle IDs so that they are continous 
*/
void MNTable3D::renumberParticlesContinuous()
{
  int count=0;
  
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	count=m_data[idx(i,j,k)].renumberParticlesContinuous(count);
      }
    }
  }
}

/*!
  remove all particles with a given tag within a group, using default mask -1

  \param tag the tag
  \param gid the group Id
*/
void MNTable3D::removeParticlesWithTag(int tag,unsigned int gid)
{
  for(int i=0;i<m_nx;i++){
    for(int j=0;j<m_ny;j++){
      for(int k=0;k<m_nz;k++){
	m_data[idx(i,j,k)].removeTagged(gid,tag,-1);
      }
    }
  }
}

/*!
  remove all particles with a given tag within a group

  \param tag the tag
  \param gid the group Id
  \param mask the mask
*/
void MNTable3D::removeParticlesWithTagMask(unsigned int gid,int tag, int mask)
{
  for(int i=0;i<m_nx;i++){
    for(int j=0;j<m_ny;j++){
      for(int k=0;k<m_nz;k++){
	m_data[idx(i,j,k)].removeTagged(gid,tag,mask);
      }
    }
  }
}

/*!
  Remove all particles inside a given volume. The parameter
  "full" decides if only particles completely inside the 
  volume are removed (full==true) or all particles with the
  center inside the volume (full==false)

  \param Vol the volume
  \param full remove particles fully inside / center inside
*/
void MNTable3D::removeParticlesInVolume(AVolume3D* Vol, int gid,bool full)
{
 for(int i=0;i<m_nx;i++){
    for(int j=0;j<m_ny;j++){
      for(int k=0;k<m_nz;k++){
	m_data[idx(i,j,k)].removeInVolume(Vol,gid,full);
      }
    }
  }
}

/*!
  Remove all particle in a group. Convenience function combining
  tagParticlesInGroup and removeTaggedParticles

  \param gid the ID of the group of the particles to be removed
*/
void MNTable3D::removeParticlesInGroup(unsigned int gid)
{
	tagParticlesInGroup(gid,0,-1);
	removeParticlesWithTag(0,gid);
}

// ---------------
// bbx handling
// ---------------

/*!
   Calculate a the axis-aligned bounding box of the current particle set   	
*/
void MNTable3D::calculateTightBoundingBox()
{}

/*!
   Set bounding box tracking on/off
*/
void MNTable3D::setBoundingBoxTracking(bool b)
{
   m_bbx_tracking=b;	
}
	
/*!
   Switch if initial min/max points or current bounding box 
   is written to geo file
*/
void MNTable3D::SetWriteTightBoundingBox(bool b)
{
   m_write_tight_bbx=b;
   if(b & !m_has_tight_bbx){
	std::cout << "WARNING: Writing of actual bounding box switched on, but bounding box not calculated yet!" << std::endl; 
   }
}

/*!
  output content as VTK-XML format

  \param vtkfile the output stream
*/
void MNTable3D::WriteAsVtkXml(ostream& vtkfile) const
{
  // inverse mapping index<->particle id
  map<int,int> inv_map;

  int pcount=0;
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	vector<int> ids=m_data[idx(i,j,k)].getIdList();
	for(vector<int>::iterator iter=ids.begin();
	    iter!=ids.end();
	    iter++){
	  inv_map.insert(make_pair(*iter,pcount));
	  pcount++;
	}
      }
    }
  }
  
  // get bond count
  int bcount=0;

  for(map<int,set<bond> >::const_iterator iter=m_bonds.begin();
      iter!=m_bonds.end();
      iter++){
    bcount+=iter->second.size();
  }

  // write header 
  vtkfile << "<VTKFile type=\"UnstructuredGrid\" version=\"0.1\">\n";
  vtkfile << "<UnstructuredGrid>\n";
  int nparts=0;
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	nparts+=m_data[idx(i,j,k)].NParts();
      }
    }
  }
  vtkfile << "<Piece NumberOfPoints=\"" << nparts << "\" NumberOfCells=\"" << bcount << "\">\n";
  // write particle pos
  vtkfile << "<Points>\n";
  vtkfile << "<DataArray NumberOfComponents=\"3\" type=\"Float64\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	m_data[idx(i,j,k)].writePositions(vtkfile);
      }
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";
  vtkfile << "</Points>\n";

  // --- write particle data ---
  // radius
  vtkfile << "<PointData Scalars=\"radius\">\n";
  vtkfile << "<DataArray type=\"Float64\" Name=\"radius\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	m_data[idx(i,j,k)].writeRadii(vtkfile);
      }
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";

  // ID
  vtkfile << "<DataArray type=\"Int32\" Name=\"particleID\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	m_data[idx(i,j,k)].writeIDs(vtkfile);
      }
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";

  // Tag
  vtkfile << "<DataArray type=\"Int32\" Name=\"particleTag\" NumberOfComponents=\"1\" format=\"ascii\">\n";
  for(int i=1;i<m_nx-1;i++){
    for(int j=1;j<m_ny-1;j++){
      for(int k=1;k<m_nz-1;k++){
	m_data[idx(i,j,k)].writeTags(vtkfile);
      }
    }
  }
  vtkfile << endl;
  vtkfile << "</DataArray>\n";  

  vtkfile << "</PointData>\n";
  // --- write cells section ---
  // connections
  vtkfile << "<Cells>\n";
  vtkfile << "<DataArray type=\"Int32\" NumberOfComponents=\"1\" Name=\"connectivity\" format=\"ascii\">\n";
  for(map<int,set<bond> >::const_iterator iter=m_bonds.begin();
      iter!=m_bonds.end();
      iter++){
    for(set<bond>::const_iterator iter_b=iter->second.begin();
	iter_b!=iter->second.end();
	iter_b++){
      vtkfile << inv_map[iter_b->first] << " " << inv_map[iter_b->second] << endl;
    }
  }

  vtkfile << "</DataArray>\n";
  // offsets
   vtkfile << "<DataArray type=\"Int32\" NumberOfComponents=\"1\" Name=\"offsets\" format=\"ascii\">\n";
  for (size_t i = 1; i < bcount*2; i+=2) vtkfile << i+1 << "\n";
  vtkfile << "</DataArray>\n";

  // element type
  vtkfile << "<DataArray type=\"UInt8\" NumberOfComponents=\"1\" Name=\"types\" format=\"ascii\">\n";
  const int CELL_LINE_TYPE = 3;
  for (size_t i = 0; i < bcount; i++)   vtkfile << CELL_LINE_TYPE << endl;
  vtkfile << "</DataArray>\n";
  vtkfile << "</Cells>\n";
  
  // cell data
  vtkfile << "<CellData>\n";  
  vtkfile << "<DataArray type=\"Int32\" Name=\"bondTag\" NumberOfComponents=\"1\" format=\"ascii\">\n";
    for(map<int,set<bond> >::const_iterator iter=m_bonds.begin();
      iter!=m_bonds.end();
      iter++){
    for(set<bond>::const_iterator iter_b=iter->second.begin();
	iter_b!=iter->second.end();
	iter_b++){
      vtkfile << iter->first << endl;
    }
  }
  vtkfile << "</DataArray>\n";  
  vtkfile << "</CellData>\n";
  // write footer
  vtkfile << "</Piece>\n";
  vtkfile << "</UnstructuredGrid>\n";
  vtkfile << "</VTKFile>\n";
}

/*!
  Set output precision

  \param prec number of significant digits
*/
void MNTable3D::SetOutputPrecision(int prec)
{
  m_write_prec=prec;
}

void MNTable3D::write(char *filename, int outputStyle)
{
   ofstream outFile;
   outFile.precision(m_write_prec);

   SetOutputStyle(outputStyle);

   outFile.open(filename);
   outFile << *(this);
   outFile.close();
}

// --- block writing ---
void MNTable3D::initBlockWriting(const string& filename)
{
	m_outfilename=filename;
	m_temp_bondfilename=filename+string("_tmp_bond");
	m_block_particles_written=0;
	m_block_bonds_written=0;
	// write header 
	ofstream ost(filename.c_str());
    ost << "LSMGeometry 1.2" << endl;
    if(m_write_tight_bbx & m_has_tight_bbx){
	ost << "BoundingBox " << m_min_tbbx << " " << m_max_tbbx << endl;
    } else { 	    
        ost << "BoundingBox " << m_min_pt << " " << m_max_pt << endl;
    }
    ost << "PeriodicBoundaries "  << m_x_periodic << " " << m_y_periodic << " " << m_z_periodic << endl;
    ost << "Dimension 3D" << endl;
    // particles
    ost << "BeginParticles" << endl;
    ost << "Simple" << endl;
	m_np_write_pos=ost.tellp();
	ost << "          " << endl; // IMPORTANT: 10 spaces as placeholder for the number of particles 
	std::cout << m_np_write_pos << std::endl;
	
	m_is_writing_blocks=true;	
}
	
void MNTable3D::writeBlock(const Vector3& min_pt,const Vector3& max_pt)
{
	ofstream ost(m_outfilename.c_str(),ios::app); // open file in append mode
	ost.precision(m_write_prec);
	for(int i=1;i<m_nx-1;i++){
		for(int j=1;j<m_ny-1;j++){
			for(int k=1;k<m_nz-1;k++){
				m_block_particles_written+=m_data[idx(i,j,k)].writeParticlesInBlock(ost,min_pt,max_pt);
			}
		}
     }
	 ost.close();
	 std::cout << m_block_particles_written << std::endl;
}
	
void MNTable3D::writeBondsBlocked()
{
	ofstream ost(m_temp_bondfilename.c_str(),ios::app);
	for(map<int,set<pair<int,int> > >::const_iterator iter=m_bonds.begin();
		iter!=m_bonds.end();
		iter++){
		for(set<pair<int,int> >::const_iterator v_iter=iter->second.begin();
			v_iter!=iter->second.end();
			v_iter++){
			if(v_iter->first<=v_iter->second){
				ost << v_iter->first << " " << v_iter->second << " " << iter->first << endl;
			} else {
				ost << v_iter->second << " " << v_iter->first << " " << iter->first << endl;
			}
			m_block_bonds_written++;
		}
	}
	ost.close();
	std::cout << "bonds written: " << m_block_bonds_written << std::endl;
}

void MNTable3D::finishBlockWriting()
{
	ofstream ost(m_outfilename.c_str(),ios::in | ios::out | ios::ate); // open for writing, initially positioned at the end
	// -- write end of particles --
	ost << "EndParticles" << endl;
	// -- write bonds --
	ost << "BeginConnect" << endl;
	ost << m_block_bonds_written << endl;
	ifstream ifs(m_temp_bondfilename.c_str());
	ost << ifs.rdbuf();
	ifs.close();
	ost << "EndConnect" << endl;
	 // -- reposition to the place where nr. of particles should be 
	 ost.seekp(m_np_write_pos);
	 // overwrite placeholders
	 ost << m_block_particles_written;
	 
	 // close file
	 ost.close();
}
