/* This file contains code for keeping track of species and the
   parasitic interactions between them. 

   06/30/94 ML  Created.
*/
#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#include "species.h"

#define SPE_DEBUG 1 
#undef SPE_DEBUG

void print_species_list(Population *population)
{
  Species_list *cur_species;

  printf("   %d species\n",population->num_species);
  printf("   %d total critters\n",population->current_size);
  fflush(stdout);
  for (cur_species = population->species_list;
       cur_species != NULL;
       cur_species = cur_species->next_species) {
    printf("%d, ", cur_species->num_critters);
    fflush(stdout);
  }
  printf("\n");
  fflush(stdout);
}

/* Compares species (metabolisms) A and B.  Returns 1 if they are
   the same, zero otherwise */
int same_species(Reaction *A, Reaction *B)
{
  Reaction *A_ptr, *B_ptr;

#ifdef DEBUG
  printf("      ---in same_species---\n");
  fflush(stdout);
#endif

  /* go through both metabolisms simultaneously until a difference is
     found, or until one or both of them end.  If they end at the same
     time, then they are the same.  Otherwise, they are different. */
  for (A_ptr = A, B_ptr = B;
       (A_ptr != NULL) && (B_ptr != NULL)
       && (A_ptr->first_reactant == B_ptr->first_reactant)
       && (A_ptr->second_reactant == B_ptr->second_reactant);
       A_ptr = A_ptr->next_reaction, B_ptr = B_ptr->next_reaction) 
    ;
  
#ifdef DEBUG
  printf("      ---end of same_species---\n");
  fflush(stdout);
#endif

  if ((A_ptr == NULL) && (B_ptr == NULL))
    return 1;
  else
    return 0;
}

void free_species(Species_list *species)
{
  Reaction *cur_reaction, *next_reaction;

#ifdef SPE_DEBUG
  printf("       --- in free_species------\n");
  fflush(stdout);
#endif

  /* free fast_metabolism */
  cur_reaction = species->species;
  while (cur_reaction != NULL) {
    next_reaction = cur_reaction->next_reaction;
    free(cur_reaction);
    cur_reaction = next_reaction;
  }

  free(species);

#ifdef SPE_DEBUG
  printf("       --- end of free_species------\n");
#endif
}

/* This decrements the count of the number of critters of the given
   species.  It also removes and frees the species if it goes extinct. */
void decrement_species_count(Species_list *species)
{
#ifdef DEBUG
  printf("    ---in decrement_species_count---\n");
#endif

  if (species->num_critters <= 0) {
    printf("ERROR in decrement_species_count. species already extinct!\n");
    exit(10);
  }
  
  species->num_critters--;
  
  if (species->num_critters == 0) { /* species is extinct */
    the_population->num_species--;
    
    /* Note that all interaction node involving this species should 
       already have been removed. */
    if ((species->host_species != NULL) || (species->para_species != NULL)) {
      printf("ERROR in decrement_species_count. Species is extinct\n");
      printf(" but the host and parasite lists are non-NULL.\n");
      exit(10);
    }
    
    /* remove species from linked list */
    if (species->prev_species == NULL) 
      the_population->species_list = species->next_species;
    else 
      species->prev_species->next_species = species->next_species;
    
    if (species->next_species != NULL) 
      species->next_species->prev_species = species->prev_species;

    free_species(species);
  }
  
#ifdef DEBUG
  printf("    ---end of decrement_species_count---\n");
#endif
}


/* This increments the count of the number of critters of the given
   species.  It assumes that the species already exits. */
void increment_species_count(Species_list *species)
{
#ifdef DEBUG
  printf("    ---in increment_species_count---\n");
#endif

  if (species->num_critters == 0) {
    printf("ERROR in increment_species_count.  Species is extinct!\n");
    exit(10);
  }
  
  species->num_critters++;

#ifdef DEBUG
  printf("    ---end of increment_species_count---\n");
#endif
}

/* check if species is in population->species_list.  If so, it 
   increments that species critter counter, and set the species
   pointer of the critter.  If not, then it creates a node for it 
   and inserts it. */
void check_species_list(Critter *critter, Population *population)
{
  Species_list *cur_species, *new_species;
  Reaction *metab, *metab_ptr, *cur_reaction;

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

  for (cur_species = population->species_list;
       (cur_species != NULL) && 
       (!same_species(cur_species->species, critter->fast_metabolism));
       cur_species = cur_species->next_species)
    ;
    
  if (cur_species == NULL) {
    /* new species */
    population->num_species++;

    /* insert species at beginning of list */
    new_species = (Species_list *) malloc(sizeof(Species_list));
    new_species->next_species = population->species_list;
    if (population->species_list != NULL)
      population->species_list->prev_species = new_species;
    population->species_list = new_species;
    new_species->prev_species = NULL;

    new_species->host_species = NULL;
    new_species->para_species = NULL;

    new_species->num_critters = 1;
    
    /* copy reaction linked list */
    if (critter->fast_metabolism == NULL) 
      new_species->species = NULL;
    else {
      new_species->species = (Reaction *) malloc(sizeof(Reaction));
      metab_ptr = new_species->species;
      metab_ptr->reaction_type = critter->fast_metabolism->reaction_type;
      metab_ptr->first_reactant = critter->fast_metabolism->first_reactant;
      metab_ptr->second_reactant = critter->fast_metabolism->second_reactant;

      cur_reaction = critter->fast_metabolism->next_reaction;
      while (cur_reaction != NULL) {
	metab_ptr->next_reaction = (Reaction *) malloc(sizeof(Reaction));
	metab_ptr = metab_ptr->next_reaction;
	metab_ptr->reaction_type = cur_reaction->reaction_type;
	metab_ptr->first_reactant = cur_reaction->first_reactant;
	metab_ptr->second_reactant = cur_reaction->second_reactant;
	cur_reaction = cur_reaction->next_reaction;
      }
      metab_ptr->next_reaction = NULL;
    }
    critter->species = new_species;
  }

  else {  /* critter belongs to already existing species */
    critter->species = cur_species;
    increment_species_count(cur_species);
  }

#ifdef DEBUG
  printf("    ---end of check_species_list---\n");
#endif
}

