#include <string>
std::string version("MatchingModel 1.0$");

#include <signal.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <algorithm>

#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>

// simple error handling and debugging:
#define FATAL_ERROR(MSG) do{					\
  std::cout << __FILE__ << ':' << __LINE__ << ':' << MSG << std::endl;	\
  std::cout << "aborting." << std::endl ; \
  abort(); \
}while(0)
#define REPORT(X) std::cout << #X << " = " << (X) << std::endl
#if 0 // do we need debugging?
#define ASSERT(X) do{if(!(X)) FATAL_ERROR("Assertion " #X " failed");}while(0)
#else
#define ASSERT(X)
#endif
// end error handling

// a few utilities:
#include "sequence.h"
#include "ublasCholesky.hpp"
#include "normsinv.h"
// end utilities

// random numbers:
using namespace boost::numeric::ublas;
typedef matrix<double> Matrix;
typedef vector<double> Vector;
typedef boost::mt19937 base_generator_type;
base_generator_type generator(42u);
boost::uniform_real<> uni_dist(0,1);
boost::variate_generator<base_generator_type&, boost::uniform_real<> > 
unirand(generator, uni_dist);
boost::normal_distribution<> normal_dist;
boost::variate_generator<base_generator_type&, boost::normal_distribution<> >
gaussian(generator, normal_dist);
int random_integer(int i){return int(i*unirand());}
static unsigned int random_seed=1;
// end random numbers

using namespace std;

void read_arguments(int argc,char *argv[]){
  int c;
 
  while(1){
    c=getopt(argc, argv, "hvpm:P");
    if (c == -1)
      break;
    
    switch(c){
    case 'v':
      printf("%s\n",version.c_str());
      printf("%s\n","(c) 2008, Axel G. Rossberg. License: GPL 3");
      exit(0);
      break;
    case 'h':
    default:
        fprintf(stdout,"\nusage: %s -v -h\n",argv[0]);
        exit(c=='h'?0:1);
      }
  }
  argc-=optind-1;
  argv+=optind-1;

}

// model parameters:
double r1,rp,rm,kappa,rho,D,lambda,pf,pv,C0;

// sample the number of clades:
void set_n_clades(int & n_clades){
  // n_clades is Poisson distributed:
  const double mu=-kappa*log(1-rho); //expectation value
  // n_clades=poisson(mu);
  // return;
  //// hand made:
  const double full=exp(mu);
  const double target=full*unirand();
  int k=0;
  double last=1;
  double sum=1;
  while(sum<target){
    k++;
    last*=mu/k;
    sum+=last;
  }
  n_clades=k;
}

// sample a random clade size:
int random_clade_size(){
  // clade size is log-series distributed_clades is Poisson distributed:
  const double mu=-kappa*log(1-rho); //expectation value
  // n_clades=poisson(mu);
  // return;
  //// hand made:
  const double full=-log(1-rho);
  const double target=full*unirand();
  int k=1;
  double last=rho;
  double sum=rho;
  while(sum<target){
    k++;
    last=last*rho;
    sum+=last/k;
  }
  return k;
}

class degree_t:public  sequence< sequence < int > >{
public:
  int & operator()(int i,int j){
    if(i>=j)
      return (*this)[i][j];
    else
      return (*this)[j][i];
  }
};

// symmetrize indices for degrees of relation:
#define DEGREEP(I,J) ((I)>=(J)?degree_of_relation_plus_one[(I)][(J)]:degree_of_relation_plus_one[(J)][(I)])

// link two phylogenetic trees into one:
void link_trees(int s1,int s2,int &open_degrees,
		degree_t &degree_of_relation_plus_one ,
		int virtual_start){
  // from the distances in the two trees starting at s1 and at s2
  // compute the distances between all members of s1 and s2.

  // all species
  for(int i=degree_of_relation_plus_one.size();
      i-->0;){
    if(DEGREEP(i,s1) && i!=s1){
      // i is a proper sub-tree of s1
      for(int j=degree_of_relation_plus_one.size();j-->0;){
	if(DEGREEP(j,s2) && j!=s2){
	  // j is a proper sub-tree of s2
	  DEGREEP(i,j)=DEGREEP(i,s1)+DEGREEP(j,s2);
	  if(i<virtual_start && j<virtual_start){
	    open_degrees--;
	  }
	}
      }
    }
  }
  for(int i=degree_of_relation_plus_one.size();
      i-->0;){
    if(i!=s1 && i!=s2){
      if(DEGREEP(i,s1)){
	DEGREEP(i,s2)=DEGREEP(i,s1)+1;
	if(i<virtual_start && s2<virtual_start)
	  open_degrees--;
      }else if(DEGREEP(i,s2)){
	DEGREEP(i,s1)=DEGREEP(i,s2)+1;
	if(i<virtual_start && s1<virtual_start)
	  open_degrees--;
      }
    }
  }
  DEGREEP(s1,s2)=2;
  if(s1<virtual_start && s2<virtual_start){
    open_degrees--;
  }
}

