/**********************************
 * Assignment 5: Pacman
 * Date: 5/5/2006
 *
 * Jenna Holloway: jh382, 563324
 * Benji Shultz: bas67, 1786511
 **********************************/
package game;


import java.util.*;
import game.actor.Actor;
import game.actor.Poo;
import game.actor.Ghost;
import game.actor.Pacman;
import game.actor.Token;
import game.map.Cell;
import game.map.PacmanMap;
import game.player.AIPlayer;
import game.player.LocalPlayer;
import game.player.Player;
import game.player.Pooper;
import gui.GUI;

import java.awt.Graphics;
import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

/**
 * This is the 'Central' class that holds all the data and state for the game.
 * It holds on to the local and remote player, the game phase, the map, and the
 * various resource tables.
 */
public class Game implements Constants {
 /**
  * TimerTake that is fired when all the ghosts should be put into Active
  * mode
  * 
  * @author Alec Berntson
  * 
  */
 class ActiveTimerTask extends TimerTask {
  public void run() {
   for (Ghost g : aiPlayer.getActorList()) {
    g.makeActive();
   }
  }
 };

 enum Mode {
  NORMAL, POOP, PSYCHADELIC, MARIO
 }

 class PooRecoverTask extends TimerTask {
   Ghost g;
   PooRecoverTask(Ghost g){
     super();
     this.g=g;
   }
   public void run(){
 g.makeRecovering();
   timers[g.timerIndex].schedule(new ActiveTimerTask(), 2000);
  }
 }

 /**
  * TimerTask that is fired when all the ghosts should be put into Recovering
  * mode. Also schedules the timer to make the ghosts active again
  * 
  * @author Alec Berntson
  * 
  */
 class RecoveringTimerTask extends TimerTask {
  public void run() {
   for (Ghost g : aiPlayer.getActorList()) {
    g.makeRecovering();
   }
   vulnerableGhostTimer.schedule(new ActiveTimerTask(), 3000);
  }
 }

 // the player that controls the ghosts
 public final AIPlayer aiPlayer;

 // the player who has control of the GUI
 public final LocalPlayer localPlayer;

 public final Pooper pooper;

 /**
  * This is the graph you have to write.
  */
 public PacmanMap map;

 // the gui
 public final GUI gui;

 public final gui.HighScoreWindow highScore;

 // which phase of the game was are in
 private Phase phase;

 private Set<Token> tokenList = new HashSet<Token>();

 private Set<Actor> actorList = new HashSet<Actor>();

 private Timer vulnerableGhostTimer;

 private Timer[] timers=new Timer[4];

 private int score = 0, numtokes = 0, poopometer = 0, pooAmmo=0, lives = 3;

 Mode mode;

 LinkedList<Poo> eaten = new LinkedList<Poo>();
 /**
  * Constructor for the game.
  * 
  * @param gui
  *            reference to the GUI
  */

 public Game(GUI gui) {
  this.gui = gui;
  this.highScore = gui.hsw;

  map = new PacmanMap(0);
  Actor.setGame(this);
  /*
   * add the token objects
   */
  mode=Mode.NORMAL;
  Cell[][] cells = map.getCellGrid();
  for (int y = 0; y < map.getTileHeight(); y++) {
   for (int x = 0; x < map.getTileWidth(); x++) {
    if (cells[x][y].isToken()) {
     char type = cells[x][y].type;
     Token t = new Token(new Point2D.Double(x * CELL, y * CELL),
       type == '*');
     tokenList.add(t);
    }
   }
  }
  numtokes = tokenList.size();
  // create the players
  localPlayer = new LocalPlayer(this);
  pooper = new Pooper(this);
  aiPlayer = new AIPlayer(this, map);
  actorList.add(localPlayer.getPacman());
  actorList.addAll(aiPlayer.getActorList());
  phase = Phase.SPLASH_SCREEN;
 }
 /**
  * check if pacman has hit any of the ghosts or tokens. Take the neccessary
  * action depending on what was hit
  * 
  */
 public void checkCollisions() {
  Pacman p = localPlayer.getPacman();
  Iterable<Poo> poos = pooper.getActorList();
  Iterable<Ghost> ghosts = aiPlayer.getActorList();

  for (Ghost g : ghosts) {
   if (p.collidesWith(g)) {
    // we hit a ghost - die if the ghost is active
    if (g.isActive()) {
     score -= g.getScore() / 10;
     if(!isMario()){
      gui.die.playAndWait(5);}
     else{
      gui.loseLife.playAndWait(5);}
     restart();
    } else { // else send it to the corral
     gui.eatingGhosts.play();
     score += g.getScore();
     g.sendToCorral();
    }
   }
   if (isPoo()) {

    for (Poo poop : poos) {
     if (g.collidesWith(poop)&&g.isActive()) {
      gui.scream[(int) Math.floor(Math.random() * 2)].play();
      score += g.getScore();
      /*
       * g.makeVulnerable(); vulnerableGhostTimer = new
       * Timer(true); vulnerableGhostTimer.schedule(new
       * RecoveringTimerTask(), 7000);
       */
      g.makePoisoned();
      timers[g.timerIndex] = new Timer(true);
      timers[g.timerIndex].schedule(new PooRecoverTask(g), 3000);
      eaten.add(poop);
      actorList.remove(poop);
     }
    }
    for (Poo poop : eaten) {
     pooper.eatPoo(poop);
    }
    eaten.clear();
   }
  }
  
  
  // eat the tokens
  for (Token t : tokenList) {
   if (p.collidesWith(t)) {
    // is a super token-> make the ghosts vulnerable
    if (t.isSuperToken()) {
     score += 3;
     poopometer += 10;
     if(!isPoo()){
       for (Ghost g : ghosts) {
      g.makeVulnerable();
     }
     vulnerableGhostTimer = new Timer(true);
     vulnerableGhostTimer.schedule(new RecoveringTimerTask(),
       7000);}
    }
    if (t.eat()) {
     score += 1;
     numtokes--;
     poopometer++;
    }
    if (mode == Mode.POOP && poopometer > 10) {
     if(pooAmmo<5){
     pooAmmo++;
     poopometer=0;}
    }
    if (numtokes == 0) {
     restart();
    }

   }

  }
 }
/**
  * draw all the tokens to the screen
  * 
  * @param g
  */
 public void drawTokens(Graphics g) {
  for (Token t : tokenList) {
   t.draw(g);
  }
 }
 public void getHighScore() {
  highScore.setVisible(true);
  highScore.setScore(score);
 }

