class Doctor {
  private PVector position;
  private PVector direction;

  private int size = 10;

  public Doctor() {
    position = new PVector(random(size, width - size), random(size, height - size));
    direction = new PVector(random(-1, 1), random(-1, 1));
  }

  public void update() {

    if (position.x <= size || position.x >= width - 10) {
      direction.x = -1 * direction.x;
    }
    if (position.y <= size || position.y >= height - 10) {
      direction.y = -1 * direction.y;
    }

    position.x += direction.x;
    position.y += direction.y;
  }

  public void draw() {
    stroke(0, 0, 0);
    fill(255, 255, 255);
    ellipse(position.x, position.y, size, size);
    noStroke();
    fill(255, 0, 0);
    rect(position.x - 1.5, position.y - 4, 3, 8);
    rect(position.x - 4, position.y - 1.5, 8, 3);
  }

  public boolean checkCollision(int xPos1, int yPos1, int xPos2, int yPos2) {
    if (position.x > xPos1 && position.x < xPos2 &&
      position.y > yPos1 && position.y < yPos2) {
      return true;
    }
    return false;
  }

  public void interact(ArrayList<Patient> population) {
    int xBounds = size / 2;
    int yBounds = size / 2;
    for (Patient neighbor : population) {
      if (neighbor.checkCollision((int)position.x - xBounds, (int)position.y - yBounds, 
        (int)position.x + xBounds, (int)position.y + yBounds)) {
        neighbor.vaccinate();
      }
    }
  }
}ArrayList<Patient> population = new ArrayList<Patient>();
ArrayList<Doctor> medicalCommunity = new ArrayList<Doctor>();
void setup() {
  size(800, 600);
  boolean isVaccinated = false;
  for (int i = 0; i < Settings.population; i++) {
    isVaccinated = Settings.vaccinationPercentage >= random(0, 100);
    population.add(new Patient(isVaccinated));
  }

  for (int i = 0; i < Settings.doctorPopulation; i++) {
    medicalCommunity.add(new Doctor());
  }
}

void draw() {

  for (Patient patient : population) {
    patient.update();
    patient.interact(population);
  }

  for (Doctor doctor : medicalCommunity) {
    doctor.update();
    doctor.interact(population);
  }
  background(255, 255, 255);
  noStroke();
  for (Patient patient : population) {
    patient.draw();
  }

  for (Doctor doctor : medicalCommunity) {
    doctor.draw();
  }

  int[] counts = {0, 0, 0, 0, 0};
  for (Patient patient : population) {
    counts[patient.getState()]++;
  }

  String message = "Uninfected: " + nf(counts[Patient.UNINFECTED], 4)+ "   Vaccinated: "+nf(counts[Patient.VACCINATED], 4)+"   Sick: "+nf(counts[Patient.SICK], 4)+"   Immune: "+nf(counts[Patient.IMMUNE], 4)+"   Dead: "+nf(counts[Patient.DEAD], 4);

  fill(0, 0, 0);
  textSize(18);
  text(message, 60, height - 5);


  fill(0, 0, 255, 180);
  ellipse(55, height - 12, 10, 10);
  fill(255, 127, 0, 180);
  ellipse(220, height - 12, 10, 10);
  fill(0, 255, 0, 180);
  ellipse(390, height - 12, 10, 10);
  fill(255, 127, 127, 180);
  ellipse(496, height - 12, 10, 10);
  fill(127, 127, 127, 180);
  ellipse(643, height - 12, 10, 10);
}

