package edu.wisc.cs.games10.chrononaut;

import processing.core.*;
//import processing.opengl.*; 
import java.util.ArrayList;


@SuppressWarnings("serial")
public class Platformer extends PApplet {
	Camera camera;
	Player player;
	Physics physics;
	ArrayList<BodyPart> availableBodyParts = new ArrayList<BodyPart>();

	// === level loading related variables ===
	int currentLevel;
	Level level;
	Level splashLevel;
	String[] levelLocations = {"forest", "03", "cave"};
	// === end level loading ===
	
	// === splash screen related variables ===
	boolean isSplashLevel = true;
	float timeToWaitBeforeAllowingInput = 100;
	float timeOfLastLevelLoad;
	boolean isChoosingLevel;
	boolean isChoosingBodyParts;
	// === end splash screen ===
	
	PImage backpanel;
	PImage batteryLeft;
	PImage batteryRight;
	PImage ASbackpanel;
	PImage shellImage;
	PImage hurtFeedbackImage;
	PImage hurtMoreFeedbackImage;
	
	PFont font;
	boolean isDrawingFramerate;
	boolean isDrawingTileCoordinates;

//	map creation parameters
	float maplength;
	int mapwidth;
	int mapwinwidth;
	int tilesize;
	int tilewidth;
	int tileheight;
	
	int playableWidth;
	int playableHeight;

	float previoustime;

// timeshifting 'states'
	int timepeak = 0;	// -1 is for looking behind
						//  0 is for normal
						//  1 is for looking ahead


	public void setup() {
		size(640, 480, OPENGL);
		colorMode(RGB,255,255,255,100);
		noStroke();

		playableWidth = 640;
		playableHeight = 400;
		
		// create map parameters
		maplength = 3.0f;
		tilesize = 32;
		tilewidth = PApplet.parseInt(maplength * ceil(playableWidth / tilesize));
		tileheight = PApplet.parseInt(ceil(playableHeight / tilesize)) * 2;
		mapwidth = tilesize * tilewidth;
		mapwinwidth = mapwidth - 2 * tilesize;

		//ui stuff
		backpanel = loadImage("ui/backpanel.png");
		batteryLeft = loadImage("ui/batteryLeft.png");
		batteryRight = loadImage("ui/batteryRight.png");
		
		ASbackpanel = loadImage("ui/abilityselect/backpanel.png");
		shellImage = loadImage("player/tail/shell.png");
		
		hurtFeedbackImage = loadImage("effects/damaged.png");
		hurtMoreFeedbackImage = loadImage("effects/damaged2.png");
		
		// setup gameplay parameters
		level = null;
		camera = new Camera(playableWidth, playableHeight, tilesize);
		
		player = new Player(this);
		physics = new Physics(player, level);
		font = loadFont("fonts/VeraBd.vlw");
		splashLevel = new SplashLevel(this);

		frameRate(30);
		smooth();
		
		// time after loading everything
		previoustime = millis();
	}