// sample a phylogenetic tree for a clade:
void get_degrees_of_relation(const int clade_size,
			     degree_t & degree_of_relation_plus_one){
  sequence< char > exists;

  for(int i=clade_size;i-->0;){
    degree_of_relation_plus_one[i]*=0;
    degree_of_relation_plus_one[i][i]=1;
    exists[i]=true;
  }

  int open_degrees=(clade_size*(clade_size-1))/2;
  int n_species=clade_size;
  int n_sites=n_species; //some sites later become empty!!
  double pfrac=rho/(1+rho);
  while(open_degrees){
    if(unirand()<pfrac){
      // add one species (extinction backward in time)
      if(n_sites<n_species+clade_size){
	exists[n_sites]=true;
	degree_of_relation_plus_one[n_sites]*=0;
	degree_of_relation_plus_one[n_sites][n_sites]=1;
	n_sites++;
	n_species++;
      }else{//recycle:
	int reborn=clade_size;
	while(exists[reborn]) reborn++;
	exists[reborn]=true;
	for(int i=n_sites;i-->0;){
	  degree_of_relation_plus_one(i,reborn)=0;
	}
	degree_of_relation_plus_one(reborn,reborn)=1;
	n_species++;
      }
    }else{
      // link two species (speciation backward in time)
      int parent,child;
      
      // get parent
      do{
	parent=random_integer(n_sites);
      }while(!exists[parent]);
      
      // get child
      do{
	child=random_integer(n_sites);
      }while(parent==child || !exists[child]);
      

      link_trees(parent,child,open_degrees,
		 degree_of_relation_plus_one,clade_size);

      exists[child]=false;
      n_species--;
    }
  }
}

void get_central_multi_normal(Matrix & cov,
			      Vector & result){
  int n=cov.size2();
  Matrix & M=cov;
  Vector xi(n);
  cholesky_factorize(M);
  for(int i=n;i-->0;){
    xi[i]=gaussian();
  }
  result=prod(M,xi);
  return;
}

// sample log body sizes for a clade:
void get_log_body_size(const int clade_size,
		       degree_t & degree_of_relation_plus_one,
		       sequence< double > & log_body_size){
  if(clade_size==1){
    log_body_size[0]=unirand();
    return;
  }
  const int size_m=clade_size-1;
  Matrix cov=zero_matrix<double>(size_m,size_m);
  for(int i=size_m;i-->0;){
    cov(i,i)=DEGREEP(i,size_m)-1;
    for(int j=i;j-->0;){
      cov(i,j)=cov(j,i)=
	(DEGREEP(i,size_m)+
	 DEGREEP(j,size_m)-
	 DEGREEP(i,j)-1
	 )/2;
    }
  }
  Vector size_spread;
  get_central_multi_normal(cov,size_spread);
  
  sequence< double >  clade_body_size;
  double size_offset=unirand()-0.5;
  clade_body_size[size_m]=size_offset;
  for(int i=size_m;i-->0;){
    clade_body_size[i]=size_spread[i]*sqrt(D)+size_offset;
  }
  
  // project into interval [0,1[:
  for(int i=clade_size;i-->0;){
    double s=clade_body_size[i];
    while(s>=1 || s<0){
      if(s>=1) 
	s=2-s;
      else
	s=-s;
    }
    log_body_size[i]=s;
  }
}

// compute powers of some number b and remember the results:
class memory_power{
  const double base;
  sequence < double > pow;
  memory_power():base(0){};//no default constructor;
public:
  memory_power(double b);
  double operator()(int i);
};

memory_power::memory_power(double b):
  base(b),pow(1)
{
  pow[0]=1;
}

double memory_power::operator()(int i){
  ASSERT(i>=0);
  if(i>=pow.size()){
    for(int j=pow.size();j<=i;j++){
      pow[j]=pow(j-1)*base;
    }
  }
  return pow[i];
}

// used to sort species by size:
class is_larger{
  sequence<double> & log_size;
public:
  is_larger(sequence<double> & ls):log_size(ls){};
  bool operator()(int i,int j){
    return log_size[i]>log_size[j];
  }
};

