package edu.wisc.cs.games10.chrononaut;

import processing.core.*;

import java.util.*;


public class Player extends SimpleMovable {
	// === Player Constants ===
	static final int maxjumps = 1;
	static final int startingLivesLeft = 3;
	static final float maxExtractTime = 1000;
	static final float maxTaseTime = 1000;
	static final float maxTentacleTime = 2000;
	static final float chronoPackCooldownTime = 1000;
	static final float invincibilityAfterHurtTime = 1000;
	static final float hurtFeedbackTime = 500;
	
	// speeds are in tiles / millisecond
	static final float jumpspeed = 0.014f;
	static final float movespeed = 0.004f;
	static final float terminalVelocity = 0.02f;
	static final float wingTerminalVelocity = 0.0005f;
	static final float stompVelocity = 0.001f;
	
	// where to draw the image
	static final float yImageTop = -0.5f;
	static final float yImageBottom = 2.5f;
	static final float xImageLeft = -0.5f;
	static final float xImageRight = 1.5f;
	
	// === End Player Constants ===
	
	// physics properties
	Vector2D origloc;
    Era currentEra;
	
	int totaljumps;
	
	private boolean isGliding;
		
	//constant images
	PImage head;
	
	//midsection
	private PImage chronoCanUse;
	private PImage chronoCantUse;
	
	
	//change these to be what should be displayed.
	PImage midsection;
	PImage tail;
	PImage legs;
	PImage arms;
	PImage batWingUp;
	
	PImage defaultTail;
	PImage defaultLegs;
	PImage defaultArms;
	
	Tail defaultTailBP;
	Arm defaultArmsBP;
	Leg defaultLegBP;
	
	int livesLeft;
	boolean halfHealth;
	
	boolean faceRight;
	boolean selectAbilities;
	
	// "items"
	ArrayList<Arm> earnedArms = new ArrayList<Arm>();
	ArrayList<Leg> earnedLegs = new ArrayList<Leg>();
	ArrayList<Tail> earnedTails = new ArrayList<Tail>();
	Arm equippedArm;
	Leg equippedLeg;
	Tail equippedTail;
	
	float lastTimeAnimation;
	float timeSinceUsingChronoPack;
	
	boolean readyToUseChronoPack;
	
	float extractTime;
	boolean isExtracting;
	BodyPartExtractor extractor;
	
	boolean isTasing;
	float taseTime;
	EquippedItem taser;
	
	boolean isTentacleAttacking;
	float tentacleTime;
	EquippedItem tentacleArms;
	
	// properties that change after being hurt
	float invincibleTime;
	boolean isInvincible;
	float hurtTime;
	int amountHasBeenHurt; // 0=none, 1=half, 2=life
	
	EquippedItem turtleShell;
	
	PImage shellCanChrono;
	PImage shellCantChrono;
	

	/** constructs Player at location l */
	Player(PApplet canvas) {
		extractor = new BodyPartExtractor(this, canvas);

		// load images
        shellCanChrono = canvas.loadImage("player/midsection/shell01.png");
        shellCantChrono = canvas.loadImage("player/midsection/shell02.png");
		
		chronoCanUse = canvas.loadImage("player/midsection/01.png");
		chronoCantUse = canvas.loadImage("player/midsection/02.png");
		head = canvas.loadImage("player/head/01.png");
		
		defaultTail = canvas.loadImage("player/tail/01.png");
		defaultLegs = canvas.loadImage("player/legs/01.png");
		defaultArms = canvas.loadImage("player/arms/01.png");
		batWingUp = canvas.loadImage("player/arms/batwing_up.png");

        // create "equipped items" that follow Player around
		taser = new EquippedItem(this, canvas, canvas.loadImage("player/taser/test_taser.png"), new Vector2D(.2f, 1.2f));
		tentacleArms = new EquippedItem(this, canvas, canvas.loadImage("player/taser/tentacle.png"), new Vector2D(.2f, 1.2f)); //change this
		turtleShell = new EquippedItem(this, canvas, shellCanChrono, new Vector2D(.2f, 1.2f)); //change this

		halfHealth = false;
		faceRight = true;
		
//		legs = defaultLegs;
//		arms = defaultArms;
//		tail = defaultTail;
		
        midsection = chronoCanUse;
        
		tileWidth = 1;
		tileHeight = 2.5f;
	
		lastTimeAnimation = 0;
		timeSinceUsingChronoPack = 0;
		
		readyToUseChronoPack = true;
		
		livesLeft = startingLivesLeft;
        
		// create default body parts
        defaultArmsBP = new Arm("Default Arm", defaultArms);
        earnedArms.add(defaultArmsBP);
        
        defaultLegBP = new Leg("Default Leg", defaultLegs);
		earnedLegs.add(defaultLegBP);
		
		defaultTailBP = new Tail("Default Tail", defaultTail);
		earnedTails.add(defaultTailBP);
		
		equippedArm = defaultArmsBP;
		equippedLeg = defaultLegBP;
		equippedTail = defaultTailBP;

        origloc = new Vector2D(0, 0);
		die();
	}
	
