
import java.awt.Canvas;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Random;

import sun.audio.AudioPlayer;
import sun.audio.AudioStream;

public class Game extends Canvas {
	
	private static final long serialVersionUID = -2914858856812762749L;
	public static final double FRICTION = 0.9;
	public static final double GRAVITY = 9.8;
	
	Random r = new Random();
	
	/** The list of all the entities that exist in our game */
	public ArrayList<Entity> entities = new ArrayList<Entity>();
	/** The list of entities that need to be removed from the game this loop */
	private ArrayList<Entity> removeList = new ArrayList<Entity>();

	private int moveSpeed = 200; // 200
	private int jumpSpeed = 300; // 300
	
	public Entity player;
	/** Player that is in the body of an enemy **/
	public Entity shellPlayer;
	
	boolean loading = false;
	
	/** The message to display which waiting for a key press */
	private Sprite message;
	/** True if we're holding up game play until a key has been pressed */
	private boolean waitingForKeyPress = true;
	/** True if game logic needs to be applied this loop, normally as a result of a game event */
	private boolean logicRequiredThisLoop = false;

	/** The time at which the last rendering looped started from the point of view of the game logic */
	private long lastLoopTime = System.currentTimeMillis();
	/** The window that is being used to render the game */
	private JoglGameWindow window;
	
	/** The sprite containing the "Press Any Key" message */
	private Sprite pressAnyKey;
	/** The sprite containing the "You win!" message */
	private Sprite youWin;
	/** The sprite containing the "You lose!" message */
	private Sprite youLose;
	
//	private Sprite background;
	
	/** The time since the last record of fps */
	private long lastFpsTime = 0;
	/** The recorded fps */
	private int fps;
	
	public int currentScreen = 1;
	
	/** The normal title of the window */
	private String windowTitle = "Lunar Mind - Ludum Dare 18";
	private int countMindTime = 0;
	private int countExplodeTime = 0;
	private int timeToMindControl = 3000;
	private int timeToExplode = 3000;
	private int countMindParticleTime = 0;
	private int timeToMindParticle = 100;
	
	ParticleManager pm;
	/**
	 * Construct our game and set it running.
	 * 
	 * @param renderingType The type of rendering to use (should be one of the contansts from ResourceFactory)
	 */
	public Game() {
		window = ResourceFactory.get().getGameWindow();
		window.setResolution(800,600);
		window.setGameWindowCallback(this);
		window.setTitle(windowTitle);
	}

	public void startRendering() {
		window.startRendering();
	}
	
