/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package MineSweeper;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;

/**
 *
 * @author Cecile
 */
class MineSweeper extends JPanel {

  int w, h, N;
  byte[] matrix;
  byte TYPE_HIDDEN = -1;
  byte TYPE_HIDDEN_M = -2;
  byte TYPE_BLOCKED = -3;
  byte TYPE_BLOCKED_M = -4;
  byte TYPE_EXPLODED = -5;
  int step, estep;
  private boolean end,  win,  paused;
  Random rand;
  Date startdate, enddate;
  Date pausestart, pauseend;
  int blockedcount, blockedcount_m, ret_count;

  void pause() {
    if (paused) {
      pauseend = new Date();
      startdate.setTime(startdate.getTime() + pauseend.getTime() - pausestart.getTime());
    }
    pausestart = new Date();
    paused = !paused;
    repaint();
  }

  void pause(boolean state) {
    if (state == paused) {
      return;
    } else {
      pause();
      repaint();
      return;
    }
  }

  private void endGame() {
    end = true;
    repaint();
  }

  private void saveTime() {
    enddate = new Date();
  }

  private int randX(int c) {
    if (c <= 0) {
      return 0;
    }
    int val = 1 << c;
    return rand.nextInt(val);
  }

  private void unHidde(Point p, int v) {
    int total = randX(v);
    Queue<Point> queue = new LinkedList<Point>();
    queue.add(p);
    int count = 0;
    if (matrix[p.y * w + p.x] == TYPE_HIDDEN_M || matrix[p.y * w + p.x] == TYPE_BLOCKED_M) {
      matrix[p.y * w + p.x] = TYPE_EXPLODED;
      saveTime();
      endGame();
      return;
    }
    while (!queue.isEmpty()) {
      Point center = queue.poll();
      for (int i = center.x - 1; i < center.x + 1; i++) {
        for (int j = center.y - 1; j < center.y + 1; j++) {
          if (i < 0 || j < 0 || i >= w || j >= h) {
            //continue;
          } else if (matrix[j * w + i] == TYPE_HIDDEN_M || matrix[j * w + i] == TYPE_BLOCKED_M || matrix[j * w + i] >= 0) {
            //continue;
          } else {
            checkCell(new Point(i, j));
            count++;
            if (count < total) {
              queue.add(new Point(i, j));
            }
          }
        }
      }
    }
  }

  public MineSweeper() {
    this(10, 6, 5);
  }

  private void mouse_init() {
    this.addMouseListener(new MouseAdapter() {

      @Override
      public void mouseClicked(MouseEvent e) {
        if (end || win || paused) {
          return;
        }
        Point p = findCell(e.getPoint());
        if (p == null) {
          return;
        }
        if (e.getButton() == MouseEvent.BUTTON3) {
          if (matrix[p.y * w + p.x] == TYPE_HIDDEN) {
            matrix[p.y * w + p.x] = TYPE_BLOCKED;
            blockedcount++;
          } else if (matrix[p.y * w + p.x] == TYPE_HIDDEN_M) {
            matrix[p.y * w + p.x] = TYPE_BLOCKED_M;
            blockedcount++;
            blockedcount_m++;
          } else if (matrix[p.y * w + p.x] == TYPE_BLOCKED) {
            matrix[p.y * w + p.x] = TYPE_HIDDEN;
            blockedcount--;
          } else if (matrix[p.y * w + p.x] == TYPE_BLOCKED_M) {
            matrix[p.y * w + p.x] = TYPE_HIDDEN_M;
            blockedcount--;
            blockedcount_m--;
          }
          repaint();
          return;
        }
        if (matrix[p.y * w + p.x] == TYPE_BLOCKED || matrix[p.y * w + p.x] == TYPE_BLOCKED_M) {
          return;
        }
        step++;
        if (step < estep) {
          unHidde(p, estep - step);
        } else {
          checkCell(p);
        }
      }
    });
  }
  Timer t;
  TimerTask tt;

  private void timer_init() {
    if (tt == null) {
      tt = new TimerTask() {

        @Override
        public void run() {
          if (!win && blockedcount == blockedcount_m && (ret_count + blockedcount_m) == w * h) {
            win = true;
            saveTime();
            saveWin();
          }
          repaint();
        }
      };
      t = new Timer();
      t.schedule(tt, 0, 500);
    }
  }

  private void init(int w_, int h_, int n) {
    w = w_;
    h = h_;
    N = n;
    step = 0;
    end = false;
    win = false;
    paused = false;
    rand = new Random();
    blockedcount = 0;
    blockedcount_m = 0;
    ret_count = 0;
    startdate = new Date();
    for (estep = 0; w * h > (1 << (estep + 1)); estep++) {
    }
    this.setBackground(Color.GRAY.darker());
    matrix = new byte[w * h];
    Arrays.fill(matrix, (byte) TYPE_HIDDEN);
    int count = 0;
    while (count < n) {
      int pos = rand.nextInt(w * h);
      if (matrix[pos] != TYPE_HIDDEN_M) {
        matrix[pos] = TYPE_HIDDEN_M;
        count++;
      }
    }
  }

  public MineSweeper(MineSweeperGameType msgt) {
    int w_ = msgt.getWidth();
    int h_ = msgt.getHeight();
    int n_ = msgt.getMines();
    init(w_, h_, n_);
    mouse_init();
    timer_init();
  }
  /*
   *  Constructor width the width, height the height and n the number of mines
   * */

  public MineSweeper(int width, int height, int n) {
    init(width, height, n);
    mouse_init();
    timer_init();
  }

