/////////////////////////////////////////////////////////////
//                                                         //
// 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 "HexAggregateInsertGenerator2DRand.h"
// --- System includes ---

#include <cmath>
#include <cstdlib>
#include <sys/time.h>

using std::ceil;
using std::sqrt;
using std::sin;
using std::cos;

// --- project includes ---
#include "sphere_fitting/Sphere2DFitter.h"

HexAggregateInsertGenerator2DRand::HexAggregateInsertGenerator2DRand()
{}

/*!
  Constructor

  \param rmin minimum particle radius
  \param rmax maximum particle radius
  \param ntries max. nr. of tries to insert particle
  \param max_iter maximum iterations within the iterative solvers
  \param prec max. error in iterative solvers
  \param rprob probablility (0..1) that one of the particles is removed 
*/
HexAggregateInsertGenerator2DRand::HexAggregateInsertGenerator2DRand(double rmin,
								     double rmax,
								     int tries,
								     int max_iter,
								     double prec,
								     double rprob) : 
  HexAggregateInsertGenerator2D(rmin,rmax,tries,max_iter,prec) 
{
  m_remove_prob=rprob;
  struct timeval tv;
  gettimeofday(&tv,NULL);    
  int random_seed=tv.tv_usec;
  srand(random_seed);
}


/*!
  seed the area with particles

  \param vol a pointer to the packing volume
  \param ntable a pointer to the neighbour table used 
  \param gid particle group id
  \param tag the particle tag
*/
void HexAggregateInsertGenerator2DRand::seedParticles(AVolume2D* vol,MNTable2D*ntable ,int gid,int tag)
{
  std::cout << "HexAggregateInsertGenerator2DRand::seedParticles" << std::endl;
  // get bounding box
  pair<Vector3,Vector3> bbx=vol->getBoundingBox();
  double dx=(bbx.second.X()-bbx.first.X())-2.0*m_rmax;
  double dy=(bbx.second.Y()-bbx.first.Y())-2.0*m_rmax;
  // get index limits for seeding
  int imax=int(ceil(dx/(m_rmax*2.0)));
  int jmax=int(ceil(dy/(m_rmax*sqrt(3.0))));
  // seed positions
  for(int i=0;i<imax;i++){
    for(int j=0;j<jmax;j++){
      // get position
      double px=bbx.first.X()+m_rmax+(double(i)+0.5*double(j%2))*m_rmax*2.0;
      double py=bbx.first.Y()+m_rmax+double(j)*sqrt(3.0)*m_rmax;

      // get dist to egde
      double dex=(bbx.second.X()-px) < (px-bbx.first.X()) ? bbx.second.X()-px  : px-bbx.first.X();
      double dey=(bbx.second.Y()-py) < (py-bbx.first.Y()) ? bbx.second.Y()-py  : py-bbx.first.Y();
      
      double de=(dex<dey) ? dex : dey;
      
      // check max rad.
      if(de>m_rmin){
	// calc random radius
	double r;
	if(de<m_rmax) {
	  r=m_rmin+((de-m_rmin)*((double)(rand())/(double)(RAND_MAX)));
	} else {
	  r=m_rmin+((m_rmax-m_rmin)*((double)(rand())/(double)(RAND_MAX)));
	}
	Sphere S(Vector3(px,py,0.0),r);
	bool fit=vol->isIn(S) && ntable->checkInsertable(S,gid);
	if(fit){
	  // -- insert 7 smaller particles instead of 1 large
	  double rn=r/3.0; // new radii
	  // center sphere
	  Sphere Sc(Vector3(px,py,0.0),rn);
	  Sc.setTag(tag);
	  ntable->insertChecked(Sc,gid);
	  int Sc_id=Sc.Id();
	  // outer spheres
	  int Sk_id[6];
	  // generate random value
	  double rval=((double)(rand())/(double)(RAND_MAX));
	  int npart;
	  if(rval>m_remove_prob){
	  // if random value exceed removal probability - all 6 particles
	    npart = 6;
	  } else {
	    npart = 5;
	    Sk_id[5]=-1;
	  }
	  for(int k=0;k<npart;k++){
	    double phi=double(k)*1.04719551; // k*pi/3
	    double pxk=px+2.0000*rn*sin(phi);
	    double pyk=py+2.0000*rn*cos(phi);
	    Sphere Sk(Vector3(pxk,pyk,0.0),rn*0.9999);
	    if(vol->isIn(Sk) && ntable->checkInsertable(Sk,gid)){
	      Sk.setTag(tag);
	      ntable->insert(Sk,gid);
	      Sk_id[k]=Sk.Id();
	      ntable->insertBond(Sc_id,Sk_id[k],0); // bond between center and outer
	    } else {
	      Sk_id[k]=-1;
	    }
	  }
	  for(int k=0;k<npart;k++){
	    int k2=(k+1) % 6;
	    if((Sk_id[k]!=-1) && (Sk_id[k2]!=-1)) {
	      ntable->insertBond(Sk_id[k],Sk_id[k2],0);
	    }
	  }
	} 
      }
    }
  }
}

