import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.HashMap;

import javax.imageio.ImageIO;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

public class Animal {
	Object lock = new Object();
	int x;
	int y;
	int dx;
	int dy;
	int ground;
	HashMap<Integer, Boolean> keyMap = new HashMap<Integer, Boolean>();

	// KeyCode constants
	int UP;
	int DOWN;
	int LEFT;
	int RIGHT;
	int PUNCH;
	int LOW_KICK;
	int HIGH_KICK;

	Point[] centerOffset;
	int[] radius;

	Point punchOffset;
	int punchRadius;

	Point lowKickOffset;
	int lowKickRadius;

	Point highKickOffset;
	int highKickRadius;

	byte currentAction;

	int shield = 100;

	boolean facingLeft;

	AudioInputStream audioIn; 
	Clip clip = null;

	final static byte STANDING = 1;
	final static byte WALKING_RIGHT = 2;
	final static byte WALKING_LEFT = 3;
	final static byte JUMPING_UP = 4;
	final static byte JUMPING_RIGHT = 5;
	final static byte JUMPING_LEFT = 6;
	final static byte PUNCHING = 7;
	final static byte KICKING = 8;
	final static byte FALLING_DOWN = 9;
	final static byte GETTING_UP = 10;
	final static byte THROWING_PROJECTILE = 11;
	final static byte LOW_KICKING = 12;
	final static byte HIGH_KICKING = 13;


	BufferedImage animalBufferedImage = null;
	byte frame_i = 0;
	long animationStartTime = 0;
	long frameYTranslationStartTime = 0;
	long frameXTranslationStartTime = 0;

	// walking
	long walkingFrameDurationMillis = 50;
	final static byte walkingFrames_n = 8;
	BufferedImage[] animationWalkingFrames = null;

	// jumping
	long jumpingFrameDurationMillis = 50;
	final static byte jumpingFrames_n = 10;
	BufferedImage[] animationJumpingFrames = null;

	// standing
	long standingFrameDurationMillis = 100;
	final static byte standingFrames_n = 4;
	BufferedImage[] animationStandingFrames = null;

	// falling down
	long fallingDownFrameDurationMillis = 50;
	final static byte fallingDownFrames_n = 9;
	BufferedImage[] animationFallingDownFrames = null;

	// throwing projectile
	long throwingProjectileFrameDurationMillis = 50;
	final static byte throwingProjectileFrames_n = 7;
	BufferedImage[] animationThrowingProjectileFrames = null;

	// punching
	long punchingFrameDurationMillis = 25;
	final static byte punchingFrames_n = 6;
	BufferedImage[] animationPunchingFrames = null;

	// low kicking 
	long lowKickingFrameDurationMillis = 25;
	final static byte lowKickingFrames_n = 6;
	BufferedImage[] animationLowKickingFrames = null;

	// high kicking 
	long highKickingFrameDurationMillis = 25;
	final static byte highKickingFrames_n = 6;
	BufferedImage[] animationHighKickingFrames = null;

	static String SPRITE_SHEET_FILENAME = null;
	static String SOUND_FILENAME = null;

	BufferedImage animalSheetBufferedImage;

