class PlayedGame {

  // State Machine
  
  playStatus {
    Ready {
      entry / { doSetup(); }
      play -> Moving;
    }
    Moving {
      pause -> Paused;
      move [hitPaddle()] / { doHitPaddleOrWall(); } -> Moving;
      move [isOutOfBoundsAndLastLife()] / { doOutOfBounds(); } -> GameOver;
      move [isOutOfBounds()] / { doOutOfBounds(); } -> Paused;
      move [hitLastBlockAndLastLevel()] / { doHitBlock(); } -> GameOver;
      move [hitLastBlock()] / { doHitBlockNextLevel(); } -> Ready;
      move [hitBlock()] / { doHitBlock(); } -> Moving;
      move [hitWall()] / { doHitPaddleOrWall(); } -> Moving;
      move / { doHitNothingAndNotOutOfBounds(); } -> Moving;
    }
    Paused {
      play -> Moving;
    }
    GameOver {
      entry / { doGameOver(); }
    }
  }
  
  // Guards
  
  private boolean hitPaddle() {
    // George
    BouncePoint bp = calculateBouncePointPaddle();
    
    setBounce(bp);
    
    if(bp != null){
    	return true;
    }
    return false;
  }

  private boolean isOutOfBoundsAndLastLife() {
     int lives = getLives(); 
   boolean outOfBounds = isOutOfBounds();
   if(outOfBounds == true){
      if(lives == 1){
        return true;
      }
  }
  else return false;
  }
  
  private boolean isOutOfBounds() {
   double xI = getCurrentBallX();
     double yI = getCurrentBallY();
     double dx = getBallDirectionX();
     double dy = getBallDirectionY();

     double xF = xI+dx;
     double yF = yI+dy;
     
    Rectangle rect = new Rectangle(0,Game.PLAY_AREA_SIDE, Game.PLAY_AREA_SIDE, (Ball.BALL_DIAMETER / 2));
    
    boolean outOfBounds = rect.intersectsLine(xI, yI, xF, yF);
    return outOfBounds;
    
  }
  
  private boolean hitLastBlockAndLastLevel() {
	// Yannick
	game = getGame();

	int nrLevels = game.numberOfLevels();
	
	setBounce(null);

	if(nrLevels == currentLevel){
		int nrBlocks = numberOfBlocks();

		if(nrBlocks == 1){
			PlayedBlockAssignment block = getBlock(0);
			BouncePoint bp = calculateBouncePointBlock(block);
			if(bp == null){
				return false;
			}
			setBounce(bp);
			return true;
		}
	}
    return false;
  }
  
	private boolean hitLastBlock(){
    // Yannick
    	int nrBlocks = numberOfBlocks();
    
    	setBounce(null);
    
    	if(nrBlocks == 1){
    
    		PlayedBlockAssignment block = getBlock(0);
    
    		BouncePoint bp = calculateBouncePointBlock(block);
    
    		if(bp == null){
    			return false;
    		}
    	
    		setBounce(bp);
			return true;
		}
    
		return false;
	}
  
  private boolean hitBlock() {
    // Yannick
    
    int nrBlocks = numberOfBlocks();
    
    setBounce(null);
    
    for(int i = 0 ; i < numberOfBlocks() - 1 ; i++){
    
    	 PlayedBlockAssignment block = getBlock(i);
    	 
    	 BouncePoint bp = calculateBouncePointBlock(block);
    	 
    	 bounce = getBounce();
    	 
    	 if(bp != null && bounce != null){
    	 	Boolean closer = isCloser(bp, bounce);
    	 
    	 	if(closer){
    	 	
    	 		setBounce(bp);
    	 	
    		}
    		return true;
    	}
    }
    return false;
  }
  
  private boolean hitWall() {
    // George
    BouncePoint bp = calculateBouncePointWall();
    
    setBounce(bp);
    
    if(bp != null){
    	return true;
    }
    return false;
  }
  
  // Actions
  
  private void doSetup() {
     				resetCurrentBallX();
                              resetCurrentBallY();
                              resetCurrentBallX();
                              resetCurrentBallY();
                              resetCurrentPaddleX();
                              getGame();
                              Level assignment = game.getLevel(currentLevel - 1);
                              List<BlockAssignment> assignments = assignment.getBlockAssignments();
                              for (BlockAssignment a : assignments) {
                                PlayedBlockAssignment pblock = new PlayedBlockAssignment(Game.WALL_PADDING + (Block.SIZE +
                                        Game.COLUMNS_PADDING) * (a.getGridHorizontalPosition() - 1), Game.WALL_PADDING + (Block.SIZE + Game.ROW_PADDING) *
                                        (a.getGridVerticalPosition() - 1), a.getBlock(), this);
                              }

                              while (numberOfBlocks() < game.getNrBlocksPerLevel()) {
                                int x = ThreadLocalRandom.current().nextInt(1, 15);
                                int y = ThreadLocalRandom.current().nextInt(1, 15);



                                for (BlockAssignment ablockAssignment : assignments) {
                                  if (ablockAssignment.getGridHorizontalPosition() == x && ablockAssignment.getGridVerticalPosition() == y) {
                                    x=x++;
                                    y=y++;
                                  }
                                  if (ablockAssignment.getGridHorizontalPosition() != x && ablockAssignment.getGridVerticalPosition() != y){
                                    PlayedBlockAssignment pblock = new PlayedBlockAssignment(x, y, game.getRandomBlock(), this);
                                  }
                                }
                              }
  }
  
  private void doHitPaddleOrWall() {
    // George
    bounceBall();
  }

  private void doOutOfBounds() {
    
     setLives(lives-1);
     resetCurrentBallX();
     resetCurrentBallY();
     resetBallDirectionX();
     resetBallDirectionY();
     resetCurrentPaddleX();
  }

  private void doHitBlock() {
    // Yannick
    int score = getScore();
    bounce = getBounce();
    
    PlayedBlockAssignment pblock = bounce.getHitBlock();
    
    Block block = pblock.getBlock();
    
    int points = block.getPoints();
    
    setScore(score + points);
    
    pblock.delete();
    
    bounceBall();
    
  }

  private void doHitBlockNextLevel() {
    // Yannick
    doHitBlock();
    
    int level = getCurrentLevel();
    
    setCurrentLevel(level + 1);
    
    setCurrentPaddleLength(getGame().getPaddle().getMaxPaddleLength() -
    	(getGame().getPaddle().getMaxPaddleLength() - getGame().getPaddle().getMinPaddleLength()) / 
    	(getGame().numberOfLevels() - 1) * (getCurrentLevel() - 1));
    	
    setWaitTime(INITIAL_WAIT_TIME * Math.pow(getGame().getBall().getBallSpeedIncreaseFactor(), (getCurrentLevel() - 1)));
    
  }

  private void doHitNothingAndNotOutOfBounds() {
    double x = getCurrentBallX();
    double y = getCurrentBallY();
    double dx = getBallDirectionX();
    double dy = getBallDirectionY();
    
    setCurrentBallX(x+dx);
    setCurrentBallY(y+dy);
    
  }

 private void doGameOver(){
     
     Block223 block223 = getBlock223();
     Player p = block223.getPlayer();
     if(p!=null){
       PlayedGame pg = getGame();
       HallOfFameEntry hof = new HallOfFameEntry(score, playername, p, pg, block223);
       Game game;
       game.setMostRecentEntry(hof);
     }

     pg.delete();
    
  }

}
