package dk.itu.mario.level;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;

import dk.itu.mario.MarioInterface.GamePlay;
import dk.itu.mario.MarioInterface.LevelInterface;
import dk.itu.mario.engine.DataRecorder;
import dk.itu.mario.engine.sprites.SpriteTemplate;
import dk.itu.mario.level.grammar.LevelGrammar;
import dk.itu.mario.level.grammar.LevelGrammarFactory;
import dk.itu.mario.level.grammar.LevelParseTree;
import dk.itu.mario.level.matcher.ArchetypeMatcher;
import dk.itu.mario.level.matcher.ProfileMatcher;

public class PCGLevel extends Level {
	public static long lastSeed;
	// disable TESTING - enable grammar-based level generation
	public static boolean TESTING = false;

	public int BLOCKS_COINS = 0; // the number of coin blocks
	public int BLOCKS_EMPTY = 0; // the number of empty blocks
	public int BLOCKS_POWER = 0; // the number of power blocks
	public int COINS = 0; // These are the coins in boxes that Mario collect

	// Store information about the level
	public int ENEMIES = 0; // the number of enemies the level contains
	private DataRecorder dataRecorder;
	private HashMap<PlayerProfile.ChallengeRewardType, Double> probability = new HashMap<PlayerProfile.ChallengeRewardType, Double>();
	private Random random;
	private int type;

	public PCGLevel(int width, int height) {
		super(width, height);
	}

	public PCGLevel(int width, int height, long seed, int difficulty, int type,
			GamePlay playerMetrics) {
		this(width, height);

		System.out
				.println("Generating level based on previous GamePlay metrics ONLY.");
		this.dataRecorder = DataRecorder.BLANK_RECORD;

		generateLevel(seed, playerMetrics);
	}

	public PCGLevel(int width, int height, long seed, int difficulty, int type,
			GamePlay playerMetrics, DataRecorder dataRecorder) {
		this(width, height);

		System.out
				.println("Generating level based on previous GamePlay AND DataRecorder metrics.");
		this.dataRecorder = dataRecorder;

		generateLevel(seed, playerMetrics);
	}

	@Override
	public RandomLevel clone() throws CloneNotSupportedException {

		RandomLevel clone = new RandomLevel(width, height);

		clone.xExit = xExit;
		clone.yExit = yExit;
		byte[][] map = getMap();
		SpriteTemplate[][] st = getSpriteTemplate();

		for (int i = 0; i < map.length; i++)
			for (int j = 0; j < map[i].length; j++) {
				clone.setBlock(i, j, map[i][j]);
				clone.setSpriteTemplate(i, j, st[i][j]);
			}
		clone.BLOCKS_COINS = BLOCKS_COINS;
		clone.BLOCKS_EMPTY = BLOCKS_EMPTY;
		clone.BLOCKS_POWER = BLOCKS_POWER;
		clone.ENEMIES = ENEMIES;
		clone.COINS = COINS;

		return clone;

	}