	public void resetFromLevel(Level l) {
		origloc = l.getStartLocation().copy();
		currentEra = l.getStartEra();
		die();
	}
	
	public void resetBodyParts()
	{
		earnedArms.clear();
		earnedLegs.clear();
		earnedTails.clear();
		
		earnedArms.add(defaultArmsBP);
		earnedLegs.add(defaultLegBP);
		earnedTails.add(defaultTailBP);
		
		equippedArm = defaultArmsBP;
		equippedLeg = defaultLegBP;
		equippedTail = defaultTailBP;
	}

	
	public void hurt()
	{
		// didn't feel it :-P
		if (isInvincible)
			return;
		
		// lose some health
		if (halfHealth == true) {
			halfHealth = false;
			loseLife();
		} else {
			halfHealth = true;
			
			// feedback
			hurtTime = 0;
			amountHasBeenHurt = 1;
		}
		
		// invincible for a time
		isInvincible = true;
		invincibleTime = 0;
	}
	
	public void loseLife()
	{
		livesLeft--;
		
		// feedback
		hurtTime = 0;
		amountHasBeenHurt = 2;
	}
	
	public boolean getHalfHealth()
	{
		return halfHealth;
	}
	
	public int getLives()
	{
		return livesLeft;
	}
	
    public void forwardInTime()
    {
        if(currentEra == Era.PAST)
        {
            currentEra = Era.PRESENT;
        }else if(currentEra == Era.PRESENT)
        {
            currentEra = Era.FUTURE;
        }else if(currentEra == Era.FUTURE)
        {
            currentEra = Era.PAST;
        }
    }

    public void backwardInTime()
    {
        if(currentEra == Era.PAST)
        {
            currentEra = Era.FUTURE;
        }else if(currentEra == Era.PRESENT)
        {
            currentEra = Era.PAST;
        }else if(currentEra == Era.FUTURE)
        {
            currentEra = Era.PRESENT;
        }
    }

    public Era getCurrentEra()
    {
        return currentEra;
    }
    
    public void addHalfLife() {
    	if (halfHealth) {
    		halfHealth = false;
    	} else {
    		livesLeft++;
    		halfHealth = true;
    	}
    }
    
    public void addBodyPart(BodyPart bp) {
    	// gain life when adding body parts
    	addHalfLife();
    	
    	if(bp instanceof Arm) {
    		if (!earnedArms.contains(bp)) {
    			earnedArms.add((Arm)bp);
    			equippedArm = (Arm) bp;
    		}
    	} else if(bp instanceof Leg) {
    		if (!earnedLegs.contains(bp)) {
	    		earnedLegs.add((Leg)bp);
	    		equippedLeg = (Leg) bp;
    		}
    	} else if(bp instanceof Tail) {
    		if (!earnedTails.contains(bp)) {
	    		earnedTails.add((Tail)bp);
	    		equippedTail = (Tail) bp;
    		}
    	}
    }
    
    public ArrayList<Arm> getArms() {
    	return earnedArms;
    }
    
    public ArrayList<Leg> getLegs() {
    	return earnedLegs;
    }
    
    public ArrayList<Tail> getTails() {
    	return earnedTails;
    }
    
    public void setequippedLeg(int index) {
    	equippedLeg = earnedLegs.get(index);;
    }
    
    public void setequippedArm(int index) {
    	equippedArm = earnedArms.get(index);
    }
    
    public void setequippedTail(int index) {
    	equippedTail = earnedTails.get(index);
    }
    
    public Leg getequippedLeg() {
    	return equippedLeg;
    }
    
    public Arm getequippedArm() {
    	return equippedArm;
    }
    
    public Tail getequippedTail() {
    	return equippedTail;
    }


	public Vector2D getLocation() {
		return loc;
	}
	
	public Vector2D getVelocity() {
		return vel;
	}
	
	public Vector2D getAcceleration() {
		return new Vector2D(0, 0);
	}
	
	/** Tries to create BodyPartExtractor */
	public void extract() {
		if (!isExtracting) {
			isExtracting = true;
			extractTime = 0;
			extractor.move(this);
		}
	}
	
