/**
 * A simple, turn-based, text-based prototype for a zombie-shooting game.
 * <p>
 * The player stands at the end of a long hallway leading into darkness.
 * Armed with only a revolver and a pocket full of shells, he/she must
 * keep the unending waves of undead at bay.  The challenge is to manage
 * the revolver, since it contains only 6 shots and spins its cylinder
 * after each reload.
 * <p>
 * See {@link #play()} for more details of the game.
 *
 * @author Samuel "Henry" Colt
 */
public class ZombieHallway {

  public static final char PLAYER = 'P';
  public static final char ZOMBIE = 'Z';
  public static final char FLOOR = '_';

  private final double ZOMBIE_FREQ;  //likelihood of next tile containing a zombie
  private char[] hallway;
  private Revolver gun;

  /**
   * Starts a single game of Zombie Hallway with reasonable defaults.
   */
  public static void main(String[] args) {
    ZombieHallway game = new ZombieHallway(7, 0.5);
    game.play();
  }

  /**
   * Provides a new hallway with the given length of squares.  Zombies appear
   * each round with the given zFreq probability, which should be a % likelihood
   * between 0.0 and 1.0.
   * <p>
   * If hLength < 2, it will be treated as 2.  If zFreq < 0, it will be set to 0;
   * if zFreq > 1, it will be set to 1.
   */
  public ZombieHallway(int hLength, double zFreq) {
    //make sure hLength no negative or too short
    if (hLength < 2) {
      hLength = 2;
    }

    //make sure zFreq is in range: 0 to 1
    zFreq = (zFreq < 0) ? 0 : zFreq;
    zFreq = (zFreq > 1) ? 1 : zFreq;

    ZOMBIE_FREQ = zFreq;

    //build hallway
    hallway = new char[hLength];
    hallway[0] = PLAYER;
    for (int i = 1; i < hallway.length; i++) {
      hallway[i] = FLOOR;
    }

    //need a loaded gun
    gun = new Revolver();
    gun.open();
    gun.load(6);
    gun.close();
  }

  /**
   * Plays through a single game of ZombieHallway.
   * <p>
   * Each turn, the player may choose to fire her gun, or open and empty it.
   * Once open, she may reload the gun with 1 new round per turn.
   * If the gun ever fails to fire, they will automatically attempt to fire
   * once more in the same turn.
   * <p>
   * Once the player has moved, the hallway advances like a conveyer belt,
   * advancing any existing zombies and generating another at the end of the
   * hallway according to this game's zombie frequency setting.
   * <p>
   * In tribute to the bleakness of true zombie infestation,
   * the game continues until a zombie reaches the player.
   */
  public void play() {
    System.out.println("You stand at the end of a long dark hallway, " +
                       "loaded revolver in hand.");
    while (this.advanceHallway()) {
      System.out.println();
      this.printHallway();
      this.movePlayer();
    }
    System.out.println("\nTwitching zombie fingers tug at your clothing, and " +
                       "then clammy hands grasp at\nyour limbs. You gag on " +
                       "the choking stench of death as steely arms " +
                       "embrace you.\nAs your gun clatters uselessly to the " +
                       "floor and the zombie's teeth sink into\nyour flesh, " +
                       "you catch a glimpse over its rotting shoulder of more "+
                       "undead\nshuffling out of the darkness in vacant moaning " +
                       "eagerness.\n");
    System.out.println("Your end is bloody and gruesome.  Thanks for playing!");
  }

  /**
   * Advances the hallway towards the player, repopulating the far end
   * according to the zombie frequency.  Returns true if the player is still
   * standing after the update; false if a zombie reached the player.
   */
  protected boolean advanceHallway() {
    //advance everything one square to left, overwriting player
    for (int i = 1; i < hallway.length; i++) {
      hallway[i - 1] = hallway[i];
    }

    //fill in last square with zombie or floor
    if (this.ZOMBIE_FREQ >= 1) {
      hallway[hallway.length - 1] = ZOMBIE;
    }else {
      hallway[hallway.length - 1] = (Math.random() <= ZOMBIE_FREQ) ?
                                    ZOMBIE :
                                    FLOOR;
    }

    //check on player's status after shift
    if (hallway[0] == ZOMBIE) {
      //player eclipsed by zombie
      return false;
    }else {
      //replace shifted floor tile with player
      hallway[0] = PLAYER;
      return true;
    }
  }

  /**
   * Prompts the user for what they want to do and then does it.
   */
  protected void movePlayer() {

    java.util.Scanner keybd = new java.util.Scanner(System.in);

    while (true) {  //repeat until return after good input

      //print prompt
      if (this.gun.isOpen()) {
        System.out.println("Your gun is OPEN.  You can see " + gun.countShells() +
                           " loaded rounds.");
        System.out.print("L)oad another round.  C)lose gun.  What to do? ");
      }else {
        System.out.print("F)ire gun.  O)pen gun.  W)ait.  What to do? ");
      }

      //get choice (as first character of input)
      String input = keybd.nextLine();
      char choice = 'I';  //invalid
      if (input.length() > 0) {
        choice = Character.toUpperCase(input.charAt(0));
      }

      //determine validity/response
      switch(choice) {
        //Close
        case 'C':
          if (this.gun.isOpen()) {
            System.out.println("You close the gun with a spin of the cylinder.");
            this.gun.close();
            this.gun.spin();
            return;
          }else {
            System.out.println("Your gun is already closed.  Try again.");
            break;
          }

        //Open
        case 'O':
          if (this.gun.isOpen()) {
            System.out.println("Your gun is already open.  Try again.");
            break;
          }else {
            System.out.println("You flick your revolver open, emptying its contents.");
            this.gun.open();
            this.gun.empty();
            return;
          }

        //Fire
        case 'F':
          if (this.gun.isOpen()) {
            System.out.println("You can't fire with your gun open!  Try again.");
            break;
          }else {
            System.out.print("You squeeze the trigger... ");
            boolean fired = this.gun.fire();
            System.out.println((fired) ? "BOOM!" : "*click*");
            if (!fired) {
              //get a free shot if first attempt was a dud
              System.out.print("You frantically try again... ");
              fired = this.gun.fire();
              System.out.println((fired) ? "BOOM!" : "*click*");
            }
            if (fired) {
              //need to remove the slain zombie
              for (int i = 1; i < this.hallway.length; i++) {
                if (hallway[i] == ZOMBIE) {
                  hallway[i] = FLOOR;
                  System.out.println("Cold brains splatter the shadowy walls.");
                  break;
                }
              }
            }
            return;
          }

        //Load
        case 'L':
          if (this.gun.isOpen()) {
            try {
              this.gun.load(1);
              System.out.println("You drop another round into an open chamber.");
              return;
            }catch (IllegalArgumentException iae) {
              System.out.println("All the chambers are already full!  Try again.");
              break;
            }
          }else {
            System.out.println("You can't load a closed gun.  Try again.");
            break;
          }

        //Wait
        case 'W':
          System.out.println("You peer into the darkness, alert for the sounds " +
                             "of moaning or shuffling.");
          return;

        //Quit (undocumented in UI)
        case 'Q':
          System.out.println("\nYou turn and flee in terror...");
          System.exit(0);

        default:
          System.out.println("That is not a valid option.  Try again.");
          break;

      }//end switch
    }
  }

  /**
   * Prints the current state of the hallway to the screen as a single line
   * of text.
   */
  protected void printHallway() {
    for (int i = 0; i < hallway.length; i++) {
      System.out.print(hallway[i]);
    }
    System.out.println();
  }

}
