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;
	HashMap<Integer, Boolean> keyMap = new HashMap<Integer, Boolean>();

	// KeyCode constants
	int UP;
	int DOWN;
	int LEFT;
	int RIGHT;

	Point[] centerOffest;
	int[] radius;

	byte currentAction;

	int shield = 100;

	boolean facingLeft;

	AudioInputStream audioIn; 
	Clip clip = null;

	final static byte STANDING = 1;
	final static byte WALKING_FORWARD = 2;
	final static byte WALKING_BACKWARDS = 3;
	final static byte JUMPING_UP = 4;
	final static byte JUMPING_FORWARD = 5;
	final static byte JUMPING_BACKWARDS = 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;

	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;

	// jumping
	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;

	static String SPRITE_SHEET_FILENAME = null;
	static String SOUND_FILENAME = null;

	BufferedImage animalSheetBufferedImage;

	public Animal (int x, int y, int up, int left, int down, int right, String sprite_sheet_filename, String sound_filename) {
		dx = 5;
		dy = 5;
		this.x = x;
		this.y = 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;

		UP = up;
		LEFT = left;
		DOWN = down;
		RIGHT = right;

		keyMap.put(UP, false);
		keyMap.put(LEFT, false);
		keyMap.put(DOWN, false);
		keyMap.put(RIGHT, false);

		currentAction = STANDING;

		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() {
		playSound();
	}

	void playSound() {   
		clip.start();
		clip.setFramePosition(0);
	}

	public void move() {
		if (keyMap.get(LEFT)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = WALKING_BACKWARDS;
				frame_i = walkingFrames_n - 1;
			}
		}		
		if (keyMap.get(RIGHT)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = WALKING_FORWARD;
				frame_i = 0;
			}
		}
		if (keyMap.get(UP)) {
			if (currentAction == STANDING) {
				speak();
				animationStartTime = System.currentTimeMillis();
				currentAction = JUMPING_UP;
				frame_i = 0;
			}
			else if (currentAction == WALKING_FORWARD) {
				speak();
				animationStartTime = System.currentTimeMillis();
				currentAction = JUMPING_FORWARD;
				frame_i = 0;
			}
			else if (currentAction == WALKING_BACKWARDS) {
				speak();
				animationStartTime = System.currentTimeMillis();
				currentAction = JUMPING_BACKWARDS;
				frame_i = 0;
			}
		}
		if (keyMap.get(DOWN)) {
			if (currentAction == STANDING) {
				animationStartTime = System.currentTimeMillis();
				currentAction = THROWING_PROJECTILE;
				frame_i = 0;
			}
		}

		if (currentAction == WALKING_FORWARD) {
			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 == WALKING_BACKWARDS) {
			long animationElapsedTime = System.currentTimeMillis() - animationStartTime;
			if (animationElapsedTime > walkingFrameDurationMillis) {
				animationStartTime = System.currentTimeMillis();
				frame_i--;
			}
			if (frame_i == 0) frame_i = walkingFrames_n - 1;
			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 ||
				currentAction == JUMPING_FORWARD ||
				currentAction == JUMPING_BACKWARDS) {

			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 (currentAction == JUMPING_FORWARD) {
					x += dx;
				}
				if (currentAction == JUMPING_BACKWARDS) {
					x -= dx;
				}
			}
			if (y == 300) {
				currentAction = STANDING;
			}
		}
		for (int i = 0; i < 3; i++) {
			Point center = new Point(x + centerOffest[i].x, y + centerOffest[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;
					shield -= 10;
					if (currentAction == STANDING) {
						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);
		}
	}

	public void keyReleased(KeyEvent evt) {
		if (evt.getKeyCode() == UP) {
			keyMap.put(UP, false);
		}
		if (evt.getKeyCode() == LEFT) {
			keyMap.put(LEFT, false);
			if (currentAction == WALKING_BACKWARDS) currentAction = STANDING;
		}
		if (evt.getKeyCode() == DOWN) {
			keyMap.put(DOWN, false);
		}
		if (evt.getKeyCode() == RIGHT) {
			keyMap.put(RIGHT, false);
			if (currentAction == WALKING_FORWARD) currentAction = STANDING;
		}
	}

	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)centerOffest[i].x - radius[i], y + (int)centerOffest[i].y - radius[i], radius[i] * 2, radius[i] * 2);
		}
	}

	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.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)));
	}
	//
	//////////////////////////////////////
}