	public void tase() {
		if (!isTasing && !isTentacleAttacking) {
			isTasing = true;
			taseTime = 0;
			taser.move(this);
		}
	}
	
	/** flaps wings, raises tentacle, etc. */
	public void useArm() {
		if (equippedArm.glideArms) {
			isGliding = true;
		}
		
		if (equippedArm.tentacleArms && !isTentacleAttacking)
		{
			isTentacleAttacking = true;
			tentacleTime = 0;
			tentacleArms.move(this);
		}
	}
	
	/** stops players arms from being used. */
	public void stopUseArm() {
		isGliding = false;
	}
	
	public void useLeg()
	{
		
	}
	
	public void useTail()
	{
		
	}
	
	public void tentacleAttack() {
		if(equippedArm.tentacleArms && !isTentacleAttacking && !isTasing) {
			isTentacleAttacking = true;
			tentacleTime = 0;
			tentacleArms.move(this);
		}
	}
	
	public void toggleAbilitySelector() {
		selectAbilities = !selectAbilities;
	}

	// tells the Hero to try to jump
	public void jump() {
		if (loc.y >= 0)
		{
			if (totaljumps < maxjumps && vel.y == 0) {
				float newjumpspeed = jumpspeed;
				newjumpspeed *= equippedLeg.getJumpStrength();
				newjumpspeed *= equippedTail.getJumpStrength();
				vel.y = -newjumpspeed;
				totaljumps++;
			}
		}
	}

	// moves the Hero in a direction (0 means stop)
	public void move(float direction) {
		if(direction == -1)
			faceRight = false;
		if(direction == 1)
			faceRight = true;
		
		float newmovespeed = movespeed;
		
		newmovespeed = newmovespeed * equippedLeg.getSpeed();
		
		vel.x = direction * newmovespeed;
	}
	
	public void collisionWithEnemy(Enemy enemy) {
		if (equippedLeg.canStomp &&
				vel.y > stompVelocity && enemy.loc.y > (loc.y + tileHeight * 0.75f)) {
			// stomp the enemy
			// (must be moving down fast enough to stomp)
			// (also, should be stomping with feet, not chest or something)
			enemy.kill();
		} else if (enemy instanceof BranderBotEnemy) {
			// this feels like a horrible hack
			// but it's possibly less hacky than having a
			// permanent bullet made by the BranderBot
			if ((enemy.vel.x < 0 && (loc.x + tileWidth) < (0.1 + enemy.loc.x)) ||
				(enemy.vel.x > 0 && (loc.x + tileWidth) > (enemy.loc.x + enemy.tileWidth - 0.1))) {
				hurt();
			}
		}
	}

	// resets parameters to their original values
	public void die() {
		loc = origloc.copy();
		vel = new Vector2D(0, 0);
		totaljumps = 0;
		isGliding = false;
	}
	
	public void kill() {
		//hurt();
		loseLife();
		halfHealth = false;
		die();
	}
	
	public void useChronoPack()
	{
		timeSinceUsingChronoPack = 0;
	}
	
	public boolean canUseChronoPack()
	{
		return readyToUseChronoPack;
	}
	
	public float getTerminalVelocity() {
		if(isGliding) {
			return wingTerminalVelocity;
		} else {
			return terminalVelocity;
		}
	}

	public void update(float time) {
		// 2 second cool down
		timeSinceUsingChronoPack += time;
		if (timeSinceUsingChronoPack > chronoPackCooldownTime)
		{
			readyToUseChronoPack = true;
			midsection = chronoCanUse;
			turtleShell.imageToDraw = shellCanChrono;
		}
		else
		{
			readyToUseChronoPack = false;
			midsection = chronoCantUse; 
			turtleShell.imageToDraw = shellCantChrono;
		}
		
		// are we currently invincible?
		if (isInvincible) {
			invincibleTime += time;
			isInvincible = invincibleTime < invincibilityAfterHurtTime;
		}
		
		if (amountHasBeenHurt != 0) {
			hurtTime += time;
			if (hurtTime > hurtFeedbackTime) {
				amountHasBeenHurt = 0;
			}
		}
		
		// body part extractor can only be used every so often
		if (isExtracting) {
			extractTime += time;
			isExtracting = extractTime < maxExtractTime;
		}
		extractor.move(this);
		
		if(isTasing) {
			taseTime +=time;
			isTasing = taseTime < maxTaseTime;
			taser.playerFacingRight = faceRight;
			
		}
		
		if(isTentacleAttacking) {
			tentacleTime +=time;
			isTentacleAttacking = tentacleTime < maxTentacleTime;
			tentacleArms.playerFacingRight = faceRight;
		}
		
		if(equippedTail.turtleShell) {
			turtleShell.playerFacingRight = faceRight;
		}
		
		taser.move(this);
		tentacleArms.move(this);
		turtleShell.move(this);
		
		if(equippedTail.turtleShell) {
			turtleShell.loc.y += .15f;
			if(faceRight) 
				turtleShell.loc.x -= 1.25f;
			else
				turtleShell.loc.x += 1.25f;
		}
		

	}