	public Animal (int x, int y, String sprite_sheet_filename, String sound_filename, boolean facingLeft) {
		dx = 5;
		dy = 5;
		this.x = x;
		this.y = y;
		ground = y;

		SPRITE_SHEET_FILENAME = sprite_sheet_filename;
		SOUND_FILENAME = sound_filename;

		Image animalSheet = null;
		try {
			animalSheet = ImageIO.read(new File(SPRITE_SHEET_FILENAME));
		} 
		catch (IOException e) {
			e.printStackTrace();
		}

		animalSheetBufferedImage = (BufferedImage)animalSheet;

		this.facingLeft = facingLeft;

		if (facingLeft) {
			UP = KeyEvent.VK_UP;
			LEFT = KeyEvent.VK_LEFT;
			DOWN = KeyEvent.VK_DOWN;
			RIGHT = KeyEvent.VK_RIGHT;
			PUNCH = KeyEvent.VK_SEMICOLON;
			LOW_KICK = KeyEvent.VK_PERIOD;
			HIGH_KICK = KeyEvent.VK_O;
		}
		else {
			UP = KeyEvent.VK_W;
			LEFT = KeyEvent.VK_A;
			DOWN = KeyEvent.VK_S;
			RIGHT = KeyEvent.VK_D;
			PUNCH = KeyEvent.VK_F;
			LOW_KICK = KeyEvent.VK_B;
			HIGH_KICK = KeyEvent.VK_T;
		}

		keyMap.put(UP, false);
		keyMap.put(LEFT, false);
		keyMap.put(DOWN, false);
		keyMap.put(RIGHT, false);
		keyMap.put(PUNCH, false);
		keyMap.put(LOW_KICK, false);
		keyMap.put(HIGH_KICK, false);

		currentAction = STANDING;
		if (AnimalTestG.AUDIO) {
			File f = new File(SOUND_FILENAME);
			try {
				audioIn = AudioSystem.getAudioInputStream(f.toURI().toURL());  
				clip = AudioSystem.getClip();
				clip.open(audioIn);
			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (UnsupportedAudioFileException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (LineUnavailableException e) {
				e.printStackTrace();
			}
		}
	}

	public void speak() {
		if (AnimalTestG.AUDIO) playSound();
	}

	void playSound() {   
		clip.start();
		clip.setFramePosition(0);
	}

	public void move() {
		if (keyMap.get(LEFT)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = WALKING_LEFT;
				frame_i = 0;
			}
		}		
		if (keyMap.get(RIGHT)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = WALKING_RIGHT;
				frame_i = 0;
			}
		}
		if (keyMap.get(UP)) {
			if (currentAction == STANDING) {
				speak();
				animationStartTime = System.currentTimeMillis();
				currentAction = JUMPING_UP;
				frame_i = 0;
			}
			else if (currentAction == WALKING_RIGHT) {
				speak();
				animationStartTime = System.currentTimeMillis();
				currentAction = JUMPING_RIGHT;
				if (!facingLeft) frame_i = 0; else frame_i = jumpingFrames_n - 1;
			}
			else if (currentAction == WALKING_LEFT) {
				speak();
				animationStartTime = System.currentTimeMillis();
				currentAction = JUMPING_LEFT;
				if (facingLeft) frame_i = 0; else frame_i = jumpingFrames_n - 1;
			}
		}
		if (keyMap.get(DOWN)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = THROWING_PROJECTILE;
				frame_i = 0;
			}
		}