	public void draw() {
		// load level on the fly
		boolean didLoadLevel = level == null;
		
		if (didLoadLevel) {
			if (isSplashLevel) {
				level = splashLevel;
			} else {
				level = new SquareLevel(levelLocations[currentLevel], this);
			}

			// update level pointers
			if(isSplashLevel) camera.setImages(level, this);
			else camera.setSquares((SquareLevel)level, this);
			physics.setLevel(level);
			
			// reset player / enemy state
			level.reloadLevel(this);
			player.resetFromLevel(level);
			
			previoustime = millis();
		}
		
		// get time since last frame, for updating animation/physics/etc
		float currenttime = millis();
		float frametime = currenttime - previoustime;
		previoustime = currenttime;
		
		// make sure the frametime is never too big
		// (to prevent falling through floors)
		if (frametime > 100) {
			frametime = 100;
		}

		camera.renderBackground(this, player.getCurrentEra());
		if(isSplashLevel) camera.renderForeground(this, player.getCurrentEra(), timepeak);
		
		level.update(frametime, player, player.getCurrentEra());
		physics.update(frametime, player.getCurrentEra(), camera);
		player.update(frametime);
		
		// update camera based on new hero position
		camera.center(level, player.loc);
		
		pushMatrix();
		scale(tilesize);
		translate(-camera.leftx, -camera.topy);

		if(!isSplashLevel) camera.renderForeground2(this, player.getCurrentEra(), timepeak, level);
		
		level.renderEnemies(this);
		level.renderCreatures(this, player.getCurrentEra());
		level.renderBullets(this, player.getCurrentEra());
		
		// draw player and associated objects
		if (player.isTasing) {
			player.taser.render(this);
		}
		player.render(this);
		if(player.isTentacleAttacking) {
			player.tentacleArms.render(this);
		}
		if (player.isExtracting) {
			player.extractor.render(this, currenttime);
		}
		
		if(player.equippedTail.turtleShell)
		{
			player.turtleShell.render(this);
		}
		
		// draw grid showing tile locations
		if (isDrawingTileCoordinates) {
			camera.renderGrid(this);
		}
		
		popMatrix();
		
		// draw frame-rate
		if (isDrawingFramerate) {
			fill(0);
			textFont(font);
			text(Float.toString(1000 / frametime), 0, 16);
		}
		
		// draw tile coordinates under mouse (for making maps)
		if (isDrawingTileCoordinates) {
			fill(150, 0, 0);
			textFont(font);
			text(Integer.toString((int) Math.floor(mouseX / tilesize + camera.leftx)) + ", " +
					Integer.toString((int) Math.floor(mouseY / tilesize + camera.topy)), mouseX, mouseY);
		}
		
		drawUI();		
		if(player.selectAbilities) drawAbilitySelectorUI();
		drawEffects();
		
		// did we just finish loading a level?
		// don't use any input that was queued during this time
		if (didLoadLevel) {
			timeOfLastLevelLoad = millis();
		}
		
		// should we switch levels next frame?
		int nextLevel = level.isLevelFinished(player);
		if (nextLevel >= 0) {
        	isSplashLevel = !isSplashLevel;
        	if (!isSplashLevel) {
        		currentLevel = nextLevel % levelLocations.length;
        	}
        	level = null;
		}
		
		if(player.livesLeft == 0)
		{
			isSplashLevel = true;
			player.livesLeft = 3;
			player.resetBodyParts();
			level = null;
		}
	}
	
	private void drawEffects() {
		PImage overlayImage = null;
		
		// hurt feedback
		if (player.amountHasBeenHurt == 1) {
			overlayImage = hurtFeedbackImage;
		} else if (player.amountHasBeenHurt == 2) {
			overlayImage = hurtMoreFeedbackImage;
		}
		
		if (overlayImage != null) {
			beginShape(PConstants.QUADS);
				texture(overlayImage);
				vertex(0, 0, 0, 0);
				vertex(width, 0, overlayImage.width, 0);
				vertex(width, height, overlayImage.width, overlayImage.height);
				vertex(0, height, 0, overlayImage.height);
			endShape();
		}
	}
	
	private void drawAbilitySelectorUI() {
		image(ASbackpanel, 70, 50);
		
		/*draw available body parts*/
		imageMode(CENTER);
		
		ArrayList<Arm> arms = player.earnedArms;
		for(int i=0; i<arms.size(); i++) {
			Arm a = arms.get(i);
			if(player.equippedArm == a) {
				noFill();
				stroke(0);
				rect(175+(40*i) - 20, 120, 40, 65);
				noStroke();
			}
			image(a.image, 175 + (40*i), 140);
		}
		
		ArrayList<Leg> legs = player.earnedLegs;
		for(int i=0; i<legs.size(); i++) {
			Leg l = legs.get(i);
			if(player.equippedLeg == l) {
				noFill();
				stroke(0);
				rect(175+(40*i) - 20, 200, 40, 60);
				noStroke();
			}
			image(l.image, 175 + (40*i), 205);
		}
		
		ArrayList<Tail> tails = player.earnedTails;
		for(int i=0; i<tails.size(); i++) {
			Tail t = tails.get(i);
			if(player.equippedTail == t) {
				noFill();
				stroke(0);
				rect(175+(40*i) - 20, 275, 40, 50);
				noStroke();
			}
			
			PImage tailImage = t.image;
			if (tailImage == null)
				tailImage = shellImage;
			image(tailImage, 190 + (40*i), 265);
		}
		imageMode(CORNER);
		
	}
	
