package dk.itu.mario.engine;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.File;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.sound.sampled.LineUnavailableException;
import javax.swing.JComponent;

import dk.itu.mario.MarioInterface.LevelGenerator;
import dk.itu.mario.engine.sonar.FakeSoundEngine;
import dk.itu.mario.engine.sonar.SonarSoundEngine;
import dk.itu.mario.engine.sprites.Mario;
import dk.itu.mario.scene.LevelScene;
import dk.itu.mario.scene.LevelSceneCustom;
import dk.itu.mario.scene.LoseScene;
import dk.itu.mario.scene.Scene;
import dk.itu.mario.scene.WinScene;

public class MarioComponent extends JComponent implements Runnable,
		KeyListener, FocusListener, MouseListener {
	
	private static final long serialVersionUID = 739318775993206607L;
	public static final int TICKS_PER_SECOND = 24;
	public static final int EVOLVE_VERSION = 4;
	public static final int GAME_VERSION = 4;

	private boolean running = false;
	private boolean screenshotTaken = false;
	private boolean useScale2x = false;
	private boolean isCustom = false;
	private int width, height;
	private GraphicsConfiguration graphicsConfiguration;
	private LevelGenerator levelGenerator;
	private Scene scene;
	private SonarSoundEngine sound;
	private Scale2x scale2x = new Scale2x(320, 240);

	public MarioComponent(int width, int height, boolean isCustomized,
			LevelGenerator levelGenerator) {
		addFocusListener(this);
		addMouseListener(this);
		addKeyListener(this);

		this.setFocusable(true);
		this.setEnabled(true);
		this.width = width;
		this.height = height;
		this.isCustom = isCustomized;

		Dimension size = new Dimension(width, height);
		setPreferredSize(size);
		setMinimumSize(size);
		setMaximumSize(size);

		try {
			sound = new SonarSoundEngine(64);
		} catch (LineUnavailableException e) {
			e.printStackTrace();
			sound = new FakeSoundEngine();
		}
		this.setFocusable(true);
		this.levelGenerator = levelGenerator;
		LevelScene.bothPlayed = false;
	}

	private void toggleKey(int keyCode, boolean isPressed) {
		if (scene == null) {
			return;
		}
		if (keyCode == KeyEvent.VK_LEFT) {
			scene.toggleKey(Mario.KEY_LEFT, isPressed);
		}
		if (keyCode == KeyEvent.VK_RIGHT) {
			scene.toggleKey(Mario.KEY_RIGHT, isPressed);
		}
		if (keyCode == KeyEvent.VK_DOWN) {
			scene.toggleKey(Mario.KEY_DOWN, isPressed);
		}
		if (keyCode == KeyEvent.VK_UP) {
			scene.toggleKey(Mario.KEY_UP, isPressed);
		}
		if (keyCode == KeyEvent.VK_A) {
			scene.toggleKey(Mario.KEY_SPEED, isPressed);
		}
		if (keyCode == KeyEvent.VK_S) {
			scene.toggleKey(Mario.KEY_JUMP, isPressed);
		}
		if (keyCode == KeyEvent.VK_ENTER) {
			scene.toggleKey(Mario.KEY_ENTER, isPressed);
		}
		if (isPressed && keyCode == KeyEvent.VK_F1) {
			useScale2x = !useScale2x;
		}
		if (keyCode == KeyEvent.VK_D) {
			if (!isPressed) {
				screenshotTaken = false;
			} else if (!screenshotTaken) {
				screenshotTaken = true;
				takeScreenShot();
			}
		}

		if (isPressed && keyCode == KeyEvent.VK_ESCAPE) {
			try {
				System.exit(1);
			} catch (Exception e) {
				System.out.println("Unable to exit.");
			}
		}
	}

	private void takeScreenShot() {
		System.out.println("Taking screenshot.");
		Point loc = getLocationOnScreen();

		Rectangle screenRect = new Rectangle(loc.x, loc.y, getWidth(), getHeight());
		
		try {
			
			Robot robot = new Robot();
			BufferedImage bufferedImg = robot.createScreenCapture(screenRect);
			File tempFile = File.createTempFile("screenshot", ".png", new File("."));
			ImageIO.write(bufferedImg, "png",
					tempFile);
			System.out.println("Screeshot saved to current working directory: " + tempFile.getName());
		} catch (Exception ex) {
			ex.printStackTrace(System.out);
		}
	}

	public void paint(Graphics g) {
		super.paint(g);
	}

	public void update(Graphics g) {
	}

	public void start() {
		if (!running) {
			running = true;
			new Thread(this, "Game Thread").start();
		}
	}

	public void stop() {
		Art.stopMusic();
		running = false;
	}

	public void run() {

		graphicsConfiguration = getGraphicsConfiguration();

		Art.init(graphicsConfiguration, sound);

		VolatileImage image = createVolatileImage(320, 240);
		Graphics g = getGraphics();
		Graphics og = image.getGraphics();

		int lastTick = -1;

		long startTime = System.nanoTime();

		float time = (System.nanoTime() - startTime) / 1000000000f;
		float now = time;
		float averagePassedTime = 0;

		boolean naiveTiming = true;

		// /Not sure I understand the weird dichotomy between LevelScene and
		// LevelInterface...
		randomLevel = new LevelSceneCustom(graphicsConfiguration, this,
				new Random().nextLong(), 0, 0, isCustom, levelGenerator);

		Mario.fire = false;
		Mario.large = false;
		Mario.coins = 0;
		Mario.lives = 3;

		randomLevel.init();
		randomLevel.setSound(sound);
		scene = randomLevel;
		// /

		while (running) {
			float lastTime = time;
			time = (System.nanoTime() - startTime) / 1000000000f;
			float passedTime = time - lastTime;

			if (passedTime < 0)
				naiveTiming = false; // Stop relying on nanotime if it starts
										// skipping around in time (ie running
										// backwards at least once). This
										// sometimes happens on dual core amds.
			averagePassedTime = averagePassedTime * 0.9f + passedTime * 0.1f;

			if (naiveTiming) {
				now = time;
			} else {
				now += averagePassedTime;
			}

			int tick = (int) (now * TICKS_PER_SECOND);

			if (lastTick == -1)
				lastTick = tick;

			while (lastTick < tick) {
				scene.tick();

				lastTick++;
			}

			float alpha = (float) (now * TICKS_PER_SECOND - tick);
			sound.clientTick(alpha);

			og.setColor(Color.WHITE);
			og.fillRect(0, 0, 320, 240);

			scene.render(og, alpha);

			if (!this.hasFocus() && tick / 4 % 2 == 0) {
				String msg = "CLICK TO PLAY";

				drawString(og, msg, 160 - msg.length() * 4 + 1, 110 + 1, 0);
				drawString(og, msg, 160 - msg.length() * 4, 110, 7);
			}
			og.setColor(Color.BLACK);

			if (width != 320 || height != 240) {

				if (useScale2x) {
					g.drawImage(scale2x.scale(image), 0, 0, null);
				} else {
					g.drawImage(image, 0, 0, 640, 480, null);

				}
			} else {
				g.drawImage(image, 0, 0, null);
			}

			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
			}
		}

		Art.stopMusic();
	}

	private void drawString(Graphics g, String text, int x, int y, int c) {
		char[] ch = text.toCharArray();
		for (int i = 0; i < ch.length; i++) {
			g.drawImage(Art.font[ch[i] - 32][c], x + i * 8, y, null);
		}
	}

	public void keyPressed(KeyEvent arg0) {
		toggleKey(arg0.getKeyCode(), true);
	}

	public void keyReleased(KeyEvent arg0) {
		toggleKey(arg0.getKeyCode(), false);
	}

	public void keyTyped(KeyEvent arg0) {
	}

	public void focusGained(FocusEvent arg0) {
	}

	public void focusLost(FocusEvent arg0) {
	}

	public void levelWon() {
	}

	public static final int OPTIMIZED_FIRST = 0;
	public static final int MINIMIZED_FIRST = 1;

	private LevelScene randomLevel;

	public void lose() {
		scene = new LoseScene();
		scene.setSound(sound);
		scene.init();
	}

	public void win() {
		scene = new WinScene();
		scene.setSound(sound);
		scene.init();
	}

	public void mouseClicked(MouseEvent e) {
	}

	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
	}

	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
	}

	public void mousePressed(MouseEvent e) {
		// TODO Auto-generated method stub
	}

	public void mouseReleased(MouseEvent e) {

		while (!hasFocus()) {
			System.out.println("FORCE IT");
			requestFocus();
		}
	}

	/**
	 * Must return the actual fill of the viewable components
	 */
	public Dimension getPreferredSize() {
		return new Dimension(width, height);
	}

}
