#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "random.h"
#include "species.h"

#define REPRO_FACTOR 3   /* multiple of total length giving energy req. */
#define PENALTY_FACTOR 1 /* ditto, giving energy penalty for repro. */ 

#define TREPRO 1          /* local debugging printouts */
#undef TREPRO

/* flag whether the reproduction energy criterion is currently met */
int ok_to_repro(Critter *crit)
{
#ifdef TREPRO
  printf("checking repro condition: %d.  lengths: %d %d %d\n",crit->energy,
	 crit->hide_len,crit->seek_len,crit->meta_len);
#endif
  if(crit->energy>(crit->hide_len+crit->seek_len+crit->meta_len)
     *REPRO_FACTOR)  /*** use this simple function for now */
    {
      return(1);
    }
  else
    {
      return(0);
    }
}

/* compute energy penalty for reproduction */
int repro_penalty(Critter *crit) 
{
  return((crit->hide_len+crit->seek_len+crit->meta_len)*PENALTY_FACTOR);
}


/* split contents of parent's gut */
void split_gut(Critter *parent, Critter *child)
{
  int i;

/*** semi-arbitrary choice: extra odd metabolite goes to parent */
  for(i=0;i<NUM_METABOLITES;i++)
    {
      (child->gut)[i] = (parent->gut)[i]/2;
      (parent->gut)[i] -= (child->gut)[i];
    }

  parent->amt_food = 0;
  for(i=0;i<NUM_METABOLITES;i++)
    {
      parent->amt_food += (parent->gut)[i];
    }

  child->amt_food = 0;
  for(i=0;i<NUM_METABOLITES;i++)
    {
      child->amt_food += (child->gut)[i];
    }
}


/* allocate critter, with space inside for 3 chromosomes and a gut */
void allocate_new_critter(Critter **crit,int max_hide,
			  int max_seek, int max_meta)
{
  (*crit) = (struct critter_struct *) (malloc(sizeof(struct critter_struct)));
  (*crit)->hide = (Chromosome) (malloc(sizeof(Genetype)*max_hide));
  (*crit)->seek = (Chromosome) (malloc(sizeof(Genetype)*max_seek));
  (*crit)->metabolism = (Chromosome) (malloc(sizeof(Genetype)*max_meta));
}
				    


/* reproduce crit: adjust energy and place offspring after crit */
/* NOTE: this is long, but just goes through the Critter structure, */
/* making the copies or relevant changes */
/* Calls write_geneology to record */