	private void drawUI()
	{
		this.image(backpanel, 0, 400);
		
		fill(0);
		textFont(font);
		text("Lives:", 20, 425);
		
		for(int i = 0; i < player.getLives(); i++)
		{
			int j = 20+(i*40);
			this.image(batteryLeft, j, 430);
			if(i != player.getLives() - 1 || 
					(i == player.getLives() - 1 &&  !player.getHalfHealth()) )
				this.image(batteryRight, j+20, 430);
		}
		
		fill(0);
		textFont(font);
		text("Current period: ", 260, 430);
		String currPeriod = "";
		Era currEra = player.getCurrentEra();
		if(currEra == Era.PAST)
		{
			currPeriod = "Past";
		}
		else if(currEra == Era.PRESENT)
		{
			currPeriod = "Present";
		}
		else if(currEra == Era.FUTURE)
		{
			currPeriod = "Future";
		}
		
		text(currPeriod, 400, 430);
		
		text("Creatures remaining: ", 260, 460);
		String creaturesLeft = "";
		creaturesLeft += level.creatures.size() + "/" + level.originalNumCreatures;
		text(creaturesLeft, 450, 460);
		
	}
	

//	Add a new boid into the System
	public void mousePressed() {
		if(player.selectAbilities) {
			/* test for which one they click */
			if(mouseY < 185 && mouseY > 120) {
				if(mouseX >155 && mouseX<195 && player.earnedArms.size() >=1) {
					/* equip the first arm */
					player.equippedArm = player.earnedArms.get(0);
				} else if(mouseX >195 && mouseX<235 && player.earnedArms.size() >=2) {
					/* equip the first arm */
					player.equippedArm = player.earnedArms.get(1);
				} else if(mouseX >235 && mouseX<275 && player.earnedArms.size() >=3) {
					/* equip the first arm */
					player.equippedArm = player.earnedArms.get(2);
				}
			} else if (mouseY < 260 && mouseY > 170) {
				if(mouseX >155 && mouseX<195 && player.earnedLegs.size() >=1) {
					/* equip the first arm */
					player.equippedLeg = player.earnedLegs.get(0);
				} else if(mouseX >195 && mouseX<235 && player.earnedLegs.size() >=2) {
					/* equip the first arm */
					player.equippedLeg = player.earnedLegs.get(1);
				} else if(mouseX >235 && mouseX<275 && player.earnedLegs.size() >=3) {
					/* equip the first arm */
					player.equippedLeg = player.earnedLegs.get(2);
				}
			} else if(mouseY < 315 && mouseY > 275) {
				if(mouseX >155 && mouseX<195 && player.earnedTails.size() >=1) {
					/* equip the first arm */
					player.equippedTail = player.earnedTails.get(0);
				} else if(mouseX >195 && mouseX<235 && player.earnedTails.size() >=2) {
					/* equip the first arm */
					player.equippedTail = player.earnedTails.get(1);
				} else if(mouseX >235 && mouseX<275 && player.earnedTails.size() >=3) {
					/* equip the first arm */
					player.equippedTail = player.earnedTails.get(2);
				}
			}
		}
	}

//	move attractor around
	public void mouseDragged() {

	}

//	remove attractor when mouse released
	public void mouseReleased() {

	}