void mouseClicked() {
  fill(255, 0, 0);

  int clickSize = Settings.infectionClickSize;
  ellipse(mouseX, mouseY, clickSize * 2, clickSize * 2);
  for (Patient patient : population) {
    if (patient.checkCollision(mouseX - clickSize, mouseY - clickSize, mouseX + clickSize, mouseY + clickSize)) {
      patient.infect();
    }
  }
}class Patient {
  private PVector position;
  private PVector direction;

  public static final int UNINFECTED = 0;
  public static final int VACCINATED = 1;
  public static final int SICK = 2;
  public static final int IMMUNE = 3;
  public static final int DEAD = 4;

  private int size = 10;

  private int infectedTimer = 0;
  private int immuneTimer = 0;
  private boolean hasDeath = false;

  private int state = UNINFECTED;

  public Patient(boolean isVaccinated) {
    position = new PVector(random(size, width - size), random(size, height - size - 20));
    direction = new PVector(random(-1, 1), random(-1, 1));
    if (isVaccinated) {
      state = VACCINATED;
    }
    hasDeath = Settings.deathProbability >= random(0, 100);
  }
  
  public int getState() {
    return state;
  }

  public void update() {
    if (state != DEAD) {
      if (position.x <= size || position.x >= width - size) {
        direction.x = -1 * direction.x;
      }
      if (position.y <= size || position.y >= height - size - 20) {
        direction.y = -1 * direction.y;
      }

      position.x += direction.x;
      position.y += direction.y;
    }
    if (state == SICK) {
      infectedTimer--;
      if (infectedTimer <= 0) {
        state = UNINFECTED;
        if (Settings.canBecomeImmune) {
          this.immunize();
        }
        if (hasDeath) {
          state = DEAD;
        }
      }
    }
    if (state == IMMUNE && !Settings.immuneForLife) {
      immuneTimer--;
      if (immuneTimer <= 0) {
        state = UNINFECTED;
      }
    }
    
    if (state == VACCINATED && !Settings.vaccinatedForLife) {
      immuneTimer--;
      if (immuneTimer <= 0) {
        state = UNINFECTED;
      }
    }
  }

  public void draw() {
    switch(state) {
    case UNINFECTED:
      fill(0, 0, 255, 180); 
      break;
    case VACCINATED:
      fill(255, 127, 0, 180);
      break;
    case SICK:
      fill(0, 255, 0, 180);
      break;
    case IMMUNE:
      fill(255, 127, 127, 180);
      break;
    case DEAD:
      fill(127, 127, 127, 180);
      break;
    }

    ellipse(position.x, position.y, size, size);
  }

  public boolean checkCollision(int xPos1, int yPos1, int xPos2, int yPos2) {
    if (position.x > xPos1 && position.x < xPos2 &&
      position.y > yPos1 && position.y < yPos2) {
      return true;
    }
    return false;
  }

  public void infect() {
    if (state == UNINFECTED) {
      state = SICK;
      infectedTimer = Settings.sickDuration;
    }
  }


  public void immunize() {
    if (state == UNINFECTED || state == IMMUNE) {
      state = IMMUNE;
      immuneTimer = max(Settings.immuneDuration, immuneTimer);
    }
  }
  
  public void vaccinate() {
    if (state == UNINFECTED || state == IMMUNE) {
      state = VACCINATED;
      immuneTimer = max(Settings.vaccineDuration, immuneTimer);
    }
  }

  public void interact(ArrayList<Patient> population) {
    if ((Settings.canDeadInfect && state == DEAD) || state == SICK) {
      int xBounds = size / 2;
      int yBounds = size / 2;
      for (Patient neighbor : population) {
        if (neighbor.checkCollision((int)position.x - xBounds, (int)position.y - yBounds, 
          (int)position.x + xBounds, (int)position.y + yBounds)) {
          neighbor.infect();
        }
      }
    }
  }
}abstract static class Settings {
  public static int population = 1000;
  public static float vaccinationPercentage = 0.0;
  public static float deathProbability = 5.0;
  public static int sickDuration = 200;
  public static int infectionClickSize = 10;
  public static boolean canBecomeImmune = true;
  public static boolean immuneForLife = false;
  public static boolean vaccinatedForLife = false;
  public static int immuneDuration = 3000;
  public static int vaccineDuration = 3000;
  public static boolean canDeadInfect = false;
  public static int doctorPopulation = 0;
}