/* Takes the_population's list of critters and constructs
   an appropriate species list */
void init_species_list(Population *population)
{
  Critter *cur_crit;
  
#ifdef DEBUG
  printf("    ---in init_species_list---\n");
#endif
  
  population->num_species = 0;
  population->species_list = NULL;

  for (cur_crit = population->critters;
       cur_crit != NULL;
       cur_crit = cur_crit->p_next)
    check_species_list(cur_crit, population);
  
#ifdef DEBUG
  printf("    ---end of init_species_list---\n");
#endif
}  



/* Returns the Interaction_node containing the information about
   the given host and parasite species (as identified by their
   metabolisms).  Returns NULL if there are currently no interactions. */
Interaction_node *find_node(Species_list *host, Species_list *parasite)
{
  Interaction_node *cur_node;

#ifdef SPE_DEBUG
  printf("    ---in find_node---\n");
#endif

  /* Find parasite species in cur_host's parasite list */
  for (cur_node = host->para_species;
       (cur_node != NULL) && (cur_node->para_species !=  parasite);
       cur_node = cur_node->next_para) ;

#ifdef SPE_DEBUG
  printf("    ---end find_node---\n");
#endif
  
  return cur_node;
}

/* removes node from population->species_list, 
   and frees the relevant memory. */
void remove_interaction_node(Interaction_node *node)
{
#ifdef SPE_DEBUG
  printf("    ---in remove_interaction_node---\n");
#endif

  /* remove node from para's host list */
  if (node->prev_host == NULL)
    node->para_species->host_species = node->next_host;
  else
    node->prev_host->next_host = node->next_host;
  if (node->next_host != NULL)
    node->next_host->prev_host = node->prev_host;

  /* remove node from host's para list */
  if (node->prev_para == NULL)
    node->host_species->para_species = node->next_para;
  else
    node->prev_para->next_para = node->next_para;
  if (node->next_para != NULL)
    node->next_para->prev_para = node->prev_para;
  
  /* free memory */
  free(node);

#ifdef SPE_DEBUG
  printf("    ---end of remove_interaction_node---\n");
#endif
}

/* decrement the number of current interactions between the host species
   and the parasite species, removing the node if necessary. */
void decrement_interactions(Species_list *host, Species_list *para)
{
  Interaction_node *hp_interactions;

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

  hp_interactions = find_node(host, para);

  if (hp_interactions == NULL) {
    printf("ERROR in file species.c: decrement_interactions.\n");
    printf("  parasite does not exist in host's parasite list.\n");
    exit(10);
  }

  if (hp_interactions->num_interactions <= 0) {
    printf("ERROR in decrement_interactions.  number already = 0.\n");
    exit(10);
  }  

  hp_interactions->num_interactions--;

  /* remove node from list if no more interactions */
  if (hp_interactions->num_interactions == 0) 
    remove_interaction_node(hp_interactions);

#ifdef DEBUG
  printf("    ---end of decrement_interactions---\n");
#endif
}


  
/* Creates the Interaction_node for host_species and para_species.
   Assumes that the node does not already exist; it just goes ahead
   and creates it.  It is also assumed that both host_species and
   para_species are already in population->species_list. 
   Returns a pointer to the created node. */
Interaction_node *create_interaction_node(Species_list *host_species, 
					  Species_list *para_species)
{
  Interaction_node *node;

#ifdef SPE_DEBUG
  printf("    ---in create_interaction_node---\n");
#endif

  node = (Interaction_node *) malloc(sizeof(Interaction_node));
  /* set host_species and para_species fields */
  /* Note that nothing is allocated here. */
  node->host_species = host_species;
  node->para_species = para_species;

  /* insert node at beginning of para's host list */
  node->next_host = para_species->host_species;
  para_species->host_species = node;
  node->prev_host = NULL;
  if (node->next_host != NULL)
    node->next_host->prev_host = node;

  /* insert node at beginning of host's para list */
  node->next_para = host_species->para_species;
  host_species->para_species = node;
  node->prev_para = NULL;
  if (node->next_para != NULL)
    node->next_para->prev_para = node;

#ifdef SPE_DEBUG
  printf("    ---end of create_interaction_node---\n");
#endif

  return node;
}


/* increment the number of current interactions between the host species
   and the parasite species, adding a node if necessary. */
void increment_interactions(Species_list *host, Species_list *parasite)
{ 
  Interaction_node *hp_interactions;

#ifdef DEBUG
  printf("    ---in increment_interactions---\n");
#endif
  
  hp_interactions = find_node(host, parasite);

  if (hp_interactions != NULL) 
    hp_interactions->num_interactions++;
  else {  /* node doesn't exist yet */
    hp_interactions =
      create_interaction_node(host, parasite);
    hp_interactions->num_interactions = 1;
  }

#ifdef DEBUG
  printf("    ---end of increment_interactions---\n");
#endif
}