	public void keyPressed() {
		// don't track key presses that happen in the first
		// small amount of time after loading a level
		if (millis() - timeOfLastLevelLoad < timeToWaitBeforeAllowingInput)
			return;
		
		// move our hero around
		if (keyCode == LEFT) {
			player.move(-1);
		}
		if (keyCode == RIGHT) {
			player.move(1);
		}
		if (keyCode == UP) {
			player.jump();
		}
		if (keyCode == TAB || key == 'q' || key == 'Q') {
			player.toggleAbilitySelector();
		}
		
		switch (key) {
		case 'e':
		case 'E':
        	player.extract();
        	break;
		
		case 's':
		case 'S':
        	player.tase();
        	break;
		
		case 'l':
		case 'L':
		{
        	timepeak = 0;
        	isSplashLevel = !isSplashLevel;
        	if (!isSplashLevel) {
        		currentLevel = (currentLevel + 1) % levelLocations.length;
        	}
        	level = null;
		}
        	break;
        	
		case 'd':
		case 'D':
        	if(player.canUseChronoPack())
        	{
	        	if(timepeak==1) {
	        		timepeak--;
	        		if(level.getTile(getNextEra(player.getCurrentEra()), (int)player.getLocation().x, (int)player.getLocation().y) == Tile.EMPTY)
	        		{
	        			player.forwardInTime();
	        			player.useChronoPack();
	        		}
	        	} else if(player.getCurrentEra() != Era.FUTURE || timepeak ==-1){
	        		timepeak++;
	        	}
        	}
        	break;
        
		case 'a':
		case 'A':
        	if(player.canUseChronoPack())
        	{
	            if(timepeak==-1) {
	            	timepeak++;
	            	if(level.getTile(getPrevEra(player.getCurrentEra()), (int)player.getLocation().x, (int)player.getLocation().y) == Tile.EMPTY)
	            	{
	            		player.backwardInTime();
	            		player.useChronoPack();
	            	}
	            } else if(player.getCurrentEra() != Era.PAST || timepeak == 1){
	            	timepeak--;
	            }
        	}
        	break;
        
		case 'f':
		case 'F':
        	isDrawingFramerate = !isDrawingFramerate;
        	break;
        
		case 't':
		case 'T':
        	isDrawingTileCoordinates = !isDrawingTileCoordinates;
        	break;
        
		case 'w':
		case 'W':
        	player.useArm();
        	break;
        
		case '2':
        	player.useLeg();
        	break;
        
        case '3':
        	player.useTail();
        	break;
		}
	}

	public void keyReleased() {// move our hero around
		if (keyCode == LEFT && player.vel.x < 0) {
			player.move(0);
		}
		if (keyCode == RIGHT && player.vel.x > 0) {
			player.move(0);
		}
		
		switch (key) {
		case 'w':
		case 'W':
        	player.stopUseArm();
        	break;
        
		case '2':
        	// player.stopUseLeg();
        	break;
        
        case '3':
        	// player.stopUseTail();
        	break;
		}
	}
	
	public Era getNextEra(Era _era)
	{
		Era era;
		if(_era == Era.PAST)
		{
			era = Era.PRESENT;
		}
		else if (_era == Era.PRESENT)
		{
			era = Era.FUTURE;
		}
		else
		{
			era = Era.PAST;
		}
		
		return era;
	}
	
	public Era getPrevEra(Era _era)
	{
		Era era;
		
		if(_era == Era.PAST)
		{
			era = Era.FUTURE;
		}
		else if (_era == Era.PRESENT)
		{
			era = Era.PAST;
		}
		else
		{
			era = Era.PRESENT;
		}
		
		return era;
	}
	
	static public void main(String args[]) {
		PApplet.main(new String[] { "--bgcolor=#DFDFDF", "edu.wisc.cs.games10.chrononaut.Platformer" });
	}
}