void reproduce(Critter *crit)
{
  Critter *offspring;
  int mutation_type,i,offi,mutflag;

#ifdef TREPRO
  printf("starting a reproduction \n");
#endif

  mutflag = 0; 

  allocate_new_critter(&offspring,(crit->hide_len)*2+1,
		       (crit->seek_len)*2+1,(crit->meta_len)*2+1);
/* *2+1 ensures enough space,even with an insertion at every point(incl. end)*/
 
  the_context->critter_num ++;
  offspring->id_num = the_context->critter_num; /* global id */
  offspring->parent_id = crit->id_num;
  offspring->p_next = crit->p_next;      /* fix pointers */
  offspring->p_prev = crit;
  if(crit->p_next != NULL)
    {
      crit->p_next->p_prev = offspring;
    }
  crit->p_next = offspring;

/* copy chromosomes, with possible mutations */

/* hide chromosome */
  offspring->hide_len = crit->hide_len;
  offi = 0;   /* may be different from i due to insertion/deletion */
  for(i=0;i<crit->hide_len;i++)
    {
      if(funiform(1.0) < (the_context->mutation_prob))  /* check for mut. */
	{
	  mutation_type = uniform(3);
	  if(mutation_type==0)  /* bit flip */
	    {
	      (offspring->hide)[offi] = (1-(crit->hide)[i]);
	      offi++;
	    }
	  else if(mutation_type==1)   /* insertion */
	    {
	      (offspring->hide)[offi] = uniform(2);  /* random bit */
	      (offspring->hide)[offi+1] = (crit->hide)[i];
	      offi+=2;
	      offspring->hide_len += 1;
	    }
	  else if(mutation_type==2)   /* deletion */
	    {
	      offspring->hide_len -= 1;
	    }
	  else
	    {
	      printf("* MUTATION TYPE ERROR\n");
	    }
	}
      else  /* no mutation; simple bit copy */
	{
	  (offspring->hide)[offi] = (crit->hide)[i];
	  offi++;
	}
    }
  if(funiform(1.0) < (the_context->mutation_prob)/3.0)  
    {  /* equivalent mutation prob. since only an insertion can occur at end */
      (offspring->hide)[offspring->hide_len] = uniform(2);  /* random bit */
      offspring->hide_len += 1;
    }

/* seek chromosome */
  offspring->seek_len = crit->seek_len;
  offi = 0;   /* may be different from i due to insertion/deletion */
  for(i=0;i<crit->seek_len;i++)
    {
      if(funiform(1.0) < (the_context->mutation_prob))  /* check for mut. */
	{
	  mutation_type = uniform(3);
	  if(mutation_type==0)  /* bit flip */
	    {
	      (offspring->seek)[offi] = (1-(crit->seek)[i]);
	      offi++;
	    }
	  else if(mutation_type==1)   /* insertion */
	    {
	      (offspring->seek)[offi] = uniform(2);  /* random bit */
	      (offspring->seek)[offi+1] = (crit->seek)[i];
	      offi+=2;
	      offspring->seek_len += 1;
	    }
	  else if(mutation_type==2)   /* deletion */
	    {
	      offspring->seek_len -= 1;
	    }
	  else
	    {
	      printf("* MUTATION TYPE ERROR\n");
	    }
	}
      else  /* no mutation; simple bit copy */
	{
	  (offspring->seek)[offi] = (crit->seek)[i];
	  offi++;
	}
    }
  if(funiform(1.0) < (the_context->mutation_prob)/3.0)  
    {  /* equivalent mutation prob. since only an insertion can occur at end */
      (offspring->seek)[offspring->seek_len] = uniform(2);  /* random bit */
      offspring->seek_len += 1;
    }

/* metabolism chromosome */
  offspring->meta_len = crit->meta_len;
  offi = 0;   /* may be different from i due to insertion/deletion */
  for(i=0;i<crit->meta_len;i++)
    {
      if(funiform(1.0) < (the_context->mutation_prob))  /* check for mut. */
	{
	  mutflag = 1;
	  mutation_type = uniform(3);
	  if(mutation_type==0)  /* bit flip */
	    {
	      (offspring->metabolism)[offi] = (1-(crit->metabolism)[i]);
	      offi++;
	    }
	  else if(mutation_type==1)   /* insertion */
	    {
	      (offspring->metabolism)[offi] = uniform(2);  /* random bit */
	      (offspring->metabolism)[offi+1] = (crit->metabolism)[i];
	      offi+=2;
	      offspring->meta_len += 1;
	    }
	  else if(mutation_type==2)   /* deletion */
	    {
	      offspring->meta_len -= 1;
	    }
	  else
	    {
	      printf("* MUTATION TYPE ERROR\n");
	    }
	}
      else  /* no mutation; simple bit copy */
	{
	  (offspring->metabolism)[offi] = (crit->metabolism)[i];
	  offi++;
	}
    }
  if(funiform(1.0) < (the_context->mutation_prob)/3.0)  
    {  /* equivalent mutation prob. since only an insertion can occur at end */
      mutflag = 1;
      (offspring->metabolism)[offspring->meta_len] = uniform(2); /*random bit*/
      offspring->meta_len += 1;
    }

#ifdef TREPRO
  printf("hide chromosome: ");
  for(i=0;i<offspring->hide_len;i++)
    {
      printf("%d",(offspring->hide)[i]);
    }
  printf("\n");
  printf("seek chromosome: ");
  for(i=0;i<offspring->seek_len;i++)
    {
      printf("%d",(offspring->seek)[i]);
    }
  printf("\n");
  printf("metabolism chromosome: ");
  for(i=0;i<offspring->meta_len;i++)
    {
      printf("%d",(offspring->metabolism)[i]);
    }
  printf("\n");
#endif

  split_gut(crit,offspring);             /* divide gut contents,and set amt's*/
#ifdef TREPRO
  printf("gut contents: ");
  for(i=0;i<NUM_METABOLITES;i++)
    {
      printf("%d ",(offspring->gut)[i]);
    }
  printf("\n");
#endif

  offspring->mouth = NULL;  
  
  /* penalize and split energy: */
  crit->energy -= repro_penalty(crit);
  crit->energy /= 2;
  offspring->energy = crit->energy;

#ifdef TREPRO
  printf("offspring energy: %d\n",offspring->energy);
#endif

  offspring->age = 0;
  offspring->para_age = 0;
  offspring->num_offspring = 0;
  
  crit->num_offspring ++;
  
  offspring->num_times_host = 0;
  offspring->num_times_para = 0;
  offspring->num_curr_para = 0;

  genes_to_metab(offspring);    /* create fast_metabolism linked list */
  offspring->next_reaction = offspring->fast_metabolism;

  offspring->next_metabolizer = NULL;
  offspring->prev_metabolizer = NULL;

  /* check for new species if a metab. mutation occurred */
  if ((mutflag) && 
      (!same_species(offspring->fast_metabolism, crit->fast_metabolism))) {
#ifdef TREPRO
    printf("  new species \n");
#endif
    check_species_list(offspring, the_population);
  }
  else {  /* same species as parent */
    offspring->species = crit->species;
    increment_species_count(offspring->species);
  }
  
  /* record the event */
  write_geneology(output_file,the_context->time_step,offspring->id_num,
		  crit->id_num,offspring->fast_metabolism,
		  crit->fast_metabolism);
}




/* main reproduction function; called once per timestep */
void multiply_and_be_fruitful(Population *the_population)
{
  int i;
  Critter *curcritter;

#ifdef DEBUG
  printf(" ---in multiply_and_be_fruitful---\n");
#endif

  curcritter = the_population->critters;
  for(i=0; i<the_population->current_size; i++)
    {
#ifdef TREPRO
      printf("repro-checking individual #%d of %d\n",i,
	     the_population->current_size);
#endif
      if(ok_to_repro(curcritter))  /* check energy repro criterion */
	{
	  reproduce(curcritter);
	  the_population->current_size ++;   /* count offspring */
	}
      curcritter = curcritter->p_next;
      /***offspring can reproduce immediately,but unlikely to actually happen*/
    }
  
           /***This will require the stats object.***/
#ifdef DEBUG
  printf(" ---end multiply_and_be_fruitful---\n");
#endif
}


