// FILE: ecosystem.cpp
//
// A simple simulation program to model an island that contains foxes,
// geese, and ferns.

#include <iostream>    // Provides cin, cout
#include <iomanip>     // Provides setw
#include <cstdlib>     // Provides EXIT_SUCCESS, size_t
#include "bag.h"       // Provides a Bag template class
#include "organism.h"  // Provides Carnivore, Herbivore, Plant classes

using namespace std;

const size_t MANY_FERNS = 20000;     // Number of ferns on the island
const double FERN_START_SIZE = 15;   // Initial size of each fern, in ounces
const double FERN_MAX_SIZE = 30000;  // Maximum fern size, in ounces
const double FERN_RATE = 2.5;        // Growth rate of ferns, in ounces/week
const size_t GOOSE_START_POP = 3000; // Initial number of geese on the island
const double GOOSE_START_SIZE = 10;  // Initial size of a goose, in ounces
const double GOOSE_MAX_SIZE = 150;   // Maximum goose size, in ounces
const double GOOSE_RATE = 2.9;       // Growth rate of geese, in ounces/week
const double GOOSE_FRACTION = 0.279; // A goose must eat this fraction times its
                                     // size during one week, or it will die.
const double GOOSE_NIBBLES = 30;     // Average number of plants nibbled by
                                     // a goose over one week
const double GOOSE_FERTILITY = 0.25; // At the end of each week in spring,
                                     // some geese have babies.
                                     // The total number of new goslings born
                                     // each week is the current geese
                                     // population times the fertility
                                     // rounded down to an integer).
const size_t FOX_START_POP = 100;    // Initial number of foxes on the island
const double FOX_START_SIZE = 10;    // Initial size of a fox, in ounces
const double FOX_MAX_SIZE = 150;     // Maximum fox size, in ounces
const double FOX_RATE = 2.9;         // Growth rate of foxes, in ounces/week
const double FOX_FRACTION = 0.5;     // A fox must eat this fraction * its
                                     // size during one week, or it will die.
const double FOX_FERTILITY = 0.25;   // At the end of each week in spring,
                                     // some foxes have babies.
                                     // The total number of new kits born
                                     // each week is the current fox
                                     // population times the fertility
                                     // (rounded down to an integer).
const double MEETING_CHANCE = 0.009; // This fraction is the probability that
                                     // a randomly selected fox will meet a
                                     // randomly selected goose in one week.
const double CATCH_CHANCE = 0.5;     // If a fox chases a goose, this is the2
                                     // probability that the goose is caught.
                                     
void island_week(Bag<Organism*>& foxes, Bag<Organism*>& geese, Bag<Organism*>& ferns, bool spring);
// Precondition: ferns.size( ) > 0. Also: Each of the Organisms in the foxes
// Bag is actually a Carnivore; in the geese Bag they are herbivores, and in
// the ferns Bag they are Plants.
// Postcondition: One week on the island has been simulated. During this week,
// each goose has nibbled on some ferns (with GOOSE_NIBBLES being the average
// number of nibbles), some foxes have chased some geese, some animals may have
// died because they didn't get enough to eat. Any animals that died have been
// removed from their bag. If it is spring, then some new animals have been
// born and added to the bags.

void simulate_all(Bag<Organism*>& collection, double max, double rate);
// Postcondition: The simulate_week member function has been activated for each
// Organism in the Bag. If the Organism is then dead, it's removed from Bag.
// Also, if the size of an Organism exceeds max, then its growth rate is
// set to zero; otherwise its growth rate is set to rate.

double total_mass(Bag<Organism*>& collection);
// Postcondition: The return value is the total mass of all the organisms in
// the collection.

void increase_to_next_week(int& day, int& month, int& year);
// Precondition: day, month, and year are a valid date with year > 2000.
// Postcondition: The day, month, and year have been moved forward one week.

void print_date(int day, int month, int year);
// Postcondition: The given date has been printed as a string to cout.

int main( ) {
  Bag<Organism*> foxes(FOX_START_POP);   // A bag of pointers to the foxes
  Bag<Organism*> geese(GOOSE_START_POP); // The foxes chase these geese
  Bag<Organism*> ferns(MANY_FERNS);      // And the geese eat these ferns
  size_t i;                              // Loop control variable
  int day = 1, month = 1, year = 2005;   // The current date on the island
  unsigned int many_weeks;               // Number of weeks to simulate
                                         // Initialize the bags of foxes, geese, and ferns.
                                         // Note that the animals start full grown.
  for (i = 0; i < FOX_START_POP; i++)
    foxes.insert(new Carnivore(FOX_MAX_SIZE, 0, FOX_MAX_SIZE*FOX_FRACTION));
  for (i = 0; i < GOOSE_START_POP; i++)
    geese.insert(new Herbivore(GOOSE_MAX_SIZE, 0, GOOSE_MAX_SIZE*GOOSE_FRACTION));
  for (i = 0; i < MANY_FERNS; i++)
    ferns.insert(new Plant(FERN_START_SIZE, FERN_RATE));
  
  // Get number of weeks, and format the output:
  
  cout << "How many weeks shall I simulate? ";
  cin >> many_weeks;
  cout.setf(ios::fixed);
  cout << endl;
  cout << "   Week         Number    Number    Plant Mass" << endl;
  cout << "               of Foxes  of Geese    (ounces)" << endl << endl;
  
  // Simulate the weeks:
  
  for (i = 1; i <= many_weeks; i++) {
    island_week(foxes, geese, ferns, (month >= 3) && (month <= 6));
    print_date(day, month, year);
    cout << setw(11) << foxes.size( );
    cout << setw(10) << geese.size( );
    cout << setw(13) << setprecision(0) << total_mass(ferns);
    cout << endl;
    increase_to_next_week(day, month, year); }
  return EXIT_SUCCESS;
}