 /**
  * return the Map used by this game
  * @return
  */
 public PacmanMap getMap() {
  return map;
 }

 /**
  * @return the Phase that this game is in
  */
 public Phase getPhase() {
  return phase;
 }
public int getScore() {
  return score;
 }
 
 public boolean isMario(){
 return mode==Mode.MARIO;
}
 
 public boolean isPoo() {
  return mode == Mode.POOP;
 }
  
 
 /**
  * Check if we have won
  */
 public boolean isWinner() {
  if (numtokes == 0) {
   return true;
  }
  return false;
 }
 
 public void playGame() {
  phase = Phase.PLAY_GAME;
 }
 public int pooAmmo(){
  return pooAmmo;
 }

 public void pooBomb(){
 
 if(pooAmmo>0){
    Pacman p=localPlayer.getPacman();
 gui.fart[(int) Math.floor(Math.random() * 3)].play();
 synchronized(pooper){
     actorList.add(pooper.pacmanPoo(p));}
 pooAmmo--;
 }}

 public int poopometer() {
  return poopometer;
 }

 /**
  * Restarts the game back to the way it was at the start
  * 
  */
 private void restart() {
  poopometer = 0;
  if (numtokes == 0 || lives == 1) {
   if(numtokes==0&isMario())
    gui.win.play();
   getHighScore();
   for (Token t : tokenList)
    t.reset();
   numtokes = tokenList.size();
   score = 0;
   lives = 3;
   if (mode == Mode.POOP) {
    for (Poo poop : pooper.getActorList()) {
     poop.reset();
     actorList.remove(poop);
    }
    pooper.clearPoos();
    pooAmmo=0;
    }
   gui.stopAnimation();
   gui.reset();
   for (Actor a : actorList)
     a.reset();
   phase = Phase.SPLASH_SCREEN;

    
  } 
  else {
   lives--;
   gui.setLives(lives);
   gui.stopAnimation();
   for (Actor a : actorList)
    a.reset();
   gui.repaint();
   new Timer().schedule(new TimerTask() {
    public void run() {
    if(!isMario()){
     if(isPoo()) gui.flush.playAndWait(10);
     else  gui.intermission.playAndWait(10);}
     gui.restartAnimation();
    }
   }, 1000);
  }
 }

 
 
 //set up the different game modes
 public void setMario(){
   int i=0;
   for (Ghost g:aiPlayer.getActorList()){
    g.setKoopa(i);
    i++;
   }
   localPlayer.getPacman().setMario();
   map=new PacmanMap(2);
   mode=Mode.MARIO;
  }

 public void setNorm(){
  int i=0;
  for (Ghost g:aiPlayer.getActorList()){
   g.setNorm(i++);
  }
  localPlayer.getPacman().setNorm();
  map=new PacmanMap(0);
  mode=Mode.NORMAL;
 }


 public void setPoo() {
  setNorm();
  mode = Mode.POOP;
 }

 public void setPsych(){
  int i=0;
  for (Ghost g:aiPlayer.getActorList()){
   g.setPsych(i);
   i++;
  }
  localPlayer.getPacman().setPsych();
  map=new PacmanMap(1);
  mode=Mode.PSYCHADELIC;
 }

 /**
  * Update the location of all of players' units based on dt amount of time
  * passing.
  * 
  * @param player
  *            the player where we getUnits() from
  * @param dt
  *            the amount of times (in seconds) that we need to move the unit
  *            over. Actor speeds are given in pixels per second, so dt*speed
  *            is the movement potential
  */
 public void updateUnits(Player player, double dt) {
  player.move(dt);
 }

}