	public void initialise() {
		youLose = ResourceFactory.get().getSprite("sprites/TextDisplay/youlose.png");
		pressAnyKey = ResourceFactory.get().getSprite("sprites/TextDisplay/pressspace.png");
		youWin = ResourceFactory.get().getSprite("sprites/youwin.gif");
		
		player = new Player(this, 0, 0);
		shellPlayer = new PlayerMindControlling("sprites/player/m1.png", this, -500, -500, -1, -1, -1, -1);
		
		message = pressAnyKey;
		
		pm = new ParticleManager(this);
		
		// Load the tune and play it
		InputStream in;
		try {
			in = new FileInputStream("test.mid");
			AudioStream as = new AudioStream(in);
			AudioPlayer.player.start(as);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		// setup the initial game state
		startGame();
	}
	
	private void startGame() {
		((Player) player).hp = 30;
		loadScreen(1, 200, 350); // NOTE CHANGE BACK
	}
	
	public Enemy findNearestEnemy(double x, double y) {
		Entity nearestEnemy = null;
		double dist = 99999;
		for (int i = 0; i < entities.size(); i++) {
			Entity e = (Entity) entities.get(i);
			if (e instanceof Enemy) {
				double dist2 = Util.distBetween(x, y, e.x, e.y);
				if (dist2 < dist) {
					dist = dist2;
					nearestEnemy = e;
				}
			}
		}
		return (Enemy) nearestEnemy;
	}
	
	public void loadScreen(int screen, int playerX, int playerY) { // TODO LOAD SCRENS
		loading = true;
		entities.clear();
		switch (screen) {
			case 1 : { 
				// background
				Entity background = new Background("sprites/backgrounds/background1.png", this, 0, 0);
				entities.add(background);
				
				// block test
				for (int i = 0; i < 4; i++) {
					for (int j = 0; j < 1; j++) {
						Entity block = new Block("sprites/platforms/short.png", 100 + i*200, 500 + j*90, 7, 20, 200, 80);
						entities.add(block);
					}
				}
				
				// add player
				player.setPos(playerX, playerY);
				entities.add(player);
				
				// Screen Changer
				Entity screenChanger = new ScreenChanger(this, 850, 380, 2, -100, 350);
				entities.add(screenChanger);
				
				break;
			}
			case 2 : {
				// background
				Entity background = new Background("sprites/backgrounds/backgroundBlah.png", this, 0, 0);
				entities.add(background);
				
				// block test
				for (int i = 0; i < 5; i++) {
					for (int j = 0; j < 1; j++) {
						Entity block = new Block("sprites/platforms/short.png", -30 + i*200, 500 + j*90, 7, 20, 200, 80);
						entities.add(block);
					}
				}
				
				// add player
				player.setPos(playerX, playerY);
				entities.add(player);
				
//				// add spider
//				Entity spider = new Spider(this, 500, 430);
//				entities.add(spider);
				
				// add piston
				Entity piston = new Piston(this, 500, 320);
				entities.add(piston);
				
				// add lever for piston
				Entity lever = new Lever(this, 200, 440, (Piston) piston);
				entities.add(lever);
				
				// Screen Changers
				Entity screenChangerBack = new ScreenChanger(this, -100, 380, 1, 850, 350);
				entities.add(screenChangerBack);
				Entity screenChanger = new ScreenChanger(this, 850, 380, 3, -100, 350);
				entities.add(screenChanger);
				
				break;
			}
			case 3 : {
				// background
				Entity background = new Background("sprites/backgrounds/background2.png", this, 0, 0);
				entities.add(background);
				
				// block test
				for (int i = 0; i < 4; i++) {
					for (int j = 0; j < 1; j++) {
						Entity block = new Block("sprites/platforms/med.png", -70 + i*360, 500 + j*90, 7, 20, 360, 80);
						entities.add(block);
					}
				}
				
				// add player
				player.setPos(playerX, playerY);
				entities.add(player);
				
				// add spider
				Entity spider = new Spider(this, 500, 430);
				entities.add(spider);
				
				// Add EnemyBouncers
				Entity b1 = new EnemyBouncer(390, 470, false);
				Entity b2 = new EnemyBouncer(750, 470, true);
				entities.add(b1);
				entities.add(b2);
				
				// add piston
				Entity piston = new Piston(this, 300, 320);
				entities.add(piston);
				
				// add lever for piston
				Entity lever = new Lever(this, 400, 440, (Piston) piston);
				entities.add(lever);
				
				// Screen Changers
				Entity screenChangerBack = new ScreenChanger(this, -100, 380, 2, 850, 350);
				entities.add(screenChangerBack);
				Entity screenChanger = new ScreenChanger(this, 850, 380, 4, -100, 30);
				entities.add(screenChanger);
				
				break;
			}
			case 4 : {
				// background
				Entity background = new Background("sprites/backgrounds/background1.png", this, 0, 0);
				entities.add(background);
				
				// blocks
				Entity block = new Block("sprites/platforms/short.png", -30, 200, 7, 20, 200, 80);
				entities.add(block);
				
				for (int i = 0; i < 2; i++) {
					for (int j = 0; j < 1; j++) {
							Entity block1 = new Block("sprites/platforms/long.png", -100 + i*480, 500 + j*90, 10, 20, 480, 80);
							entities.add(block1);
					}
				}
				
				// add player
				player.setPos(playerX, playerY); //= new Player(this, playerX, playerY);
				entities.add(player);
				
				// add spider
				Entity spider = new Spider(this, 300, 430);
				entities.add(spider);
				Entity spider2 = new Spider(this, 500, 430);
				entities.add(spider2);
				Entity spider3 = new Spider(this, 680, 430);
				entities.add(spider3);
				
				
				// Add EnemyBouncers
				Entity b1 = new EnemyBouncer(20, 470, false);
				Entity b2 = new EnemyBouncer(780, 470, true);
				entities.add(b1);
				entities.add(b2);
				
				// Screen Changers
				Entity screenChangerBack = new ScreenChanger(this, -100, 70, 3, 850, 350);
				entities.add(screenChangerBack);
				Entity screenChanger = new ScreenChanger(this, 850, 380, 5, -100, 150);
				entities.add(screenChanger);
				Entity screenChanger2 = new ScreenChanger(this, -50, 380, -50, 850, 350);
				entities.add(screenChanger2);
				
				break;
			}
			case -50 : { 
				// background
				Entity background = new Background("sprites/backgrounds/background1.png", this, 0, 0);
				entities.add(background);
				
				// block test
				for (int i = 0; i < 2; i++) {
					for (int j = 0; j < 1; j++) {
						Entity block = new Block("sprites/platforms/short.png", 500 + i*200, 500 + j*90, 7, 20, 200, 80);
						entities.add(block);
					}
				}
				
				// add player
				player.setPos(playerX, playerY);
				entities.add(player);
				
				// Screen Changer
				Entity screenChanger = new ScreenChanger(this, 850, 380, 4, -100, 350);
				entities.add(screenChanger);
				
				break;
			}
			case 5 : { 
				// background
				Entity background = new Background("sprites/backgrounds/background1.png", this, 0, 0);
				entities.add(background);
				
				// block test
				for (int i = 0; i < 2; i++) {
					for (int j = 0; j < 1; j++) {
						Entity block = new Block("sprites/platforms/long.png", -100 + i*480, 250 + j*90, 10, 20, 480, 80);
						entities.add(block);
					}
				}
				
				// add player
				player.setPos(playerX, playerY);
				entities.add(player);
				
				// add spider
				Entity spider = new Spider(this, 750, 150);
				entities.add(spider);
				
				// Add EnemyBouncers
				Entity b1 = new EnemyBouncer(20, 250, false);
				Entity b2 = new EnemyBouncer(780, 250, true);
				entities.add(b1);
				entities.add(b2);
				
				// Screen Changer
				Entity screenChanger = new ScreenChanger(this, -100, 180, 4, 850, 350);
				entities.add(screenChanger);
				Entity screenChanger2 = new ScreenChanger(this, 850, 180, 6, -50, 150);
				entities.add(screenChanger2);
				
				break;
			}
			case 6 : { 
				// background
				Entity background = new Background("sprites/backgrounds/thanks.png", this, 0, 0);
				entities.add(background);
				
				// block test
				for (int i = 0; i < 3; i++) {
					for (int j = 0; j < 1; j++) {
						Entity block = new Block("sprites/platforms/short.png", -100 + i*200, 250 + j*90, 10, 20, 200, 80);
						entities.add(block);
					}
				}
				
				// add player
				player.setPos(playerX, playerY);
				entities.add(player);
				
				// Screen Changer
				Entity screenChanger = new ScreenChanger(this, -100, 180, 5, 850, 150);
				entities.add(screenChanger);
				
				break;
			}
		}
		currentScreen = screen;
	}
	
	/**
	 * Notification from a game entity that the logic of the game
	 * should be run at the next opportunity (normally as a result of some
	 * game event)
	 */
	public void updateLogic() {
		logicRequiredThisLoop = true;
	}
	
	/**
	 * Remove an entity from the game. The entity removed will
	 * no longer move or be drawn.
	 * 
	 * @param entity The entity that should be removed
	 */
	public void removeEntity(Entity entity) {
		removeList.add(entity);
	}
	
	/**
	 * Notification that the player has died. 
	 */
	public void notifyDeath() {
		message = youLose;
		waitingForKeyPress = true;
	}
	
	/**
	 * Notification that the player has won
	 */
	public void notifyWin() {
		message = youWin;
		waitingForKeyPress = true;
	}
	
	/**
	 * Notification that a frame is being rendered. Responsible for
	 * running game logic and rendering the scene.
	 */
	public void frameRendering() {		
		//SystemTimer.sleep(lastLoopTime+10-SystemTimer.getTime());
//		try {
//			long t = lastLoopTime+10-SystemTimer.getTime();
//			if (t >= 0)
//				Thread.sleep(t);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}
		
		long delta = System.currentTimeMillis() - lastLoopTime;
		if (loading) {
			delta = 0;
			loading = false;
		}
		lastLoopTime = System.currentTimeMillis();
		lastFpsTime += delta;
		fps++;
		
		// update our FPS counter if a second has passed
		if (lastFpsTime >= 1000) {
			window.setTitle(windowTitle+" (FPS: "+fps+")");
			lastFpsTime = 0;
			fps = 0;
		}
		
//		// resolve the movemfent of the ship. First assume the ship 
//		// isn't moving. If either cursor key is pressed then
//		// update the movement appropraitely
//		ship.setHorizontalMovement(0);
//		ship.setVerticalMovement(0);
		
		boolean leftPressed = window.isKeyPressed(KeyEvent.VK_LEFT);
		boolean rightPressed = window.isKeyPressed(KeyEvent.VK_RIGHT);
		boolean spacePressed = window.isKeyPressed(KeyEvent.VK_SPACE);
		boolean upPressed = window.isKeyPressed(KeyEvent.VK_UP);
		boolean downPressed = window.isKeyPressed(KeyEvent.VK_DOWN);
		
		boolean ePressed = window.isKeyPressed(KeyEvent.VK_E);
		boolean rPressed = window.isKeyPressed(KeyEvent.VK_R);
		
		Player p = ((Player) player);
		
		// TODO KEYBOARD
		
		if (!waitingForKeyPress) {
			if ((leftPressed) && (!rightPressed)) {
				player.setHorizontalMovement(-moveSpeed);
			} else if ((rightPressed) && (!leftPressed)) {
				player.setHorizontalMovement(moveSpeed);
			}
			
			if ((upPressed) && (!downPressed)) {
//				if (player.c)
				// Determine if player is on the ground to jump off
				boolean onGround = false;
				for (int i = 0; i < entities.size(); i++) {
					if (entities.get(i) instanceof Block) {
						Rectangle other = new Rectangle();
						Entity e = (Entity) entities.get(i);
						if (!((Entity) entities.get(i)).isCustomSize()) {
							other.setBounds((int) e.x,(int) e.y, e.sprite.getWidth(), e.sprite.getHeight());
						} else {
							other.setBounds((int) e.rect.x, (int) e.rect.y, e.rect.width, e.rect.height);
						}
						if (((Player) player).jumpSquare.intersects(other)) {
							onGround = true;
						} else {
//							System.out.println("Game - DO DICE");
						}
					}
				}
				if (onGround) {
					player.setVerticalMovement(-jumpSpeed);
				}
			} else if ((!upPressed) && (downPressed)) {
//				player.setVerticalMovement(-10);
			}
			
			// If not mind controlling
			if (!p.mindControlling) {
				
				if (spacePressed && player.isStill()) {
					
//					boolean enemyExists = false;
//					for (int i = 0; i < entities.size(); i++) {
//						if (entities.get(i) instanceof Enemy) {
//							enemyExists = true;
//							break;
//						}
//					}
					Entity nearestEnemy = findNearestEnemy(p.x, p.y);
					
					p.initiatingMindControl = true;
					
					if (nearestEnemy != null) {
						countMindTime += delta;
						countMindParticleTime += delta;
						
						if (countMindParticleTime > timeToMindParticle) {
							pm.createMindWarpParticle((int) p.x,(int)  p.y, 
									(int) nearestEnemy.x, (int) nearestEnemy.y);
							countMindParticleTime = 0;
						}
						
						if (countMindTime > timeToMindControl) {
							p.justMindControlled = true;
							p.mindControlling = true;
							countMindTime = 0;
							countMindParticleTime = 0;
						}
					}
				}
				if (!spacePressed || !player.isStill()) {
					countMindTime = 0;
					((Player) player).initiatingMindControl = false;
				}
			} else { // Mind controlling controls
				
				if (spacePressed) {
					
					countExplodeTime += delta;
					countMindParticleTime += delta * 2;
					p.initiatingExplode = true;
					
					if (countMindParticleTime > timeToMindParticle) {
						pm.createMindWarpParticle((int) p.x,(int)  p.y, (int) p.x + r.nextInt(100) - 50,
								(int) p.y + r.nextInt(100) - 50);
						countMindParticleTime = 0;
					}
					
					if (countExplodeTime > timeToExplode) {
						// Create exploding enemy at player pos
//						Entity[] chunks = new Entity[5];
						for (int i = 0; i < 5; i++) {
							Entity chunk = new Chunk("sprites/chunks/c1.png", this, (int) p.x, (int) p.y);
							entities.add(chunk);
						}
						// Return player to shellPlayer position
						((Player) player).x = shellPlayer.x;
						((Player) player).y = shellPlayer.y;
						// Remove shellPlayer
						removeEntity(shellPlayer);
						// Change player back to human
						p.mindControlling = false;
						p.lastFrameChange = 99999; // Make sure frame switch happens
						countExplodeTime = 0;
					}
				}
				if (!spacePressed) {
					countExplodeTime = 0;
					((Player) player).initiatingExplode = false;
				}
			}
			
			if (ePressed && player.isStill()) {
				((Player) player).attacking = true;
			} else {
				((Player) player).attacking = false;
			}
			
			if (rPressed) {
				switch (currentScreen) {
					case 1 : {
						loadScreen(1, 200, 350);
						break;
					}
					case 2 : {
						loadScreen(2, 50, 350);
						break;
					}
					case 3 : {
						loadScreen(3, 50, 350);
						break;
					}
					case 4 : {
						loadScreen(4, 50, 30);
						break;
					}
					case 5 : {
						loadScreen(5, 50, 130);
						break;
					}
				}
			}
			
		} else { // Waiting for key
//			if (!spacePressed) {
//				spaceHasBeenReleased = true;
//			}
			if ((spacePressed)) { //  && (spaceHasBeenReleased)
				waitingForKeyPress = false;
				System.out.println("New game");
				startGame();
			}
		}
		
		// if escape has been pressed, stop the game
		if (window.isKeyPressed(KeyEvent.VK_ESCAPE)) {
			System.exit(0);
		}
		
		// cycle round asking each entity to move itself
		if (!waitingForKeyPress) {
			for (int i=0;i<entities.size();i++) {
				Entity entity = (Entity) entities.get(i);
				
				entity.move(delta);
			}
		}
		
		// brute force collisions, compare every entity against
		// every other entity. If any of them collide notify 
		// both entities that the collision has occured
		for (int p1=0;p1<entities.size();p1++) { // TODO COLLISION DETECTION
			for (int s=p1+1;s<entities.size();s++) {
				Entity me = (Entity) entities.get(p1);
				Entity him = (Entity) entities.get(s);
				
				// Don't check non-solids
				if (me.solid == false || him.solid == false)
					continue;
				
				// Don't check for still objects against still objects
				if (me instanceof Block && him instanceof Block)
					continue;
				
				if (me.collidesWith(him)) {
					me.collidedWith(him);
					him.collidedWith(me);
				}
			}
		}
		
		// cycle round drawing all the entities we have in the game
		for (int i=0;i<entities.size();i++) {
			Entity entity = (Entity) entities.get(i);
			
			entity.draw();
		}
		
		// if we're waiting for an "any key" press then draw the 
		// current message 
		if (waitingForKeyPress) {
			message.draw(425, 300);
		}
		
		// remove any entity that has been marked for clear up
		entities.removeAll(removeList);
		removeList.clear();

		// if a game event has indicated that game logic should
		// be resolved, cycle round every entity requesting that
		// their personal logic should be considered.
//		if (logicRequiredThisLoop) {
//			for (int i=0;i<entities.size();i++) {
//				Entity entity = (Entity) entities.get(i);
//				entity.doLogic();
//			}
//			
//			logicRequiredThisLoop = false;
//		}
		
	}

	/**
	 * Notifcation that the game window has been closed
	 */
	public void windowClosed() {
		System.exit(0);
	}
	
	/**
	 * The entry point into the game. We'll simply create an
	 * instance of class which will start the display and game
	 * loop.
	 * 
	 * @param argv The arguments that are passed into our game
	 */
	public static void main(String argv[]) {

		Game g = new Game();
		g.startRendering();
		
	}
}
