#include "head.h"
#include "process.h"
#include "cover.h"
#include "maxmeasure.h"
#include "minmeasure.h"
#include "maxbirth.h"
#include "minbirth.h"
#include "maxdeath.h"
#include "mindeath.h"
#include "ssort.h"
#include "rand01.h"
#define exp1(tseed) -log(rand1(tseed))

/* compare1 is the procedure pointed to in sort() when sorting next birth 
   times.  It compares two locations pointed to by pointers i and j. */
double compare1(             /* Returns the difference of */
		             /* j->nextbirth.birth and */
		             /* i->nextbirth.birth for */
		             /* the sorting algorithm. */
		location *i, /* i and j are two locations to */
		location *j  /* compare for the sorting */
		);           /* algorithm */

/* compare2 is the procedure pointed to in sort() when sorting next death 
   times.  It compares two locations pointed to by pointers i and j. */
double compare2(             /* returns the difference of */
		             /* j->alive[0].maxdt and */
		             /* i->alive[0].maxdt for */
                             /* the sorting algorithm. */
		location *i, /* i and j are two locations to */
		location *j  /* compare for the sorting */
		);           /* algorithm */

/* Note: THIS ROUTINE ASSUMES PERIODICITY!!! */
/* It also assumes that ALL points are included in the process. */
int process(                     /* Returns 0 if coalescence has */
	                         /* occured and 1 otherwise */
	    double time,         /* Value of $-M$ */
	    long length,         /* Number of data points */
	    double gamma,        /* Value of $\gamma$ */
	    double sigma,        /* Value of $\sigma$ */
	    double tau,          /* Value of $\tau$ */
	    double exponent,     /* The power we raise $J_{jk}$ to. */
	                         /* Determines tails of the prior */
	    location points[]    /* All info about all the points */
	    )