  /**
   * Restart the game with the same parameters as previously
   */
  public void restart() {
    init(w, h, N);
    repaint();
  }

  /**
   * Restart the game with specified parameters : w_ the width, h_ the height
   * and n the number of mines
   */
  public void restart(int w_, int h_, int n) {
    init(w_, h_, n);
    repaint();
  }

  private void checkCell(Point p) {
    //System.out.println(p.y*w+p.x);
    if (p.x < 0 || p.x >= w || p.y < 0 || p.y >= h) {
      return;
    }
    if (matrix[p.y * w + p.x] >= 0) {
      return;
    }
    if (matrix[p.y * w + p.x] == TYPE_HIDDEN_M || matrix[p.y * w + p.x] == TYPE_BLOCKED_M) {
      matrix[p.y * w + p.x] = TYPE_EXPLODED;
      saveTime();
      endGame();
    } else {
      byte count = 0;
      for (int i = p.y - 1; i <= p.y + 1; i++) {
        for (int j = p.x - 1; j <= p.x + 1; j++) {
          if ((i >= 0 && i < h) && (j >= 0 && j < w)) {
            if (matrix[i * w + j] == TYPE_HIDDEN_M || matrix[i * w + j] == TYPE_BLOCKED_M) {
              count++;
            }
          }
        }
      }
      matrix[p.y * w + p.x] = count;
      ret_count++;
    }
    repaint();
  }
  static double TDX = 10.;
  static double TDY = 30.;
  static double SHIFT_X = 5.0;
  static double SHIFT_Y = 5.0;

  /**
   *
   * @param pt : position of the mouse when clicked
   * @return the position on the grid
   */
  private Point findCell(Point pt) {
    Point res = new Point();
    double wx = (getWidth() - TDX) / w;
    double wy = (getHeight() - TDY) / h;
    res.x = (int) ((pt.x - SHIFT_X) / wx);
    res.y = (int) ((pt.y - SHIFT_X) / wy);
    if (res.x < 0 || res.x >= w || res.y < 0 || res.y >= h) {
      return null;
    }
    return res;
  }

  /**
   * return the time
   * @return time display
   */
  private String makeTime() {
    if (!paused) {
      return "" + String.format("%5.1f", (new Date().getTime() - startdate.getTime()) / 1000.0);
    } else {
      return "" + String.format("%5.1f", (pausestart.getTime() - startdate.getTime()) / 1000.0);
    }

  }

  private String makeTimeDiff() {
    return "" + String.format("%5.1f", (enddate.getTime() - startdate.getTime()) / 1000.0);
  }
  String countstr="Count =";
  String timestr=" time = ";
  String retcountstr=" ret count = ";
  String endstr="End";
  String victorystr="Victory";
  @Override
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    if (win || end) {
      g2d.drawString(countstr + blockedcount + retcountstr + ret_count +
              timestr + makeTimeDiff(), 0.0f, (float) (getHeight() - TDX));
    } else {
      g2d.drawString(countstr + blockedcount + retcountstr + ret_count +
              timestr + makeTime(), 0.0f, (float) (getHeight() - TDX));
    }
    double wx = (getWidth() - TDX) / w;
    double wy = (getHeight() - TDY) / h;
    //g2d.fill(new Rectangle.Double(0.,50.,10.,50.));
    if (!paused) {
      for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
          double X = SHIFT_X + j * wx;
          double Y = SHIFT_Y + i * wy;

          if (matrix[i * w + j] == TYPE_HIDDEN || matrix[i * w + j] == TYPE_HIDDEN_M) {
            g2d.setColor(Color.BLACK);
            g2d.fill(new Rectangle.Double(X, Y, wx, wy));
          }
          if (matrix[i * w + j] == TYPE_BLOCKED || matrix[i * w + j] == TYPE_BLOCKED_M) {
            g2d.setColor(Color.GRAY);
            g2d.fill(new Rectangle.Double(X, Y, wx, wy));
          }
          if (matrix[i * w + j] == TYPE_EXPLODED) {
            g2d.setColor(Color.RED);
            g2d.fill(new Rectangle.Double(X, Y, wx, wy));
          }
          if (matrix[i * w + j] > 0) {
            g2d.setColor(Color.BLACK);
            g2d.drawString("" + matrix[i * w + j], (float) (X + wx / 2), (float) (Y + wy / 2));
          }
          g2d.setColor(Color.RED);
          g2d.draw(new Rectangle.Double(X, Y, wx, wy));
        }
      }
      if (end) {
        g2d.setColor(Color.RED.darker().darker());
        Font font = g2d.getFont();
        g2d.setFont(font.deriveFont(16f).deriveFont(Font.BOLD + Font.ITALIC));
        g2d.drawString(endstr, getWidth() / 2.f, getHeight() / 2.f);
        g2d.setFont(font);
      }
      if (win) {
        g2d.setColor(Color.RED.darker().darker());
        Font font = g2d.getFont();
        g2d.setFont(font.deriveFont(16f).deriveFont(Font.BOLD + Font.ITALIC));
        g2d.drawString(victorystr, getWidth() / 2.f, getHeight() / 2.f);
        g2d.setFont(font);
      }
    }
  }
  String user;
  ScoreRegistrar registrar;

  void setRegistrar(ScoreRegistrar registrar_, String user_) {
    registrar = registrar_;
    user = user_;
  }

  void saveWin() {
    int delay = (int) (enddate.getTime() - startdate.getTime());
    registrar.registerScore(user, delay);
  }
}

