import java.util.Random;

/** Model of a mine sweeper game */
class Minesweeper {
  public final static int RADIUS = 1;
  
  /* The number of requested bombs. */
  private int requestedBombs;
  
  /* The number of actual bombs in the grid. */
  private int numBombs;
  
  /* The number of cells that have been explored. */
  private int numExplored;
  
  /* The dimensions of the grid. */
  private int height;
  private int width;
  
  /* The number of cells that are marked as bombs */
  private int numMarked; 
  
  /* The number of hints that have been supplied to the player. */
  private int numHints;
  
  /* Whether the player has stepped on a bomb. */
  private boolean gameOver;
  
  /* The game board itself */
  private Cell board[][];
  
  /* A generator for random numbers (to create the board and provide hints). */
  private Random gen = new Random (12345);
  
  /* The gui for the game */
  private Viewer viewer;

  /** Constructor: create a new game.
    * @param requestedBombs Desired number of bombs, may not be exactly the number produced.
    * @param height Number of rows
    * @param width Number of columns */
  public Minesweeper(int requestedBombs, int height, int width) {
    this.requestedBombs = requestedBombs;
    this.height = height;
    this.width = width;
    this.viewer = new Viewer(this);
    reset();
  }
  
  /***** General information about the game *******/
  
  /** Access number of bombs hidden on this board. */
  public int getNumBombs () { return numBombs; }
  
  /** Access board dimensions */
  public int getHeight () { return height; }
  /** Access board dimensions */
  public int getWidth () { return width; }

  /** Has the board been solved? */
  public boolean isSolved() { return  (numExplored + numBombs) == (height * width); } 

  /** Has a bomb been triggered on this board? */ 
  public boolean isGameOver() { return gameOver; }
  
  /** How many squares are marked as bombs? */
  public int getNumMarked() { return numMarked; }
  
   /** How many hints have been used to solve this board? */
  public int getNumHints() { return numHints; }
  
  /*********** General information about cells **********/
  
   /** Has this cell been marked as a bomb? */
  public boolean isMarked(int i, int j) {
    return board[i][j].isMarked;
  }

  /** Has this cell been explored? */
  public boolean isExplored(int i, int j) {
    return board[i][j].isExplored;
  }

  /** Return the number of neighbors that are bombs. 
    * (Only correct if the game is over, or the cell has already been explored. */
  public int getNeighboringBombs(int i, int j) {
    if (isGameOver() || isExplored(i,j)) 
      return board[i][j].neighbors;
    else 
      return 0;
  }
  
  /** Return whether a cell is a bomb or not. (Only correct if the game is over.) */
  public boolean isBomb(int i, int j) {
    if (isGameOver())
      return board[i][j].isBomb;
    else 
      return false;
  }
  
  /************* Operations for playing the game *******************/
  /* All of these operations end by communicating to the viewer the changed 
   * state of the game. */
  
  /** End this current game */
  public void gameOver() {
    gameOver = true;
    viewer.displayGameOver();
  }
  
  /** Start the game with a new random board. */
  public void reset() {
    gameOver = false;
    numExplored = 0;
    numHints = 0;
    numMarked = 0;
    board = new Cell[height][width];
    // fill the board with bombs
    int currBombs = 0;
    for (int i=0; i<height; i++) {      
       for (int j=0; j<width; j++) {
          board[i][j] = new Cell();
          if ((currBombs < requestedBombs) && (gen.nextFloat() < ((float)requestedBombs) / (height * width))) { 
             board[i][j].isBomb = true;
             currBombs++;
         }
       }
    }
    numBombs = currBombs;
    // Calculate the number of neighbors at each place.
    for (int i=0; i<height; i++) {      
      for (int j=0; j<width; j++) {
        board[i][j].neighbors = bombs(i,j);
      }
    }
    viewer.displayReset();
  }
  
  /** Helper method: Find the number of bombs adjacent to the given cell. */
  private int bombs(int i, int j) {
     int bombs = 0;
     for (int k = Math.max(i-RADIUS, 0); k <= Math.min(i+RADIUS,height-1); k++) {
         for (int l = Math.max(j-RADIUS,0); l <= Math.min(j+RADIUS,width-1); l++) {
           if (!(k==i && l==j)) { 
            if (board[k][l].isBomb) { 
               bombs++; 
               }
           }
         }
     } 
     return bombs;
   }
    
  /** Explore the board at position (i, j). Marks that position as explored. Also checks if that 
    * postion is a bomb. */
  public void explore(int i, int j) {
    if (!board[i][j].isExplored) {
      numExplored++;
      board[i][j].isExplored = true;
      if (board[i][j].isBomb) {
        System.out.println("BOOM!");
        gameOver();
      } else if (isSolved()) {
        gameOver();
      } else {
        viewer.displayExplored(board[i][j].neighbors,i,j);
      }
    } 
  }
  
   /** Explore a random cell, guaranteed not to be a bomb. Has no effect if the game 
   * is solved. */
  public void hint() {
     if ( !isSolved() && !isGameOver() ) {
       int i = gen.nextInt(height);
       int j = gen.nextInt(width);
       if (board[i][j].isExplored || board[i][j].isBomb || board[i][j].isMarked) 
         hint();
       else {
         numHints++;
         viewer.displayHint();
         explore (i,j); 
       }
     }
   }
  
  /** Mark/Unmark a cell as a potential bomb */
  public void mark(int i, int j) {
    if (board[i][j].isMarked) {
      board[i][j].isMarked = false;
      numMarked--;
      viewer.displayUnMarked(i,j);
    } else {
      board[i][j].isMarked = true;
      numMarked++;
      viewer.displayMarked(i,j);
    }
  }

 
 /** Display the board. If we are still exploring, use * for 
  *  marked spaces, number for explored spaces, and . for 
  *  all other spaces.
  *  
  *  If a bomb was triggered or the game is solved, show the 
  *  whole board, including all of the bombs (@) and the 
  *  number of nearby bombs at each 
  *  location.
  *
  */
  public void printBoard() {
    if (!gameOver ) {
      for (int i=0; i<height; i++) {
        for (int j=0; j<width; j++) {
          if (board[i][j].isMarked) {
            System.out.print ("*");
          } else if (board[i][j].isExplored && board[i][j].isBomb) {
            // Shouldn't happen
            System.out.print("@");
          } else if (board[i][j].isExplored) {
            System.out.print(board[i][j].neighbors);
          } else {
            // unexplored squares
            System.out.print(".");
          }
        }
        System.out.print("\n");
      }
   } else {
       for (int i=0; i<height; i++) {
           for (int j=0; j<width; j++) {
             if (board[i][j].isBomb) {
               System.out.print("@");
             } else {
               int b = bombs(i,j);
               System.out.print(b);
             }
           }
           System.out.print("\n");
         }
      }
  }

}