{
  long i,j,k;
  long nextev;       /* Next location where a birth happens */
  long tseed,mseed;
  unsigned long gap; /* Gap in a binary search */
  int difference;    /* 1 means no coalescence, 0 means coalescence */
  int nonnull;       /* 1 means still more births before time 0 */

  double t;
  double mxmeasure,mnmeasure;   /* $m(x\oplus G\setminus X\oplus G)$ */
  double pmaxvalue,pminvalue;   /* Acceptance probabilities */
  double g_to_m_max,g_to_m_min; /* $\gamma^{m(x\oplus G\setminus X\oplus G)}$*/

  double s2;              /* These five variables are used in common */
  double jxtoz;           /* sub-expression elimination.  They are: */
  double jntoz;           /* $\sigma^2$, $(J^{max}_{jk})^z$, $(J^{min}_{jk})^z$, $(J^{max}_{jk}+1)^z$ */
  double jxp1toz;         /* and $(J^{min}_{jk}+1)^z$ respectively, where */
  double jnp1toz;         /* $z$ is given by \texttt{exponent} */

  double currdt; /* Death time of the point we're currently examining */

  lifetime *events;    /* The events at the location where a point */
                       /* has just been born which happen between */
                       /* that point's birth time and 0 */
  location **ppointsb; /* Pointers to the locations, sorted by */
  location **ppointsd; /* birth and by death respectively */

  location *currpointb; /* Pointer to location where next birth occurs */
  location *currpointd; /* Pointer to location where next death occurs */

  location dummy;      /* A dummy location used to speed up searching */

  s2 = sigma*sigma;

  ppointsb = (location **)malloc((length+1)*sizeof(location *));
  ppointsd = (location **)malloc((length+1)*sizeof(location *));

/* Set up dummy and set ppointsd[length] and ppointsb[length] to point 
   to it.  This is to speed up the time it takes to search for the 
   correct place to insert a new birth or death time. */
  dummy.maxdt=(double *)malloc(2*sizeof(double)); /* Allocate space*/
  dummy.maxdt[1]=-2;
  dummy.nextbirth.birth=-1;
  ppointsb[length] = ppointsd[length] = &dummy;

/* Here we initialise XplusG.min, XplusG.max and ppoints. */
  /* BEGIN STEP 1 */
  for(i=0;i<length;++i){
    ppointsb[i] = ppointsd[i] = &points[i];
    if(points[i].life.max > 0){
      points[i].XplusG.max = 1;
      if(points[i].life.min > 0)
	points[i].XplusG.min = 1;
      else
	points[i].XplusG.min = 0;
    }
    else
      points[i].XplusG.max = points[i].XplusG.min = 0;
  }
  covered(points,length);
  /* END STEP 1 */

/* Now sort the pointers to the points to get them initially in order */
  /* BEGIN STEP 2 */

  sort((void **) ppointsb, length, 
       (double (*)(void *, void *)) compare1);
  sort((void **) ppointsd, length, 
       (double (*)(void *, void *)) compare2);

  /* END STEP 2 */

/*  while next event is before time zero (otherwise we have finished) */
  /* BEGIN STEP 3 */
  while(((*ppointsd)->maxdt[1]>0) || 
	((*ppointsb)->nextbirth.birth>0)){

    currpointb = *ppointsb;  /* Update current next-birth location */
    currpointd = *ppointsd;  /* Update current next-death location */

/* The rest of the loop is devoted to updating the min and max processes 
   according to the birth or death of a new point and then updating 
   the times until the next event and the points[].order values. */
    /* BEGIN STEP A */
/* Process deaths. */
    if(currpointb->nextbirth.birth < currpointd->maxdt[1]){

      /* BEGIN STEP I */
/* One less point at currpointd. */
      --currpointd->life.max;

/* If point in min process too */
      if(currpointd->maxdt[1] == currpointd->mindt[1]){
/* One less point there */
	--currpointd->life.min;

/* If that was last point at currpointd */
	if(currpointd->life.min==0){
	  if(length>64)
/* Update XplusG.min only if lots of locations */
	    mindeath(currpointd->label,length,points);
	  else
/* Update XplusG for both if only a few locations */
	    covered(points,length);
/* Next death a long way away (past the end at $t=0$) */
	  currpointd->mindt[1] = -1;
	}

/* Shuffle up mindt. */
	for(j=0;j<currpointd->life.min;++j)              
	  currpointd->mindt[1+j] = currpointd->mindt[j+2];
      }

/* If that was last point at currpointd */
      if(currpointd->life.max==0){                  
	if(length>64)
/* Update XplusG.max only if lots of locations */
	  maxdeath(currpointd->label,length,points);
	else
/* Update XplusG for both if only a few locations */
	  covered(points,length);
/* Next death long way away (past the end at $t=0$) */
	currpointd->maxdt[1] = -1;
      }
      /* END STEP I */

      /* BEGIN STEP II */
/* Shuffle up maxdt */
      for(j=0;j<currpointd->life.max;++j)              
	currpointd->maxdt[j+1] = currpointd->maxdt[j+2];
      
      currdt = currpointd->maxdt[1];
/* Reorder locations according to new maxdt */  
      for(i=1; currdt < (ppointsd[i])->maxdt[1]; ++i)
	ppointsd[i-1]=ppointsd[i];
      ppointsd[i-1]=currpointd;
      /* END STEP II */
    }
    /* END STEP A */

    /* BEGIN STEP B */
    else{                     /* Process births */
/* Calculate size of m(XplusG) for min and max processes */
      /* BEGIN STEP I */
      g_to_m_min=g_to_m_max=1;               /* Initialise */
/* If no points currently at this location */
      if(currpointb->life.max == 0){
/* Then find measure for max process (otherwise will be 0) */
	maxmeasure(currpointb->label,length,&mxmeasure,points);
/* Update \texttt{g\_to\_m\_max} (no need if measure=0) */
	g_to_m_max=pow(gamma,-mxmeasure);
      }
/* Same as for max process */
      if(currpointb->life.min == 0){
	minmeasure(currpointb->label,length,&mnmeasure,points);
	g_to_m_min=pow(gamma,-mnmeasure);
      }

/* The following are some auxilliary variables for common sub-expression 
   elimination. */
      jxtoz = tau*pow((double)currpointb->life.max,exponent);
      jntoz = tau*pow((double)currpointb->life.min,exponent);
      jxp1toz = tau*pow((double)currpointb->life.max+1,exponent);
      jnp1toz = tau*pow((double)currpointb->life.min+1,exponent);

/* Now calculate rejection probabilities.  See thesis for details. */
      pmaxvalue = g_to_m_max * sqrt( (jxtoz+s2) / (jxp1toz+s2) ) * 
	exp( -(currpointb->data*currpointb->data * tau * 
	       (s2 * (jntoz + jnp1toz - tau) + jntoz * jnp1toz) / 
	       2*((jnp1toz + s2) * (jntoz+s2) * (s2+tau) * s2) ) );
      pminvalue = g_to_m_min * sqrt((jntoz+s2)/(jnp1toz+s2)) * 
	exp( -(currpointb->data*currpointb->data * tau * 
	       (s2 * (jxtoz + jxp1toz - tau) + jxtoz * jxp1toz) / 
	       2*((jxp1toz + s2) * (jxtoz+s2) * (s2+tau) * s2) ) );
      /* END STEP I */

      /* BEGIN STEP II */
/* If we accept the point then... */
      if(currpointb->nextbirth.mark<pmaxvalue){
/* ...If no points currently at that location */
	if(currpointb->life.max==0)
/* Update XplusG.max */
	  maxbirth(currpointb->label,length,points);

/* Add point to death list.  Start at end of list since new death likely 
   to be after most existing deaths */
	for(j=currpointb->life.max;
	    currpointb->nextbirth.death > currpointb->maxdt[j];
	    --j
	    )
/* Reorder `deaths' according to value of new event */
	  currpointb->maxdt[j+1]=currpointb->maxdt[j]; 

/* If inserting at head of list must re-order death pointers!!! */
	if(j==0){
	  currdt = currpointb->nextbirth.death;

/* Find the position in death-order of the location where our new point is 
   being born.  We must insert new death in between the head of the list 
   and here, since new point dies before previous soonest-dying point at 
   this location. */
	  for(i=0;ppointsd[i]->label!=currpointb->label;++i);

/* Use a binary search to find the place we must insert new death */
	  gap=(unsigned long) (length+1)/2;
	  k=(long)gap;
	  while(gap>1){
	    gap=gap>>1;
	    if(currdt<(ppointsd[k-1])->maxdt[1])
	      k+=(long)gap;
	    else
	      k-=(long)gap;
	  }

	  if(currdt>=(ppointsd[k-1])->maxdt[1])
	    --k;
/* Reorder locations according to new maxdt */
	  for(--i;i>=k;--i)
	    ppointsd[i+1]=ppointsd[i];
	  ppointsd[i+1]=currpointb;

	}
/* Add the point! */
	currpointb->maxdt[j+1]=currpointb->nextbirth.death;
	++currpointb->life.max;
      }
      /* END STEP II */

      /* BEGIN STEP III */
/* Same as max above except no need to re-order death pointers since 
   ordering done according to max process deaths. */
      if(currpointb->nextbirth.mark<pminvalue){   
/* Add point to death list */
	minbirth(currpointb->label,length,points);
/* Reorder `deaths' according to value of new event */
	for(j=currpointb->life.min;
	      currpointb->nextbirth.death > currpointb->mindt[j];
	    --j
	    )
	  currpointb->mindt[j+1]=currpointb->mindt[j];
	currpointb->mindt[j+1]=currpointb->nextbirth.death;

	++currpointb->life.min;
      }
      /* END STEP III */

/* Now we need to find the time of the birth after the one we have just 
   dealt with, so we need to allocate space for these events, calculate 
   them, sort pointers to them according to birth times and finally 
   work out which one is for us! */
      /* BEGIN STEP IV */
      events = 
	(lifetime *) malloc(currpointb->events*sizeof(lifetime));
      tseed=currpointb->timeseed;
      mseed=currpointb->markseed;
      nextev=nonnull=0;

      for(j=0;j<currpointb->initlives;++j){
	events[j].death=0;
	events[j].birth=exp1(&tseed);
	events[j].mark=rand1(&mseed);
	if(events[j].birth<currpointb->nextbirth.birth && 
	   (events[j].birth>=events[nextev].birth || 
	    events[nextev].birth>=currpointb->nextbirth.birth)){
	  nextev=j;
	  nonnull=1;
	}
      }

      for(t=0;
	  t<currpointb->nextbirth.birth && j<currpointb->events;
	  ++j){
	events[j].death=t+=exp1(&tseed)/currpointb->lambda;
	events[j].birth=t+exp1(&tseed);
	events[j].mark=rand1(&mseed);
	if(events[j].birth<currpointb->nextbirth.birth && 
	   (events[j].birth>=events[nextev].birth || 
	    events[nextev].birth>=currpointb->nextbirth.birth)){
	  nextev=j;
	  nonnull=1;
	}
      }

      if(nonnull==0 && nextev==0){
	currpointb->nextbirth.birth = 0;
	currpointb->nextbirth.death = 0;
	currpointb->nextbirth.mark = 0;
      }
      else{
	currpointb->nextbirth.birth = events[nextev].birth;
	currpointb->nextbirth.death = events[nextev].death;
	currpointb->nextbirth.mark = events[nextev].mark;
      }

      free(events);
      /* END STEP IV */
      
      /* BEGIN STEP V */
/* Reorder `order' according to value of new event */
      for(i=1;
	  currpointb->nextbirth.birth < 
	    (ppointsb[i])->nextbirth.birth;
	  ++i)
	ppointsb[i-1]=ppointsb[i];
      ppointsb[i-1]=currpointb;
      /* END STEP V */
    }
    /* END STEP B */
  }
  /* END STEP 3 */

  /* BEGIN STEP 4 */
  difference=0;
  for(i=0;i<length;++i)
    if(points[i].life.max!=points[i].life.min){
      difference=1;
      break;
    }

  free(ppointsb);
  free(ppointsd);
  for(i=0;i<length;++i){
    free(points[i].maxdt);
    free(points[i].mindt);
  }

  if(difference)
    return 0;
  else
    return 1;
  /* END STEP 4 */
}

/* compare1 is the procedure pointed to in sort() when sorting next birth 
   times.  It compares two locations pointed to by pointers i and j. */
double compare1(             /* Returns the difference of */
		             /* j->nextbirth.birth and */
		             /* i->nextbirth.birth for */
		             /* the sorting algorithm. */
		location *i, /* i and j are two locations to */
		location *j  /* compare for the sorting */
		)            /* algorithm */

{
  return(i->nextbirth.birth - j->nextbirth.birth);
}

/* compare2 is the procedure pointed to in sort() when sorting next death 
   times.  It compares two locations pointed to by pointers i and j. */
double compare2(             /* returns the difference of */
		             /* j->alive[0].maxdt and */
		             /* i->alive[0].maxdt for */
                             /* the sorting algorithm. */
		location *i, /* i and j are two locations to */
		location *j  /* compare for the sorting */
		)            /* algorithm */

{
  return(i->maxdt[1] - j->maxdt[1]);
}