	private void blockify(Level level, boolean[][] blocks, int width, int height) {
		int to = 0;
		if (type == LevelInterface.TYPE_CASTLE) {
			to = 4 * 2;
		} else if (type == LevelInterface.TYPE_UNDERGROUND) {
			to = 4 * 3;
		}

		boolean[][] b = new boolean[2][2];

		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				for (int xx = x; xx <= x + 1; xx++) {
					for (int yy = y; yy <= y + 1; yy++) {
						int _xx = xx;
						int _yy = yy;
						if (_xx < 0)
							_xx = 0;
						if (_yy < 0)
							_yy = 0;
						if (_xx > width - 1)
							_xx = width - 1;
						if (_yy > height - 1)
							_yy = height - 1;
						b[xx - x][yy - y] = blocks[_xx][_yy];
					}
				}

				if (b[0][0] == b[1][0] && b[0][1] == b[1][1]) {
					if (b[0][0] == b[0][1]) {
						if (b[0][0]) {
							level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
						} else {
							// KEEP OLD BLOCK!
						}
					} else {
						if (b[0][0]) {
							// down grass top?
							level.setBlock(x, y, (byte) (1 + 10 * 16 + to));
						} else {
							// up grass top
							level.setBlock(x, y, (byte) (1 + 8 * 16 + to));
						}
					}
				} else if (b[0][0] == b[0][1] && b[1][0] == b[1][1]) {
					if (b[0][0]) {
						// right grass top
						level.setBlock(x, y, (byte) (2 + 9 * 16 + to));
					} else {
						// left grass top
						level.setBlock(x, y, (byte) (0 + 9 * 16 + to));
					}
				} else if (b[0][0] == b[1][1] && b[0][1] == b[1][0]) {
					level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
				} else if (b[0][0] == b[1][0]) {
					if (b[0][0]) {
						if (b[0][1]) {
							level.setBlock(x, y, (byte) (3 + 10 * 16 + to));
						} else {
							level.setBlock(x, y, (byte) (3 + 11 * 16 + to));
						}
					} else {
						if (b[0][1]) {
							// right up grass top
							level.setBlock(x, y, (byte) (2 + 8 * 16 + to));
						} else {
							// left up grass top
							level.setBlock(x, y, (byte) (0 + 8 * 16 + to));
						}
					}
				} else if (b[0][1] == b[1][1]) {
					if (b[0][1]) {
						if (b[0][0]) {
							// left pocket grass
							level.setBlock(x, y, (byte) (3 + 9 * 16 + to));
						} else {
							// right pocket grass
							level.setBlock(x, y, (byte) (3 + 8 * 16 + to));
						}
					} else {
						if (b[0][0]) {
							level.setBlock(x, y, (byte) (2 + 10 * 16 + to));
						} else {
							level.setBlock(x, y, (byte) (0 + 10 * 16 + to));
						}
					}
				} else {
					level.setBlock(x, y, (byte) (0 + 1 * 16 + to));
				}
			}
		}
	}

	private int buildBowlingAlley(int xo, int maxLength) {
		if (maxLength >= 26) {

			int floor = height - 1 - random.nextInt(4);
			int enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE
					: SpriteTemplate.GREEN_TURTLE)
					: SpriteTemplate.GOOMPA;
			PlayerProfile.ChallengeRewardType reward = shouldAddReward();
			boolean arc = random.nextBoolean();
			int numEnemies = 0;
			for (int i = 0; i < 10; i++) {
				numEnemies += shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY) ? 1
						: 0;
			}

			// Create the pit.
			for (int y = floor - 1; y < height; y++) {
				setBlock(xo, y, Level.GROUND);
				setBlock(xo + 1, y, Level.GROUND);
			}

			for (int y = floor; y < height; y++) {
				setBlock(xo + 2, y, Level.GROUND);
				setBlock(xo + 3, y, Level.GROUND);
				setBlock(xo + 4, y, Level.GROUND);
			}

			setSpriteTemplate(xo + 2, floor - 1, new SpriteTemplate(
					SpriteTemplate.RED_TURTLE, false));

			for (int x = 5; x < 26; x++) {
				for (int y = floor - 3; y < height; y++) {
					setBlock(xo + x, y, Level.GROUND);
				}

				if (arc && reward == PlayerProfile.ChallengeRewardType.COIN
						&& x >= 11 && x <= 23) {
					if (x == 15) {
						setBlock(xo + x, floor - 6, Level.COIN);
					}

					else if (x == 16) {
						setBlock(xo + x, floor - 7, Level.COIN);
					}

					else if (x == 18) {
						setBlock(xo + x, floor - 8, Level.COIN);
					}

					else if (x == 19) {
						setBlock(xo + x, floor - 8, Level.COIN);
					}

					else if (x == 20) {
						setBlock(xo + x, floor - 8, Level.COIN);
					}

					else if (x == 22) {
						setBlock(xo + x, floor - 7, Level.COIN);
					}

					else if (x == 23) {
						setBlock(xo + x, floor - 6, Level.COIN);
					}
				}

				else if (!arc
						&& reward == PlayerProfile.ChallengeRewardType.COIN
						&& x >= 10 && x <= 20) {
					setBlock(xo + x, floor - 6, Level.COIN);
				}

				else if (reward == PlayerProfile.ChallengeRewardType.BLOCK
						&& x >= 13 && x <= 17) {
					setBlock(
							xo + x,
							floor - 6,
							(random.nextDouble() <= probability
									.get(PlayerProfile.ChallengeRewardType.POWERUP)) ? Level.BLOCK_POWERUP
									: Level.BLOCK_COIN);
				}

				if (x >= 26 - numEnemies) {
					setSpriteTemplate(xo + x, floor - 4, new SpriteTemplate(
							enemyType, false));
				}
			}

			return 26;
		}

		return 0;
	}

	private int buildCannonLine(int xo, int maxLength) {
		if (maxLength >= 6) {

			int floor = height - 1 - random.nextInt(4);
			int length = 6;

			for (int x = 0; x < length; x++) {
				for (int y = floor; y < height; y++) {
					setBlock(xo + x, y, Level.GROUND);
				}

				if (x == 3) {
					setBlock(xo + x, floor - 1, Level.CANNON_MIDDLE);
					setBlock(xo + x, floor - 2, Level.CANNON_TOP);
					setBlock(
							xo + x,
							floor - 3,
							shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? Level.CANNON_TOP
									: Level.CANNON_MIDDLE);
					setBlock(xo + x, floor - 4, Level.CANNON_TOP);
				}
			}

			return length;
		}
		return 0;
	}

	private int buildCoinDive(int xo, int maxLength) {
		if (maxLength >= 18) {
			int length = 18;

			int floor = height - 1 - random.nextInt(4);

			for (int x = 0; x < length; x++) {
				for (int y = floor; y < height; y++) {
					setBlock(xo + x, y, Level.GROUND);
				}

				if (x > 2 && x < 6) {
					setBlock(xo + x, floor - 3, Level.BLOCK_EMPTY);
				} else if (x > 2 && x < 11) {
					setBlock(xo + x, floor - 5, Level.BLOCK_EMPTY);
				} else if (x == 11 || x == 12) {
					setBlock(xo + x, floor - 6, Level.COIN);
				} else if (x == 13) {
					setBlock(xo + x, floor - 5, Level.COIN);
				} else if (x == 14) {
					setBlock(xo + x, floor - 4, Level.COIN);
				} else if (x == 15) {
					setBlock(xo + x, floor - 3, Level.COIN);
				}
			}

			return length;
		}

		return 0;

	}

	private int buildFreebie(int xo, int maxLength) {
		if (maxLength >= 9) {
			int floor = height - 1 - random.nextInt(4);
			for (int x = 0; x < 9; x++) {
				for (int y = 0; y < height; y++) {
					if (y >= floor) {
						setBlock(xo + x, y, GROUND);
					}
				}
			}

			if (random.nextBoolean()) {
				setBlock(xo + 1, floor - 1, Level.BLOCK_EMPTY);
				setBlock(xo + 7, floor - 1, Level.BLOCK_POWERUP);

				setSpriteTemplate(xo + 6, floor - 1, new SpriteTemplate(
						SpriteTemplate.GREEN_TURTLE, false));
			}

			else {
				int powerupLoc = random.nextInt(5);

				setBlock(xo + 2, floor - 3,
						powerupLoc == 0 ? Level.BLOCK_POWERUP
								: Level.BLOCK_COIN);
				setBlock(xo + 3, floor - 3,
						powerupLoc == 1 ? Level.BLOCK_POWERUP
								: Level.BLOCK_COIN);
				setBlock(xo + 4, floor - 3,
						powerupLoc == 2 ? Level.BLOCK_POWERUP
								: Level.BLOCK_COIN);
				setBlock(xo + 5, floor - 3,
						powerupLoc == 3 ? Level.BLOCK_POWERUP
								: Level.BLOCK_COIN);
				setBlock(xo + 6, floor - 3,
						powerupLoc == 4 ? Level.BLOCK_POWERUP
								: Level.BLOCK_COIN);
			}

			return 9;
		}

		else if (maxLength >= 5) {

			int floor = height - 1 - random.nextInt(4);
			for (int x = 0; x < 5; x++) {
				for (int y = floor; y < height; y++) {
					setBlock(xo + x, y, GROUND);
				}
			}

			if (random.nextBoolean()) {
				setBlock(xo, floor - 1, Level.BLOCK_EMPTY);
				setBlock(xo + 4, floor - 1, Level.BLOCK_POWERUP);

				setSpriteTemplate(xo + 3, floor - 1, new SpriteTemplate(
						SpriteTemplate.GREEN_TURTLE, false));
			}

			else {
				setBlock(xo + 2, floor - 3, Level.BLOCK_POWERUP);
			}

			return 5;
		}
		return 0;
	}

	private int buildLemmingTrap(int xo, int maxLength) {
		if (maxLength >= 14) {
			int floor = height - 1 - random.nextInt(4);
			int enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE
					: SpriteTemplate.GREEN_TURTLE)
					: SpriteTemplate.GOOMPA;
			boolean flying = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY)
					&& shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY)
					&& shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY);
			PlayerProfile.ChallengeRewardType reward = shouldAddReward();

			for (int x = 0; x < 18; x++) {
				if (x > 5) {
					for (int y = floor - 4; y < height; y++) {
						setBlock(xo + x, y, Level.GROUND);
					}
				}

				else {
					for (int y = floor; y < height; y++) {
						setBlock(xo + x, y, Level.GROUND);
					}
				}

				if (x > 6 && x % 2 == 0) {
					setSpriteTemplate(xo + x, floor - 5, new SpriteTemplate(
							enemyType, flying));
				}

				if (reward == PlayerProfile.ChallengeRewardType.COIN && x >= 7
						&& x <= 16) {
					setBlock(xo + x, floor - 7, Level.COIN);
				}

				else if (reward == PlayerProfile.ChallengeRewardType.BLOCK
						&& x >= 10 && x <= 13) {
					setBlock(
							xo + x,
							floor - 7,
							random.nextDouble() < probability
									.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
									: Level.BLOCK_COIN);
				}
			}
			return 18;
		}

		return 0;
	}

	private int buildMaze(int xo, int maxLength) {
		if (maxLength >= 19) {

			int length = random.nextInt(maxLength - 19) + 20;
			int soFar = 6;
			int next;
			// boolean skipUp = false;
			// boolean skipDown = false;

			class Stretch {
				public int len;
				public LevelComponent.MazeLevel lvl;

				public Stretch(int lngth) {
					len = lngth;

					switch (random.nextInt(3)) {
					case 0:
						lvl = LevelComponent.MazeLevel.TOP;
						break;
					case 1:
						lvl = LevelComponent.MazeLevel.MID;
						break;
					default:
						lvl = LevelComponent.MazeLevel.BOT;
					}
				}
			}

			ArrayList<Stretch> maze = new ArrayList<Stretch>();

			loop: while (soFar < length) {
				if (soFar + 3 > length) {
					length = soFar;
					break loop;
				}

				next = random.nextInt(18) + 5;

				if (soFar + next > length) {
					next = length - soFar;
				}

				maze.add(new Stretch(next));

				soFar += next;
			}

			setBlock(xo, this.height - 1, Level.GROUND);
			setBlock(xo + 1, this.height - 1, Level.GROUND);
			setBlock(xo + 2, this.height - 1, Level.GROUND);
			soFar = 3;

			Stretch str;
			// Stretch nxt;
			boolean stretchEnd;
			boolean midLine;
			boolean addEnemy;
			int heightMod;
			int enemyType;
			for (int i = 0; i < maze.size(); i++) {

				str = maze.get(i);

				for (int x = 0; x < str.len; x++) {

					setBlock(xo + soFar + x, this.height - 1, Level.GROUND);

					// skipUp = (skipUp && (x == str.len - 2))
					// || (str.len >= 5 && x == (str.len / 2));
					// skipDown = (skipDown && (x == str.len - 2))
					// || (str.len >= 5 && x == (str.len / 2));
					midLine = (str.len >= 5 && x == (str.len / 2) - 1);
					stretchEnd = (x == str.len - 1);

					addEnemy = !midLine && !stretchEnd
							&& (random.nextDouble() < .25);

					if // ((stretchEnd && nxt != null && nxt.lvl !=
						// MazeLevel.BOT)
						// ||
					(midLine && str.lvl != LevelComponent.MazeLevel.BOT) // )
					{
						setBlock(xo + soFar + x, this.height - 2, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 3, Level.ROCK);
					}
					if // ((stretchEnd && nxt != null && nxt.lvl !=
						// MazeLevel.MID)
						// ||
					(midLine && str.lvl != LevelComponent.MazeLevel.MID)// )
					{
						setBlock(xo + soFar + x, this.height - 5, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 6, Level.ROCK);
					}
					if // ((stretchEnd && nxt != null && nxt.lvl !=
						// MazeLevel.TOP)
						// ||
					(midLine && str.lvl != LevelComponent.MazeLevel.TOP)// )
					{
						setBlock(xo + soFar + x, this.height - 8, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 9, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 10, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 11, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 12, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 13, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 14, Level.ROCK);
						setBlock(xo + soFar + x, this.height - 15, Level.ROCK);
					}

					if (!stretchEnd) {
						setBlock(xo + soFar + x, this.height - 7, Level.ROCK);
					}

					if (!stretchEnd) {
						setBlock(xo + soFar + x, this.height - 4, Level.ROCK);
					}

					if (addEnemy
							&& shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)) {

						enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE
								: SpriteTemplate.RED_TURTLE)
								: SpriteTemplate.GREEN_TURTLE)
								: SpriteTemplate.GOOMPA;

						switch (random.nextInt(3)) {
						case 0:
							heightMod = 8;
							break;
						case 1:
							heightMod = 5;
							break;
						default:
							heightMod = 2;
						}

						setSpriteTemplate(xo + soFar + x, this.height
								- heightMod, new SpriteTemplate(enemyType,
								false));
					}
				}

				soFar += str.len;
			}

			setBlock(xo + length - 1, this.height - 1, Level.GROUND);
			setBlock(xo + length - 2, this.height - 1, Level.GROUND);
			setBlock(xo + length - 3, this.height - 1, Level.GROUND);

			return length;
		}
		return 0;
	}

	private int buildPipeJump(int xo, int maxLength) {
		int length = 0;
		int lastHeight = 4;
		int localHeight;
		int midFloor;
		int pipeHeight;
		int gap = 0;
		boolean space;
		boolean deadGaps = shouldAddChallenge(PlayerProfile.ChallengeRewardType.GAP);

		int numPipes;
		for (numPipes = 0; shouldAddChallenge(deadGaps ? PlayerProfile.ChallengeRewardType.GAP
				: PlayerProfile.ChallengeRewardType.JUMP); numPipes++) {
		}

		localHeight = random.nextInt(2) + 1;
		loop: for (int i = 0; i < numPipes; i++) {
			space = (length + 2) <= maxLength;

			if (space) {
				while (Math.abs(localHeight - lastHeight) > 3) {
					localHeight += localHeight > lastHeight ? -1 : 1;
				}

				lastHeight = localHeight;

				pipeHeight = localHeight > 5 ? 4 + random
						.nextInt(localHeight - 4) : 4;

				for (int y = 0; y < localHeight; y++) {
					setBlock(xo + length, this.height - 1 - y, Level.GROUND);
					setBlock(xo + length + 1, this.height - 1 - y, Level.GROUND);

					if (y == localHeight - 1) {
						setBlock(xo + length, this.height - 1 - y,
								Level.TUBE_TOP_LEFT);
						setBlock(xo + length + 1, this.height - 1 - y,
								Level.TUBE_TOP_RIGHT);

						if (shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)) {
							setSpriteTemplate(xo + length, this.height - y,
									new SpriteTemplate(
											SpriteTemplate.JUMP_FLOWER, false));
						}
					} else if (y > localHeight - pipeHeight) {
						setBlock(xo + length, this.height - 1 - y,
								Level.TUBE_SIDE_LEFT);
						setBlock(xo + length + 1, this.height - 1 - y,
								Level.TUBE_SIDE_RIGHT);
					}
				}

				length += 2;

				localHeight = random.nextInt(7) + 4;

				midFloor = localHeight - (random.nextInt(4) + 1);
				if (midFloor <= 0) {
					midFloor = 1;
				}

				for (gap = 0; gap < 4
						&& length + gap <= maxLength
						&& shouldAddChallenge(PlayerProfile.ChallengeRewardType.JUMP); gap++) {

					if (!deadGaps) {
						for (int y = 0; y < midFloor; y++) {
							setBlock(xo + length, this.height - 1 - y,
									Level.GROUND);
						}
					}
					length++;
				}
			}

			else {
				break loop;
			}
		}

		return length;
	}

	private int buildPlatformJump(int xo, int maxLength) {
		int length = 0;
		int gapLength;

		for (gapLength = 1; shouldAddChallenge(PlayerProfile.ChallengeRewardType.JUMP)
				&& gapLength < 4; gapLength++) {
		}

		int maxNumPlatforms = (maxLength - 3) / (4 + gapLength);

		if (maxNumPlatforms == 0) {
			return 0;
		}

		int numPlatforms;
		for (numPlatforms = 0; numPlatforms < maxNumPlatforms
				&& shouldAddChallenge(PlayerProfile.ChallengeRewardType.GAP); numPlatforms++) {
		}

		if (numPlatforms > 1) {
			boolean found = false;
			LevelComponent.MazeLevel nextDir = LevelComponent.MazeLevel.TOP;
			LevelComponent.PlatformLevel last;
			LevelComponent.PlatformLevel next;
			ArrayList<LevelComponent.PlatformLevel> jumps = new ArrayList<LevelComponent.PlatformLevel>();
			int heightMod = 0;
			int lastHeightMod = 0;
			int enemyType;
			PlayerProfile.ChallengeRewardType reward;
			LevelComponent.PlatformLevel hold;

			jumps.add(LevelComponent.PlatformLevel.BOT);

			for (int i = 1; i < numPlatforms; i++) {
				last = jumps.get(i - 1);
				found = false;

				while (!found) {
					switch (random.nextInt(5)) {
					case 0:
					case 1:
						nextDir = LevelComponent.MazeLevel.BOT;
						break;
					case 2:
					case 3:
						nextDir = LevelComponent.MazeLevel.TOP;
						break;
					default:
						nextDir = LevelComponent.MazeLevel.MID;
					}

					found = !((last == LevelComponent.PlatformLevel.TOP && nextDir == LevelComponent.MazeLevel.TOP) || (last == LevelComponent.PlatformLevel.BOT && nextDir == LevelComponent.MazeLevel.BOT));
				}

				if ((last == LevelComponent.PlatformLevel.BOT && nextDir == LevelComponent.MazeLevel.MID)
						|| (last == LevelComponent.PlatformLevel.MID_D && nextDir == LevelComponent.MazeLevel.BOT)) {
					next = LevelComponent.PlatformLevel.BOT;
				}

				else if ((last == LevelComponent.PlatformLevel.MID_D && nextDir == LevelComponent.MazeLevel.MID)
						|| (last == LevelComponent.PlatformLevel.MID_U && nextDir == LevelComponent.MazeLevel.BOT)
						|| (last == LevelComponent.PlatformLevel.BOT && nextDir == LevelComponent.MazeLevel.TOP)) {
					next = LevelComponent.PlatformLevel.MID_D;
				}

				else if ((last == LevelComponent.PlatformLevel.MID_U && nextDir == LevelComponent.MazeLevel.MID)
						|| (last == LevelComponent.PlatformLevel.TOP && nextDir == LevelComponent.MazeLevel.BOT)
						|| (last == LevelComponent.PlatformLevel.MID_D && nextDir == LevelComponent.MazeLevel.TOP)) {
					next = LevelComponent.PlatformLevel.MID_U;
				}

				else // if ((last == PlatformLevel.TOP && nextDir ==
						// MazeLevel.MID)
						// || (last == PlatformLevel.MID_U && nextDir ==
						// MazeLevel.TOP))
				{
					next = LevelComponent.PlatformLevel.TOP;
				}

				jumps.add(next);
			}

			setBlock(xo, this.height - 1, Level.GROUND);
			setBlock(xo + 1, this.height - 1, Level.GROUND);
			setBlock(xo + 2, this.height - 1, Level.GROUND);
			length = 3;

			for (int x = 0; x < jumps.size(); x++) {
				if (x > 0) {
					lastHeightMod = heightMod;
				}

				hold = jumps.get(x);
				switch (hold) {
				case TOP:
					heightMod = 12;
					break;
				case MID_U:
					heightMod = 9;
					break;
				case MID_D:
					heightMod = 6;
					break;
				default:
					heightMod = 3;
				}

				heightMod += (x > 0 && random.nextBoolean() ? random.nextInt(2)
						: -1 * random.nextInt(2));

				while (x > 0 && heightMod - lastHeightMod >= 5) {
					heightMod--;
				}

				setBlock(xo + length, this.height - heightMod, Level.ROCK);
				setBlock(xo + length + 1, this.height - heightMod, Level.ROCK);
				setBlock(xo + length + 2, this.height - heightMod, Level.ROCK);
				setBlock(xo + length + 3, this.height - heightMod, Level.ROCK);

				if (shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)) {

					enemyType = random.nextBoolean() ? SpriteTemplate.RED_TURTLE
							: (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? SpriteTemplate.ARMORED_TURTLE
									: SpriteTemplate.GREEN_TURTLE)
									: SpriteTemplate.GOOMPA);

					setSpriteTemplate(
							xo + length + 3,
							this.height - heightMod - 1,
							new SpriteTemplate(
									enemyType,
									(enemyType == SpriteTemplate.RED_TURTLE && shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY))
											|| enemyType != SpriteTemplate.RED_TURTLE));
				}

				reward = shouldAddReward();
				if (reward == PlayerProfile.ChallengeRewardType.COIN
						&& hold != LevelComponent.PlatformLevel.TOP) {
					setBlock(xo + length, this.height - heightMod - 3,
							Level.COIN);
					setBlock(xo + length + 1, this.height - heightMod - 3,
							Level.COIN);
					setBlock(xo + length + 2, this.height - heightMod - 3,
							Level.COIN);
					setBlock(xo + length + 3, this.height - heightMod - 3,
							Level.COIN);
				}

				else if (reward == PlayerProfile.ChallengeRewardType.BLOCK
						&& hold != LevelComponent.PlatformLevel.TOP) {
					setBlock(
							xo + length + 1,
							this.height - heightMod - 3,
							random.nextDouble() < probability
									.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
									: Level.BLOCK_COIN);
					setBlock(
							xo + length + 2,
							this.height - heightMod - 3,
							random.nextDouble() < probability
									.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
									: Level.BLOCK_COIN);
				}

				length += 4 + gapLength;
			}
		}

		return length;
	}

	private int buildSinglePit(int xo, int maxLength) {
		if (maxLength >= 13) {
			int length = 13;
			int floor = height - 1 - random.nextInt(4);

			for (int x = 0; x < length; x++) {
				if (x != 5 && x != 6 && x != 7) {
					for (int y = floor; y < height; y++) {
						setBlock(xo + x, y, Level.GROUND);
					}
				}

				if ((x >= 2 && x <= 4) || (x >= 8 && x <= 10)) {
					setBlock(xo + x, floor - 1, Level.ROCK);

					if (x == 3 || x == 4 || x == 8 || x == 9) {
						setBlock(xo + x, floor - 2, Level.ROCK);
					}

					if (x == 4 || x == 8) {
						setBlock(xo + x, floor - 3, Level.ROCK);
					}
				}
			}

			return length;
		}

		return 0;
	}

	private int buildStraight(int xo, int maxLength, boolean allowEnemies) {
		int length = random.nextInt(15) + 2;

		// if (safe)
		// length = 10 + random.nextInt(5);

		if (length > maxLength)
			length = maxLength;

		int floor = height - 1 - random.nextInt(4);
		PlayerProfile.ChallengeRewardType reward = shouldAddReward();

		// runs from the specified x position to the length of the segment
		for (int x = xo; x < xo + length; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}

		if (shouldAddChallenge(PlayerProfile.ChallengeRewardType.ENEMY)
				&& allowEnemies) {
			int enemyType = shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY) ? (shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY)
					|| (reward != null && length >= 5) ? SpriteTemplate.ARMORED_TURTLE
					: SpriteTemplate.CANNON_BALL)
					: SpriteTemplate.RED_TURTLE)
					: SpriteTemplate.GREEN_TURTLE)
					: SpriteTemplate.GOOMPA;

			if (enemyType == SpriteTemplate.CANNON_BALL) {
				int height = random.nextInt(4) + 1;

				if (height == 1) {
					setBlock(xo + (length / 2), floor - 1, Level.CANNON_TOP);
				}

				else if (height == 2) {
					setBlock(xo + (length / 2), floor - 1, Level.CANNON_MIDDLE);
					setBlock(xo + (length / 2), floor - 2, Level.CANNON_TOP);
				}

				else if (height == 3) {
					setBlock(xo + (length / 2), floor - 1, Level.CANNON_BOTTOM);
					setBlock(xo + (length / 2), floor - 2, Level.CANNON_MIDDLE);
					setBlock(xo + (length / 2), floor - 3, Level.CANNON_TOP);
				}

				else {
					setBlock(xo + (length / 2), floor - 1, Level.CANNON_BOTTOM);
					setBlock(xo + (length / 2), floor - 2, Level.CANNON_BOTTOM);
					setBlock(xo + (length / 2), floor - 3, Level.CANNON_MIDDLE);
					setBlock(xo + (length / 2), floor - 4, Level.CANNON_TOP);
				}
			}

			else {
				setSpriteTemplate(
						xo + (length / 2),
						floor - 1,
						new SpriteTemplate(
								enemyType,
								shouldAddChallenge(PlayerProfile.ChallengeRewardType.HARDER_ENEMY)));
			}
		}

		if (reward != null) {
			if (length >= 13) {
				setBlock(xo + (length - 11), floor - 3, Level.COIN);
				setBlock(xo + (length - 10), floor - 4, Level.COIN);
				setBlock(xo + (length - 8), floor - 5, Level.COIN);
				setBlock(xo + (length - 7), floor - 5, Level.COIN);
				setBlock(xo + (length - 6), floor - 5, Level.COIN);
				setBlock(xo + (length - 4), floor - 4, Level.COIN);
				setBlock(xo + (length - 3), floor - 3, Level.COIN);
			}

			else if (length >= 7) {
				int start = length / 2;
				boolean coins = (reward == PlayerProfile.ChallengeRewardType.COIN);

				setBlock(
						xo + (start - 2),
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
				setBlock(
						xo + (start - 1),
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
				setBlock(
						xo + start,
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
				setBlock(
						xo + (start + 1),
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
				if (length % 2 != 0) {
					setBlock(
							xo + (start + 2),
							floor - 3,
							coins ? Level.COIN
									: random.nextDouble() < probability
											.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
											: Level.BLOCK_COIN);
				}
			}

			else if (length >= 5) {
				int start = length / 2;
				boolean coins = (reward == PlayerProfile.ChallengeRewardType.COIN);

				setBlock(
						xo + (start - 1),
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
				setBlock(
						xo + start,
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
				setBlock(
						xo + (start + 1),
						floor - 3,
						coins ? Level.COIN
								: random.nextDouble() < probability
										.get(PlayerProfile.ChallengeRewardType.POWERUP) ? Level.BLOCK_POWERUP
										: Level.BLOCK_COIN);
			}
		}

		return length;
	}

	private void create(long seed, PlayerProfile profile,
			LevelArchetype archetype, LevelGrammar grammar) {

		System.out
				.println("PlayerProfile.getProbability(COINS): "
						+ profile
								.getProbability(PlayerProfile.ChallengeRewardType.COIN));

		if (dataRecorder == DataRecorder.BLANK_RECORD) {
			System.out
					.println("DataRecorder record is BLANK - using GamePlay metrics only.");
		}

		this.type = archetype.getTypeInt();

		lastSeed = seed;
		random = new Random(seed);
		int length = 0;

		if (TESTING) {
			int minElements = 5;
			int maxLen;
			int elementsSoFar = 0;

			length = buildStraight(0, width - 64, false);

			while (length < width - 64) {
				maxLen = elementsSoFar < minElements ? (width - 64 - length)
						/ (minElements - elementsSoFar) : width - 64 - length;

				switch (random.nextInt(9)) {
				case 0:
					length += buildStraight(length, maxLen, true);
					break;
				case 1:
					length += buildFreebie(length, maxLen);
					break;
				case 2:
					length += buildBowlingAlley(length, maxLen);
					break;
				case 3:
					length += buildLemmingTrap(length, maxLen);
					break;
				case 4:
					length += buildPipeJump(length, maxLen);
					break;
				case 5:
					length += buildPlatformJump(length, maxLen);
					break;
				case 6:
					length += buildCoinDive(length, maxLen);
					break;
				case 7:
					length += buildCannonLine(length, maxLen);
					break;
				case 8:
					length += buildSinglePit(length, maxLen);
					break;
				default:
					length += buildMaze(length, maxLen);
				}

				elementsSoFar++;
			}
		}

		else {
			System.out.println("Generating level for component list: ");
			LevelParseTree parseTree = grammar.generateRandomTree(seed, width);

			int MAX_REGENS = 10;
			int nRegens = 0;
			while (!FitnessEvaluator.isFit(parseTree, profile, archetype)
					&& nRegens < MAX_REGENS) {
				System.out
						.println("Generated level is NOT fit.  Regenerating...");
				parseTree = grammar.generateRandomTree(seed, width);
				nRegens++;
			}

			if (nRegens == MAX_REGENS) {
				System.out.println("Failed to generate a fit level after "
						+ nRegens + " attempts.  Proceeding with unfit level.");
			}

			List<LevelComponent> levelTemplate = parseTree.getLevelTemplate();

			for (LevelComponent lcomp : levelTemplate) {
				LevelComponent.TYPE lctype = lcomp.getType();
				System.out.println("Building for: " + lcomp);
				while (length < Math.min(width - 64, lcomp.getEnd())) {
					switch (lctype) {
					case FLAT_LO:
					case FLAT_HI:
						length += buildStraight(length, lcomp.getEnd(), true);
						break;
					case PIPE_JUMP:
						length += buildPipeJump(length, width - 64 - length);
						break;
					case PLATFORM_JUMP:
						length += buildPlatformJump(length, width - 64 - length);
						break;
					case MAZE:
						length += buildMaze(length, width - 64 - length);
						break;
					default:
						System.out
								.println("Cannot build level segment for unrecognized LevelComponent type: "
										+ type);
					}
				}
			}
		}

		System.out.println("Total length built: " + length);

		// set the end piece
		int floor = height - 1 - random.nextInt(4);

		xExit = length + 8;
		yExit = floor;

		fillEndPiece(length, floor);
	}

	private void fillEndPiece(int length, int floor) {
		// fills the end piece
		for (int x = length; x < width; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}

		if (type == LevelInterface.TYPE_CASTLE
				|| type == LevelInterface.TYPE_UNDERGROUND) {
			int ceiling = 0;
			int run = 0;
			for (int x = 0; x < width; x++) {
				if (run-- <= 0 && x > 4) {
					ceiling = random.nextInt(4);
					run = random.nextInt(4) + 4;
				}
				for (int y = 0; y < height; y++) {
					if ((x > 4 && y <= ceiling) || x < 1) {
						setBlock(x, y, GROUND);
					}
				}
			}
		}

		fixWalls();
	}

	private void fixWalls() {
		boolean[][] blockMap = new boolean[width + 1][height + 1];

		for (int x = 0; x < width + 1; x++) {
			for (int y = 0; y < height + 1; y++) {
				int blocks = 0;
				for (int xx = x - 1; xx < x + 1; xx++) {
					for (int yy = y - 1; yy < y + 1; yy++) {
						if (getBlockCapped(xx, yy) == GROUND) {
							blocks++;
						}
					}
				}
				blockMap[x][y] = blocks == 4;
			}
		}
		blockify(this, blockMap, width + 1, height + 1);
	}

	private void generateLevel(long seed, GamePlay playerMetrics) {

		PlayerProfile profile = ProfileMatcher.getMatchingProfile(
				playerMetrics, dataRecorder);
		System.out.println("PlayerProfile: " + profile);

		LevelArchetype archetype = ArchetypeMatcher.getMatchingArchetype(
				playerMetrics, dataRecorder);
		System.out.println("LevelArchetype: " + archetype);

		System.out.println("Creating level grammar");
		LevelGrammar grammar;
		try {
			String grammarFileName = "grammars/overland.grm";
			grammar = LevelGrammarFactory.createGrammar(new File(
					grammarFileName));
			System.out.println("Read grammar from file: " + grammarFileName);
			System.out.println("==== LEVEL GRAMMAR ====");
			System.out.println(grammar);
			System.out.println("=======================");
		} catch (Exception ex) {
			System.out
					.println("Failed to parse grammar file due to exception: "
							+ ex.getMessage());
			System.out.println("Defaulting to basic overland grammar.");
			grammar = LevelGrammarFactory.createGrammar();
		}
		System.out
				.println("Tuning grammar for PlayerProfile & LevelArchetype using RETE");
		GrammarTuner.tune(grammar, profile, archetype);

		System.out.println("Creating level.");

		initProbabilities(profile);

		create(seed, profile, archetype, grammar);
	}

	private void initProbabilities(PlayerProfile profile) {
		probability
				.put(PlayerProfile.ChallengeRewardType.BLOCK,
						new Double(
								profile.getProbability(PlayerProfile.ChallengeRewardType.BLOCK)));
		probability
				.put(PlayerProfile.ChallengeRewardType.COIN,
						new Double(
								profile.getProbability(PlayerProfile.ChallengeRewardType.COIN)));
		probability
				.put(PlayerProfile.ChallengeRewardType.POWERUP,
						new Double(
								profile.getProbability(PlayerProfile.ChallengeRewardType.POWERUP)));

		probability
				.put(PlayerProfile.ChallengeRewardType.ENEMY,
						new Double(
								profile.getProbability(PlayerProfile.ChallengeRewardType.ENEMY)));
		probability.put(PlayerProfile.ChallengeRewardType.GAP, new Double(
				profile.getProbability(PlayerProfile.ChallengeRewardType.GAP)));
		probability
				.put(PlayerProfile.ChallengeRewardType.HARDER_ENEMY,
						new Double(
								profile.getProbability(PlayerProfile.ChallengeRewardType.HARDER_ENEMY)));
		probability
				.put(PlayerProfile.ChallengeRewardType.JUMP,
						new Double(
								profile.getProbability(PlayerProfile.ChallengeRewardType.JUMP)));
	}

	private boolean shouldAddChallenge(PlayerProfile.ChallengeRewardType crt) {
		return random.nextDouble() <= probability.get(crt);
	}

	private PlayerProfile.ChallengeRewardType shouldAddReward() {
		double guess = random.nextDouble();

		if (guess < probability.get(PlayerProfile.ChallengeRewardType.COIN)) {
			return PlayerProfile.ChallengeRewardType.COIN;
		} else if (guess < probability
				.get(PlayerProfile.ChallengeRewardType.COIN)
				+ probability.get(PlayerProfile.ChallengeRewardType.BLOCK)) {
			return PlayerProfile.ChallengeRewardType.BLOCK;
		} else {
			return null;
		}

	}

}