	public void render(PApplet canvas) {
		//hacks!
		legs = equippedLeg.getImage();
		if (legs == null)
			legs = defaultLegs;
		
		arms = equippedArm.getImage();
		if (arms == null)
			arms = defaultArms;
		
		tail = equippedTail.getImage();
		if (tail == null)
			tail = defaultTail;

		// draw the player (arms/body/legs/etc.)
		canvas.pushMatrix();
		
			canvas.translate(loc.x, loc.y);
			/*
			canvas.beginShape(PConstants.QUADS);
				canvas.texture(lower);
				canvas.vertex(0, 0, 0, 0);
				canvas.vertex(1, 0, 32,0);
				canvas.vertex(1, 1, 32,32);
				canvas.vertex(0, 1, 0,32);
			canvas.endShape();
			*/
			
			int firstVertexTexCoord;
			int secondVertexTexCoord;
			int thirdVertexTexCoord;
			int fourthVertexTexCoord;
			
			if(faceRight)
			{
				firstVertexTexCoord = 0;
				secondVertexTexCoord = midsection.width;
				thirdVertexTexCoord = midsection.width;
				fourthVertexTexCoord = 0;
			}
			else
			{
				firstVertexTexCoord = midsection.width;
				secondVertexTexCoord = 0;
				thirdVertexTexCoord = 0;
				fourthVertexTexCoord = midsection.width;
			}
			
			canvas.beginShape(PConstants.QUADS);
				canvas.texture(head);
				canvas.vertex(xImageLeft, yImageTop, firstVertexTexCoord, 0);
				canvas.vertex(xImageRight, yImageTop, secondVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageTop+1, thirdVertexTexCoord,midsection.height);
				canvas.vertex(xImageLeft, yImageTop+1, fourthVertexTexCoord,midsection.height);
			canvas.endShape();
			
			canvas.beginShape(PConstants.QUADS);
				canvas.texture(midsection);
				canvas.vertex(xImageLeft, yImageTop+1, firstVertexTexCoord, 0);
				canvas.vertex(xImageRight, yImageTop+1, secondVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageTop+2 , thirdVertexTexCoord,midsection.height);
				canvas.vertex(xImageLeft, yImageTop+2, fourthVertexTexCoord,midsection.height);
			canvas.endShape();
			
			canvas.beginShape(PConstants.QUADS);
				canvas.texture(legs);
				canvas.vertex(xImageLeft, yImageTop, firstVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageTop, secondVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageBottom, thirdVertexTexCoord, legs.height);
				canvas.vertex(xImageLeft, yImageBottom, fourthVertexTexCoord, legs.height);
			canvas.endShape();
			
			if(!isTentacleAttacking && !isGliding) {
				canvas.beginShape(PConstants.QUADS);
					canvas.texture(arms);
					canvas.vertex(xImageLeft, yImageTop, firstVertexTexCoord,0);
					canvas.vertex(xImageRight, yImageTop, secondVertexTexCoord,0);
					canvas.vertex(xImageRight, yImageBottom, thirdVertexTexCoord, arms.height);
					canvas.vertex(xImageLeft, yImageBottom, fourthVertexTexCoord, arms.height);
				canvas.endShape();
			}
			if(isGliding) {
				canvas.beginShape(PConstants.QUADS);
				canvas.texture(batWingUp);
				canvas.vertex(xImageLeft, 0, firstVertexTexCoord,0);
				canvas.vertex(xImageRight, 0, secondVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageBottom, thirdVertexTexCoord, batWingUp.height);
				canvas.vertex(xImageLeft, yImageBottom, fourthVertexTexCoord, batWingUp.height);
			canvas.endShape();
			}
			
			canvas.beginShape(PConstants.QUADS);
				canvas.texture(tail);
				canvas.vertex(xImageLeft, yImageTop, firstVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageTop, secondVertexTexCoord,0);
				canvas.vertex(xImageRight, yImageBottom, thirdVertexTexCoord, tail.height);
				canvas.vertex(xImageLeft, yImageBottom, fourthVertexTexCoord, tail.height);
			canvas.endShape();
		
		canvas.popMatrix();
	}
}