void island_week(Bag<Organism*>& foxes, Bag<Organism*>& geese, Bag<Organism*>& ferns, bool spring) {
  size_t i;
  size_t many_iterations;
  Carnivore* fox_ptr;
  Herbivore* goose_ptr;
  Plant* fern_ptr;
  
  // Have randomly selected geese nibble on randomly selected plants:
  
  many_iterations = (size_t)(GOOSE_NIBBLES * geese.size( ));
  for (i = 0; i < many_iterations; i++) {
    goose_ptr = (Herbivore *) geese.grab( );
    fern_ptr = (Plant *) ferns.grab( );
    goose_ptr->nibble(*fern_ptr); }
    
  // Have the foxes chase the geese. The chance of a random fox chasing
  // a random goose is given by the constant MEETING_CHANCE, so the
  // total number of chases is given by many_iterations:
  
  many_iterations = (size_t)(MEETING_CHANCE * geese.size( ) * foxes.size( ));
  for (i = 0; i < many_iterations; i++) {
    fox_ptr = (Carnivore *) foxes.grab( );
    goose_ptr = (Herbivore *) geese.grab( );
    if (goose_ptr->is_alive( ) && fox_ptr->still_need( ) > 0)
      fox_ptr->chase(*goose_ptr, CATCH_CHANCE); }
    
  // Simulate the week for the various bags of organisms:
  
  simulate_all(foxes, FOX_MAX_SIZE, FOX_RATE);
  simulate_all(geese, GOOSE_MAX_SIZE, GOOSE_RATE);
  simulate_all(ferns, FERN_MAX_SIZE, FERN_RATE);
  
  // Create some new animals, according to the birth FERTILITY constants:
  
  if (spring) {
    many_iterations = int(GOOSE_FERTILITY * geese.size( ));
    geese.resize(geese.size( ) + many_iterations);
    for (i = 0; i < many_iterations; i++)
      geese.insert(
        new Herbivore
        (GOOSE_START_SIZE, GOOSE_FERTILITY, GOOSE_START_SIZE*GOOSE_FRACTION)
      );
    many_iterations = int(FOX_FERTILITY * foxes.size( ));
    foxes.resize(foxes.size( ) + many_iterations);
    for (i = 0; i < many_iterations; i++)
      foxes.insert(
        new Carnivore
        (FOX_START_SIZE, FOX_FERTILITY, FOX_START_SIZE * FOX_FRACTION)
      ); }
}

double total_mass(Bag<Organism*>& collection) {
  double answer;
  answer = 0;
  for (collection.start( ); collection.is_item( ); collection.advance( ))
    answer += collection.current( )->get_size( );
  return answer;
}

void simulate_all(Bag<Organism*>& collection, double max, double rate) {
  collection.start( );
  while (collection.is_item( )) {
    collection.current( )->simulate_week( );
    if (collection.current( )->is_alive( )) {
      if (collection.current( )->get_size( ) >= max)
        collection.current( )->assign_rate(0);
      else
        collection.current( )->assign_rate(rate);
      collection.advance( ); }
    else {
      delete collection.current( );
      collection.remove_current( ); } }
}

void increase_to_next_week(int& day, int& month, int& year) {
  int limit;
  switch (month) {
    case 1:
    case 3:
    case 5:
    case 7:
    case 8:
    case 10:
    case 12:
      limit = 31;
      break;
    case 2:
      if ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)))
        limit = 29; // Leap year
      else
        limit = 28; // Not a leap year
      break;
    case 4:
    case 6:
    case 9:
    case 11:
      limit = 30;
      break; }
  day += 7;
  if (day > limit) {
    day -= limit;
    month++; }
  if (month > 12) {
    month = 1;
    year++; }
}

void print_date(int day, int month, int year) {
  switch (month) {
    case 1:
      cout << "Jan ";
      break;
    case 2:
      cout << "Feb ";
      break;
    case 3:
      cout << "Mar ";
      break;
    case 4:
      cout << "Apr ";
      break;
    case 5:
      cout << "May ";
      break;
    case 6:
      cout << "Jun ";
      break;
    case 7:
      cout << "Jul ";
      break;
    case 8:
      cout << "Aug ";
      break;
    case 9:
      cout << "Sep ";
      break;
    case 10:
      cout << "Oct ";
      break;
    case 11:
      cout << "Nov ";
      break;
    case 12:
      cout << "Dec ";
      break; }
  cout << setw(2) << day << ", " << year;
}