int main(int argc,char *argv[]){
  read_arguments(argc,argv);
  
  random_seed=::time(0) + getpid();
  REPORT(random_seed);
  generator.seed(random_seed);
  
  rm=1;
  D=0.05*0.05;  //Note:  D=0.05 in original paper is a typo!

  //Bengueal Current
  r1=2.8;
  rp=1-0.080;
  lambda=0.027;
  C0=0.38;
  pv=0.003;
  pf=0.32;
  
//   //Reef
//   r1=0.48;
//   rp=1-0.0082;
//   lambda=0.068;
//   C0=0.29;
//   pv=0.008;
//   pf=0.39;
  
//   //Coachella Valley 
//   r1=1.4;
//   rp=1-0.049;
//   lambda=0.034;
//   C0=0.71;
//   pv=0.002;
//   pf=0.10;

//   // Ythan 1
//   r1=1.0;
//   rp=1-0.010;
//   lambda=0.0029;
//   C0=0.08;
//   pv=0.033;
//   pf=0.037;

//   // Scotch Broom
//   r1=1.3;
//   rp=1-0.0067;
//   lambda=0.001;
//   C0=0.03;
//   pv=0.031;
//   pf=0.006;
  
//   //British Grassland 
//   r1=1.4;
//   rp=1-0.014;
//   lambda=0;
//   C0=0.09;
//   pv=0.014;
//   pf=0.013;

//   //Tester 
//   r1=0.5;
//   rp=1-0.025;
//   lambda=0.01;
//   C0=0.01;
//   pv=0.001;
//   pf=0.5;

//   //Little Rock Lake
//   r1=1.3;
//   rp=1-0.0092;
//   lambda=0.25;
//   C0=0.12;
//   pv=0.001;
//   pf=0.043;

//   //El Verde Rainforest 
//   r1=0.76;
//   rp=1-0.0054;
//   lambda=0.12;
//   C0=0.09;
//   pv=0.015;
//   pf=0.036;

  cout << endl;
  cout << "invasion rate:   ";  REPORT(r1);
  cout << "speciation rate: ";  REPORT(rp);
  cout << "extinction rate: ";  REPORT(rm);
  kappa=r1/rp;
  rho=rp/rm;
  cout << endl;
  cout << "evolution rates of..." << endl;
  cout << "log size:             ";  REPORT(D);
  cout << "vulnerability traits: ";  REPORT(pv);
  cout << "foraging traits:      ";  REPORT(pf);
  cout << endl;
  cout << "log PPMR threshold: ";  REPORT(-lambda);
  double x0=-normsinv(C0);
  cout << "connectance within allowed size range: "; REPORT(C0);
  cout << "log link-strength threshold: "; REPORT(x0);


  int n_clades;
  sequence< int > clade_size;
  sequence< sequence< double > > log_body_size;
  sequence< degree_t > degrees;
  sequence< int > offset;
  sequence< Matrix > fCholesky,vCholesky;
  memory_power pow_f(1-2*pf),pow_v(1-2*pv);

  // sample number of clades:
  do{
    set_n_clades(n_clades);
  }while(n_clades==0);

  cout << "sampling phylogenies..." << endl;
  // sample sizes of clades, phylogenetic trees, and trait covariances:
  int S0=0;
  for(int i=0;i<n_clades;i++){
    offset[i]=S0;
    clade_size[i]=random_clade_size();
    get_degrees_of_relation(clade_size[i],degrees[i]);
    get_log_body_size(clade_size[i],degrees[i],log_body_size[i]);
    fCholesky[i]=Matrix(clade_size[i],clade_size[i]);
    vCholesky[i]=Matrix(clade_size[i],clade_size[i]);
    for(int k=clade_size[i];k-->0;){
      for(int l=clade_size[i];l-->0;){
	fCholesky[i](k,l)=
	  pow_f(degrees[i](k,l)-1);
	vCholesky[i](k,l)=
	  pow_v(degrees[i](k,l)-1);
      }
    }
    cholesky_factorize(fCholesky[i]);
    cholesky_factorize(vCholesky[i]);

    degrees[i].resize(clade_size[i]);
    S0+=clade_size[i];
  }
 
  // sample log link strengths and threshold to get links:
  matrix<bool> m=zero_matrix<bool>(S0);
  sequence<double> community_log_body_size(S0);

  cout << "sampling links..." << endl;
  for(int f=n_clades;f-->0;){
    for(int v=n_clades;v-->0;){
      Matrix x(clade_size[v],clade_size[f]);
      for(int fi=clade_size[f];fi-->0;){
	for(int vi=clade_size[v];vi-->0;){
	  x(vi,fi)=gaussian();
	}
      };
      x=prod(vCholesky[v],x);
      x=prod(x,trans(fCholesky[f]));
      
      for(int fi=clade_size[f];fi-->0;){
	community_log_body_size[offset[f]+fi]=log_body_size[f][fi];
	for(int vi=clade_size[v];vi-->0;){
	  if(log_body_size[f][fi]>log_body_size[v][vi]-lambda){
	    if(x(vi,fi)>x0){
	      m(offset[v]+vi,offset[f]+fi)=true;
	    }
	  }
	}
      }
      
    }
  }

  // compute ordering of species by size:
  std::vector<int> size_order(S0);
  for(int i=S0;i-->0;) size_order[i]=i;
  sort(size_order.begin(),size_order.end(),is_larger(community_log_body_size));


  // print adjacency matrix:
  cout << "*";
  for(int j=0;j<S0;j++){
    cout << '*';
  }
  cout << endl;
  cout << "Sample web (without standardization):" << endl;
  cout << " ";
  for(int j=0;j<S0;j++){
    cout << j%10;
  }
  cout << endl;
  for(int i=0;i<S0;i++){
    cout << i%10;
    for(int j=0;j<S0;j++){
      cout << (m(size_order[i],size_order[j])?'X':' ');
    }
    cout << i%10;
    cout << endl;
  }
  cout << " ";
  for(int j=0;j<S0;j++){
    cout << j%10;
  }
  cout << endl;
}

// Local Variables:
// compile-command: "g++ --ansi --pedantic MatchingModel.cc -o MatchingModel"
// End:
