namespace ca.mcgill.ecse223.block.model;
use Block223Play.ump;
use Block223Persistence.ump;
use Block223StateMachine.ump;

use Block223Persistence.ump;

class Block223 {
	depend ca.mcgill.ecse223.block.application.Block223Application;
   1 <@>- * User users;
   1 <@>- * UserRole roles;
   1 <@>- * Game games;
   
   public static Game findGame(String gameName) {
	  Game foundGame = null;
	  for(Game game: Block223Application.getBlock223().getGames()) {
		  if(game.getName().equals(gameName)) {
			  foundGame = game;
			  break;
		  }
	  }
	return foundGame;
  }   
}

class User {
   unique username;
   
   before constructor {
   if(aUsername == null || aUsername.equals(""))
     throw new RuntimeException("The username must be specified.");
   }
   
   1 -> 1..2 UserRole roles;
}

class UserRole {
   abstract;
   password;
   
   before constructor {
   if(aPassword == null || aPassword.equals(""))
     throw new RuntimeException("The player password needs to be specified.");
   }
}

class Admin {
   isA UserRole;
}

class Player {
   isA UserRole;
}

class Game {
   const Integer MIN_NR_LEVELS = 1;
   const Integer MAX_NR_LEVELS = 99; // this is somewhat redundant because the max multiplicity is enforced by Umple
   const Integer PLAY_AREA_SIDE = 390; // play area is now constant
   const Integer WALL_PADDING = 10;
   const Integer COLUMNS_PADDING = 5;
   const Integer ROW_PADDING = 2;
   unique name;
   Integer nrBlocksPerLevel;
   lazy Boolean isPublished;
   
    before constructor{
	if(aName.equals("") || aName == null){
		throw new RuntimeException("The name of a game must be specified.");
		}
	}
	
	before setName{
	if(aName == null || aName.equals("")){
	throw new RuntimeException("The name of a game must be specified.");
	}
	}

	before setNrBlocksPerLevel{
		if(aNrBlocksPerLevel<1){
			throw new RuntimeException("The number of blocks per level must be greater than zero.");
		}
		int max = 0;
		List<Level> l = this.getLevels();
		for (int a =0; a<l.size();a++) {
			if(max<l.get(a).getBlockAssignments().size())
				max = l.get(a).getBlockAssignments().size();
		}
		if(aNrBlocksPerLevel<max){
			throw new RuntimeException("The maximum number of blocks per level cannot be less than the number of existing blocks in a level.");
		}
	}
   
   Block findBlock(int id) {
   		List<Block> blocks = this.getBlocks();
   		
   		for(Block block : blocks) {
   			int blockId =  block.getId();
   			
   			if(id == blockId) {
   				return block;
   			}
   		}
   		return null;
   }
   
   * -- 1 Admin admin;
   1 <@>- * Block blocks;
   1 <@>- 1..99 Level levels;
   1 <@>- * BlockAssignment blockAssignments;
   1 <@>- 1 Ball ball;
   1 <@>- 1 Paddle paddle;
}

class Block {
   const Integer MIN_COLOR = 0;
   const Integer MAX_COLOR = 255;
   const Integer MIN_POINTS = 1;
   const Integer MAX_POINTS = 1000; 
   const Integer SIZE = 20;
   autounique id;
   Integer red;
   Integer green;
   Integer blue;
   Integer points;
   
   before constructor, setRed {
   	
   	if (aRed < MIN_COLOR || aRed > MAX_COLOR) {
   	throw new RuntimeException("Red must be between " +MIN_COLOR+ " and "+MAX_COLOR+".");
   	}
   
   }
   
   before constructor, setGreen {
   	
   	if (aGreen < MIN_COLOR || aGreen > MAX_COLOR) {
   	throw new RuntimeException("Green must be between " +MIN_COLOR+ " and "+MAX_COLOR+".");
   	}
   
   }
   
   before constructor, setBlue {
   
   	if (aBlue < MIN_COLOR || aBlue > MAX_COLOR) {
   		throw new RuntimeException("Blue must be between " +MIN_COLOR+ " and "+MAX_COLOR+".");
   	}
   	
   }
   
   before constructor, setPoints {
   
   	if (aPoints < MIN_POINTS || aPoints > MAX_POINTS) {
   		throw new RuntimeException("Points must be between "+MIN_POINTS+" and "+MAX_POINTS+".");
   	}
   }
}

// random attribute not needed anymore
// Each level is filled up with random blocks just before playing the level to reach the nrBlocksPerLevel defined in Game
class Level {
	BlockAssignment findBlockAssignment(int gridHorizontalPosition, int gridVerticalPosition) {
   		List<BlockAssignment> assignments = this.getBlockAssignments();
   		
   		for(BlockAssignment assignment : assignments) {
   			int h = assignment.getGridHorizontalPosition();
   			int v = assignment.getGridVerticalPosition();
   			
   			if(h == gridHorizontalPosition && v == gridVerticalPosition) {
   				return assignment;
   			}
   		}
   		return null;
   }
}

class BlockAssignment {
   Integer gridHorizontalPosition;
   Integer gridVerticalPosition;
   
   before constructor {
   
   if (aGridHorizontalPosition <= 0 || aGridHorizontalPosition > (1+(Game.PLAY_AREA_SIDE-2*Game.WALL_PADDING-Block.SIZE)/(Block.SIZE+Game.COLUMNS_PADDING)))
   	 throw new RuntimeException("The horizontal position must be between 1 and "+ (1+(Game.PLAY_AREA_SIDE-2*Game.WALL_PADDING-Block.SIZE)/(Block.SIZE+Game.COLUMNS_PADDING))+".");
   
   if (aGridVerticalPosition <= 0 || aGridVerticalPosition > (1+(Game.PLAY_AREA_SIDE-Paddle.VERTICAL_DISTANCE-Game.WALL_PADDING-Paddle.PADDLE_WIDTH-Ball.BALL_DIAMETER-Block.SIZE)/(Block.SIZE+Game.ROW_PADDING)))
   	 throw new RuntimeException("The vertical position must be between 1 and "+ (1+(Game.PLAY_AREA_SIDE-Paddle.VERTICAL_DISTANCE-Game.WALL_PADDING-Paddle.PADDLE_WIDTH-Ball.BALL_DIAMETER-Block.SIZE)/(Block.SIZE+Game.ROW_PADDING))+".");
   }
   
 
   before setGridHorizontalPosition {
   if (aGridHorizontalPosition <= 0 || aGridHorizontalPosition > (1+(Game.PLAY_AREA_SIDE-2*Game.WALL_PADDING-Block.SIZE)/(Block.SIZE+Game.COLUMNS_PADDING)))
   	 throw new RuntimeException("The horizontal position must be between 1 and "+ (1+(Game.PLAY_AREA_SIDE-2*Game.WALL_PADDING-Block.SIZE)/(Block.SIZE+Game.COLUMNS_PADDING))+".");
   }
   
   before setGridVerticalPosition {
   if (aGridVerticalPosition <= 0 || aGridVerticalPosition > (1+(Game.PLAY_AREA_SIDE-Paddle.VERTICAL_DISTANCE-Game.WALL_PADDING-Paddle.PADDLE_WIDTH-Ball.BALL_DIAMETER-Block.SIZE)/(Block.SIZE+Game.ROW_PADDING)))
   	 throw new RuntimeException("The vertical position must be between 1 and "+ (1+(Game.PLAY_AREA_SIDE-Paddle.VERTICAL_DISTANCE-Game.WALL_PADDING-Paddle.PADDLE_WIDTH-Ball.BALL_DIAMETER-Block.SIZE)/(Block.SIZE+Game.ROW_PADDING))+".");
   }
   
   * -- 1 Level level;
   * -- 1 Block block;
}

class Ball {
   const Integer BALL_DIAMETER = 10;
   Integer minBallSpeedX;
   Integer minBallSpeedY;
   Double ballSpeedIncreaseFactor;
   
   
   before setBallSpeedIncreaseFactor{
   if(!(aBallSpeedIncreaseFactor>0)){
   	throw new RuntimeException("The speed increase factor of the ball must be greater than zero.");
   }
   }
}

class Paddle {
   const Integer PADDLE_WIDTH = 5;
   const Integer VERTICAL_DISTANCE = 30;
   Integer maxPaddleLength;
   Integer minPaddleLength;
   
   before setMinPaddleLength{
   if(aMinPaddleLength<1){
   	throw new RuntimeException("The minimum length of the paddle must be greater than zero.");
   }
   }
   before setMaxPaddleLength{
   if(aMaxPaddleLength<1 || aMaxPaddleLength>Game.PLAY_AREA_SIDE){
   	throw new RuntimeException("The maximum length of the paddle must be greater than zero and less than or equal to "+Game.PLAY_AREA_SIDE+".");
   }
   }
}