		if (keyMap.get(PUNCH)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = PUNCHING;
				frame_i = 0;
			}
		}

		if (keyMap.get(LOW_KICK)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = LOW_KICKING;
				frame_i = 0;
			}
		}
		if (keyMap.get(HIGH_KICK)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = HIGH_KICKING;
				frame_i = 0;
			}
		}
		if (currentAction == WALKING_RIGHT) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > walkingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
				if (frame_i >= walkingFrames_n) frame_i = 0;
				animalBufferedImage = animationWalkingFrames[frame_i];
			}
			x += dx;
		}

		if (currentAction == STANDING) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > standingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
			}
			if (frame_i >= standingFrames_n) frame_i = 0;
			animalBufferedImage = animationStandingFrames[frame_i];
		}

		if (currentAction == PUNCHING) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > punchingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
			}
			if (frame_i >= punchingFrames_n) {
				frame_i = 0;
				currentAction = STANDING;
			}
			animalBufferedImage = animationPunchingFrames[frame_i];

			if (frame_i == 4 || frame_i == 5) {
				Point center = new Point(x + (int)punchOffset.x, y + (int)punchOffset.y);
				// Check for collision with targets of other player
				if (AnimalTestG.player1 == this) {
					for (int i = 0; i < AnimalTestG.player2.centerOffset.length; i++) {
						Point targetCenter = new Point(AnimalTestG.player2.x + AnimalTestG.player2.centerOffset[i].x, AnimalTestG.player2.y + AnimalTestG.player2.centerOffset[i].y);
						if (distance(center, targetCenter) <= punchRadius + AnimalTestG.player2.radius[i]) {
							AnimalTestG.player2.shield -= 10;
							AnimalTestG.player2.currentAction = FALLING_DOWN;
						}
					}
				}
				else {
					for (int i = 0; i < AnimalTestG.player1.centerOffset.length; i++) {
						Point targetCenter = new Point(AnimalTestG.player1.x + AnimalTestG.player1.centerOffset[i].x, AnimalTestG.player1.y + AnimalTestG.player1.centerOffset[i].y);
						if (distance(center, targetCenter) <= punchRadius + AnimalTestG.player1.radius[i]) {
							AnimalTestG.player1.shield -= 10;
							AnimalTestG.player1.currentAction = FALLING_DOWN;
						}
					}
				}
			}
		}

		if (currentAction == LOW_KICKING) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > lowKickingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
			}
			if (frame_i >= lowKickingFrames_n) {
				frame_i = 0;
				currentAction = STANDING;
			}
			animalBufferedImage = animationLowKickingFrames[frame_i];
			if (frame_i == 2 || frame_i == 3) {
				Point center = new Point(x + (int)lowKickOffset.x, y + (int)lowKickOffset.y);
				// Check for collision with targets of other player
				if (AnimalTestG.player1 == this) {
					for (int i = 0; i < AnimalTestG.player2.centerOffset.length; i++) {
						Point targetCenter = new Point(AnimalTestG.player2.x + AnimalTestG.player2.centerOffset[i].x, AnimalTestG.player2.y + AnimalTestG.player2.centerOffset[i].y);
						if (distance(center, targetCenter) <= lowKickRadius + AnimalTestG.player2.radius[i]) {
							AnimalTestG.player2.shield -= 10;
							AnimalTestG.player2.currentAction = FALLING_DOWN;
						}
					}
				}
				else {
					for (int i = 0; i < AnimalTestG.player1.centerOffset.length; i++) {
						Point targetCenter = new Point(AnimalTestG.player1.x + AnimalTestG.player1.centerOffset[i].x, AnimalTestG.player1.y + AnimalTestG.player1.centerOffset[i].y);
						if (distance(center, targetCenter) <= lowKickRadius + AnimalTestG.player1.radius[i]) {
							AnimalTestG.player1.shield -= 10;
							AnimalTestG.player1.currentAction = FALLING_DOWN;
						}
					}
				}
			}
		}

		if (currentAction == HIGH_KICKING) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > highKickingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
			}
			if (frame_i >= highKickingFrames_n) {
				frame_i = 0;
				currentAction = STANDING;
			}
			animalBufferedImage = animationHighKickingFrames[frame_i];
			if (frame_i == 2 || frame_i == 3) {
				Point center = new Point(x + (int)highKickOffset.x, y + (int)highKickOffset.y);
				// Check for collision with targets of other player
				if (AnimalTestG.player1 == this) {
					for (int i = 0; i < AnimalTestG.player2.centerOffset.length; i++) {
						Point targetCenter = new Point(AnimalTestG.player2.x + AnimalTestG.player2.centerOffset[i].x, 
								AnimalTestG.player2.y + AnimalTestG.player2.centerOffset[i].y);
						if (distance(center, targetCenter) <= highKickRadius + AnimalTestG.player2.radius[i]) {
							AnimalTestG.player2.shield -= 10;
							AnimalTestG.player2.currentAction = FALLING_DOWN;
						}
					}
				}
				else {
					for (int i = 0; i < AnimalTestG.player1.centerOffset.length; i++) {
						Point targetCenter = new Point(AnimalTestG.player1.x + AnimalTestG.player1.centerOffset[i].x, 
								AnimalTestG.player1.y + AnimalTestG.player1.centerOffset[i].y);
						if (distance(center, targetCenter) <= highKickRadius + AnimalTestG.player1.radius[i]) {
							AnimalTestG.player1.shield -= 10;
							AnimalTestG.player1.currentAction = FALLING_DOWN;
						}
					}
				}
			}
		}

		if (currentAction == WALKING_LEFT) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > walkingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
				if (frame_i >= walkingFrames_n) frame_i = 0;
				animalBufferedImage = animationWalkingFrames[frame_i];
			}
			x -= dx;

		}

		if (currentAction == THROWING_PROJECTILE) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > throwingProjectileFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;

				if (frame_i >= throwingProjectileFrames_n) {
					frame_i = 0;
					currentAction = STANDING;
					AnimalTestG.projectiles.add(new PlasmaBall(x + (facingLeft ? 16 : 48), y + 50, facingLeft));

				}
				animalBufferedImage = animationThrowingProjectileFrames[frame_i];
			}
		}

		if (currentAction == FALLING_DOWN) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > fallingDownFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;
				if (frame_i >= fallingDownFrames_n) {
					frame_i = 0;
					currentAction = STANDING;
				}
				animalBufferedImage = animationFallingDownFrames[frame_i];
			}
		}

		if (currentAction == JUMPING_UP) {
			int sign = 1;
			if (frame_i < 5) sign = -1;

			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > jumpingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i++;

				if (frame_i >= jumpingFrames_n) {
					frame_i = 0;
				}
				animalBufferedImage = animationJumpingFrames[frame_i];
			}
			if (frame_i == 0) return;

			long frameYTranslationElapsedTime = System.currentTimeMillis() - frameYTranslationStartTime;
			if (frameYTranslationElapsedTime > jumpingFrameDurationMillis / 5) {
				frameYTranslationStartTime = System.currentTimeMillis();
				y += sign * dy;
			}
			long frameXTranslationElapsedTime = System.currentTimeMillis() - frameXTranslationStartTime;
			if (frameXTranslationElapsedTime > jumpingFrameDurationMillis / 5) {
				frameXTranslationStartTime = System.currentTimeMillis();
			}
			if (y == ground) {
				currentAction = STANDING;
			}
		}

		if (currentAction == JUMPING_RIGHT) {

			int sign = 1;
			if (!facingLeft) {
				if (frame_i < 5) sign = -1;
			}
			else {
				if (frame_i >= 5) sign = -1;
			}

			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > jumpingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				if (!facingLeft) {
					frame_i++;
					if (frame_i >= jumpingFrames_n) {
						frame_i = 0;
					}
				}
				else {
					frame_i--;
					if (frame_i <= -1) {
						frame_i = jumpingFrames_n - 1;
					}
				}
				animalBufferedImage = animationJumpingFrames[frame_i];
			}
			if (!facingLeft && frame_i == 0) return;
			if (facingLeft && frame_i == jumpingFrames_n - 1) return;

			long frameYTranslationElapsedTime = System.currentTimeMillis() - frameYTranslationStartTime;
			if (frameYTranslationElapsedTime > jumpingFrameDurationMillis / 5) {
				frameYTranslationStartTime = System.currentTimeMillis();
				y += sign * dy;
			}
			long frameXTranslationElapsedTime = System.currentTimeMillis() - frameXTranslationStartTime;
			if (frameXTranslationElapsedTime > jumpingFrameDurationMillis / 5) {
				frameXTranslationStartTime = System.currentTimeMillis();
				if (currentAction == JUMPING_RIGHT) {
					x += dx;
				}
				if (currentAction == JUMPING_LEFT) {
					x -= dx;
				}
			}
			if (y == ground) {
				currentAction = STANDING;
			}
		}

		if (currentAction == JUMPING_LEFT) {

			int sign = 1;
			if (facingLeft) {
				if (frame_i < 5) sign = -1;
			}
			else {
				if (frame_i >= 5) sign = -1;
			}

			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > jumpingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				if (facingLeft) {
					frame_i++;
					if (frame_i >= jumpingFrames_n) {
						frame_i = 0;
					}
				}
				else {
					frame_i--;
					if (frame_i <= -1) {
						frame_i = jumpingFrames_n - 1;
					}
				}
				animalBufferedImage = animationJumpingFrames[frame_i];
			}
			if (facingLeft && frame_i == 0) return;
			if (!facingLeft && frame_i == jumpingFrames_n - 1) return;

			long frameYTranslationElapsedTime = System.currentTimeMillis() - frameYTranslationStartTime;
			if (frameYTranslationElapsedTime > jumpingFrameDurationMillis / 5) {
				frameYTranslationStartTime = System.currentTimeMillis();
				y += sign * dy;
			}
			long frameXTranslationElapsedTime = System.currentTimeMillis() - frameXTranslationStartTime;
			if (frameXTranslationElapsedTime > jumpingFrameDurationMillis / 5) {
				frameXTranslationStartTime = System.currentTimeMillis();
				if (currentAction == JUMPING_RIGHT) {
					x += dx;
				}
				if (currentAction == JUMPING_LEFT) {
					x -= dx;
				}
			}
			if (y == ground) {
				currentAction = STANDING;
			}
		}
		for (int i = 0; i < 3; i++) {
			Point center = new Point(x + centerOffset[i].x, y + centerOffset[i].y);
			for (Projectile p : AnimalTestG.projectiles) {
				Point pCenter = new Point(p.x + p.centerOffset.x, p.y + p.centerOffset.y);
				if (distance(center, pCenter) <= p.radius + this.radius[i]) {
					p.destroyed = true;
					this.shield -= 10;
					if (currentAction != FALLING_DOWN && currentAction != JUMPING_UP &&  currentAction != JUMPING_LEFT && currentAction != JUMPING_RIGHT) {
						currentAction = FALLING_DOWN;
						frame_i = 0;
					}
				}
			}
		}
	}

	public void keyPressed(KeyEvent evt) {
		if (evt.getKeyCode() == UP) {
			keyMap.put(UP, true);
		}
		if (evt.getKeyCode() == LEFT) {
			keyMap.put(LEFT, true);
		}
		if (evt.getKeyCode() == DOWN) {	
			keyMap.put(DOWN, true);
		}
		if (evt.getKeyCode() == RIGHT) {
			keyMap.put(RIGHT, true);
		}
		if (evt.getKeyCode() == PUNCH) {
			keyMap.put(PUNCH, true);
		}
		if (evt.getKeyCode() == LOW_KICK) {
			keyMap.put(LOW_KICK, true);
		}
		if (evt.getKeyCode() == HIGH_KICK) {
			keyMap.put(HIGH_KICK, true);
		}
	}

	public void keyReleased(KeyEvent evt) {
		if (evt.getKeyCode() == UP) {
			keyMap.put(UP, false);
		}
		if (evt.getKeyCode() == LEFT) {
			keyMap.put(LEFT, false);
			if (currentAction == WALKING_LEFT) currentAction = STANDING;
		}
		if (evt.getKeyCode() == DOWN) {
			keyMap.put(DOWN, false);
		}
		if (evt.getKeyCode() == RIGHT) {
			keyMap.put(RIGHT, false);
			if (currentAction == WALKING_RIGHT) currentAction = STANDING;
		}
		if (evt.getKeyCode() == PUNCH) {
			keyMap.put(PUNCH, false);
		}
		if (evt.getKeyCode() == LOW_KICK) {
			keyMap.put(LOW_KICK, false);
		}
		if (evt.getKeyCode() == HIGH_KICK) {
			keyMap.put(HIGH_KICK, false);
		}
	}

	public void draw(Graphics g) {
		// Use the drawImage() method of the Graphics object g, to draw the Animal.
		g.drawImage(animalBufferedImage.getScaledInstance(128, -1, Image.SCALE_DEFAULT), x, y, null);
		//		g.setColor(Color.RED);
		//		for (int i = 0; i < 3; i++) {
		//			g.drawOval(x + (int)centerOffset[i].x - radius[i], y + (int)centerOffset[i].y - radius[i], radius[i] * 2, radius[i] * 2);
		//		}
		//		if (currentAction == PUNCHING && (frame_i == 4 || frame_i == 5)) {
		//			g.drawOval(x + (int)punchOffset.x, y + (int)punchOffset.y, punchRadius, punchRadius);
		//		}
		//		if (currentAction == LOW_KICKING && (frame_i == 2 || frame_i == 3)) {
		//			g.drawOval(x + (int)lowKickOffset.x, y + (int)lowKickOffset.y, lowKickRadius, lowKickRadius);
		//		}
		//		if (currentAction == HIGH_KICKING && (frame_i == 2 || frame_i == 3)) {
		//			g.drawOval(x + (int)highKickOffset.x, y + (int)highKickOffset.y, highKickRadius, highKickRadius);
		//		}
	}

	protected BufferedImage flipImageOverVertical(BufferedImage image) {
		AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
		tx.translate(-image.getWidth(null), 0);
		AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
		BufferedImage flippedImage = op.filter(image, null);
		return flippedImage;
	}

	void drawString(Graphics g, int x, int y) {
		g.setColor(new Color(0,255,0));
		g.drawString("x: " + this.x, x, y += g.getFontMetrics().getHeight());
		g.drawString("y: " + this.y, x, y += g.getFontMetrics().getHeight());
		g.drawString("currentAction: " + currentAction, x, y += g.getFontMetrics().getHeight());
		g.drawString("shield: " + shield, x, y += g.getFontMetrics().getHeight());
	}

	int distance(Point a, Point b) {
		return (int)(Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)));
	}

	public String toString() {
		return "I'm an animal";
	}
	//
	//////////////////////////////////////
}