/*!
  seed the area with particles

  \param vol a pointer to the packing volume
  \param ntable a pointer to the neighbour table used 
  \param gid particle group id
  \param tag the particle tag
*/
void HexAggregateInsertGenerator2DRand::fillIn(AVolume2D* vol,MNTable2D* ntable ,int gid,int tag)
{
  Sphere nsph;

  int total_tries=0;
  int count_insert=0;

  int nvol=vol->getNumberSubVolumes();
  for(int ivol=0;ivol<nvol;ivol++){
    int countfail=0; // number of failed attenpts since last successfull insertion
    while(countfail<m_max_tries){
      bool findfit=false;
      Vector3 P=vol->getAPoint(ivol); // get random point within volume
      multimap<double,const Sphere*> close_particles=ntable->getSpheresClosestTo(P,3); // get 3 nearest spheres
      map<double,const Line2D*> close_lines=vol->getClosestPlanes(P,2); // get 2 nearest planes

      map<double,const AGeometricObject*> geomap;
      geomap.insert(close_particles.begin(),close_particles.end());
      geomap.insert(close_lines.begin(),close_lines.end());
      
      if(geomap.size()>=3){
	map<double,const AGeometricObject*>::iterator iter=geomap.begin();
	const AGeometricObject* GO1=iter->second;iter++;
	const AGeometricObject* GO2=iter->second;iter++;
	const AGeometricObject* GO3=iter->second;
	nsph=FitSphere2D(GO1,GO2,GO3,P,m_max_iter,m_prec);
	findfit=true;
      }

      if(findfit){
	// check if within radius range
	bool is_radius=(m_rmin<nsph.Radius()) && (m_rmax>nsph.Radius());
	
	// check inside volume, intersections ...
	bool fit=vol->isIn(nsph) && ntable->checkInsertable(nsph,gid);
	
	if(fit && is_radius){ // acceptable particle 
	  // -- insert 7 smaller particles instead of 1 large
	  double rn=nsph.Radius()/3.0; // new radii
	  double px=nsph.Center().X();
	  double py=nsph.Center().Y();
	  // center sphere
	  Sphere Sc(Vector3(px,py,0.0),rn);
	  Sc.setTag(tag);
	  ntable->insertChecked(Sc,gid);
	  int Sc_id=Sc.Id();
	  // outer sphere
	  int Sk_id[6];
	  // generate random value
	  double rval=((double)(rand())/(double)(RAND_MAX));
	  int npart;
	  if(rval>m_remove_prob){
	  // if random value exceed removal probability - all 6 particles
	    npart = 6;
	  } else {
	    npart = 5;
	    Sk_id[5]=-1;
	  }
	  for(int k=0;k<npart;k++){
	    double phi=double(k)*1.04719551; // k*pi/3
	    double pxk=px+2.0*rn*sin(phi);
	    double pyk=py+2.0*rn*cos(phi);
	    Sphere Sk(Vector3(pxk,pyk,0.0),rn*0.9999);
	    Sk.setTag(tag);
	    if(vol->isIn(Sk) && ntable->checkInsertable(Sk,gid)){
	      Sk.setTag(tag);
	      ntable->insert(Sk,gid);
	      Sk_id[k]=Sk.Id();
	      ntable->insertBond(Sc_id,Sk_id[k],0); // bond between center and outer
	    } else {
	      Sk_id[k]=-1;
	    }
	  }
	  for(int k=0;k<npart;k++){
	    int k2=(k+1) % 6;
	    if((Sk_id[k]!=-1) && (Sk_id[k2]!=-1)) {
	      ntable->insertBond(Sk_id[k],Sk_id[k2],0);
	    }
	  }
	  count_insert++;
	  if((count_insert%100)==0) std::cout << "inserted: " << count_insert << std::endl;
	  total_tries+=countfail;
	  if(countfail>m_max_tries/10) std::cout << countfail << " tries" << std::endl;
	  countfail=0; // reset failure counter
	} else countfail++; 
      } else countfail++;  
    }
  }
  std::cout << "total tries: " << total_tries << std::endl;
}
