package net.soupgames.guimark2;

/**
 * GuiMark2 in Java.
 * Version 1.3
 *
 * Written by Jesper Juul, www.jesperjuul.net
 * 
 * This is a quick port from HTML5/JavaScript to Java 1.5.
 * 
 *  Results on my 2007 Thinkpad T60p, running Windows XP, Java 1.6.0_20:
 *  
 *  HTML Firefox: 4.28 FPS
 *  Flash 10 Firefox: 12.9 FPS
 *  Java: 215.2 FPS. (No difference between browsers.)
 *  
 *  2008 MacBook Pro:
 *  HTML Firefox 3.6: 4.5 FPS
 *  Flash 10 Firefox: 13.5 FPS
 *  Java: 13.5 FPS
 *  
 *  
 *  Version notes:
 *  1.0: Drawing done via a buffer image.
 *  1.1: Drawing done using a buffer strategy. (This increased the Windows XP FPS from 20 to 215 FPS. No change on the MacBook Pro.)
 *  1.2: Added button to increase # of monsters
 *  1.3: Added button to switch drawing strategies and keyboard shortcut ('V') to show version
 */
import java.applet.Applet;
import java.awt.BasicStroke;
import java.awt.Button;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Label;
import java.awt.MediaTracker;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferStrategy;
import java.util.Hashtable;
import java.util.Vector;

public class guimark2 extends Applet implements Runnable, ActionListener,
		KeyListener {

	private int MONSTERNUM = 600;

	final static int W = 1200, H = 600, XPOS = 0, YPOS = 40;

	private Thread thread = null;
	private static final long serialVersionUID = 1882731086792923772L;

	private static final String BUFFER_STRATEGY = "Buffer: Strategy";

	private static final String BUFFER_IMAGE = "Buffer: Image";

	Hashtable<String, Image> assets = new Hashtable<String, Image>();

	String assetnames[] = { "EnemySouth1", "assets/monster/s1.png",
			"EnemySouth2", "assets/monster/s2.png", "EnemySouth3",
			"assets/monster/s3.png", "EnemyEast1", "assets/monster/e1.png",
			"EnemyEast2", "assets/monster/e2.png", "EnemyEast3",
			"assets/monster/e3.png", "EnemyWest1", "assets/monster/w1.png",
			"EnemyWest2", "assets/monster/w2.png", "EnemyWest3",
			"assets/monster/w3.png", "EnemyNorth1", "assets/monster/n1.png",
			"EnemyNorth2", "assets/monster/n2.png", "EnemyNorth3",
			"assets/monster/n3.png", "Tower", "assets/lighthouse.png",
			"TowerTop", "assets/lighthousetop.png", "Bomb1",
			"assets/explosion/e_f01.png", "Bomb2",
			"assets/explosion/e_f02.png", "Bomb3",
			"assets/explosion/e_f03.png", "Bomb4",
			"assets/explosion/e_f04.png", "Bomb5",
			"assets/explosion/e_f05.png", "Bomb6",
			"assets/explosion/e_f06.png", "Bomb7",
			"assets/explosion/e_f07.png", "Bomb8",
			"assets/explosion/e_f08.png", "Background", "assets/groundtile.png" };

	MediaTracker mt;
	Label meterlabel;
	FPSMeter meter = new FPSMeter();
	Button starttestbutton;
	double fps;
	Canvas drawcanvas;
	Button changemonsternumbutton;

	FramedBitmap[] monsters;
	FramedBitmap[] explosions = new FramedBitmap[8];

	Image bgimage;
	int bgimagew, bgimageh;

	private Label testresult;

	private BufferStrategy bufferstrategy;

	private Image buffer;

	// If we should change buffer strategy at next update
	Boolean changebuffer = false;

	// Change monster number at next update
	private Integer changemonsternum = 0;

	// Start timer at next update
	private Boolean starttest = false;

	private Button changebufferbutton;

	synchronized void preloadAssets() {
		mt = new MediaTracker(this);

		for (int i = 0; i < assetnames.length; i += 2) {
			Image img = getImage(getCodeBase(), assetnames[i + 1]);
			mt.addImage(img, i);
			assets.put(assetnames[i], img);
		}
		try {
			mt.waitForAll();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < assetnames.length; i += 2) {
			if (mt.getErrorsID(i) != null)
				System.out.println("Error loading " + assetnames[i + 1]);
		}

		bgimage = assets.get("Background");
		bgimagew = bgimage.getWidth(this);
		bgimageh = bgimage.getHeight(this);
	}

	@Override
	public synchronized void start() {

		if (thread == null) {
			thread = new Thread(this);
			thread.start();
		}
	}

	@Override
	public synchronized void stop() {
	}

	synchronized void initGraphics() {
		preloadAssets();

		buffer = createImage(W, H);
		setupMonsters();

		setLayout(null);

		Label title = new Label("GuiMark2 Java Bitmap Gaming Test");
		title.setBounds(getInsets().left + 10, 10 + getInsets().top, 290, 20);
		Font f = getFont();
		title.setFont(new Font(f.getName(), Font.BOLD, f.getSize()));
		add(title);

		meterlabel = new Label("FPS label");
		meterlabel.setBounds(getInsets().left + 300, 10 + getInsets().top, 150,
				20);
		add(meterlabel);

		testresult = new Label("");
		testresult.setBounds(getInsets().left + 450, 10 + getInsets().top, 200,
				20);
		add(testresult);

		starttestbutton = new Button("Start Test");
		starttestbutton.setBounds(getInsets().left + 650, 10 + getInsets().top,
				150, 20);
		add(starttestbutton);
		starttestbutton.addActionListener(this);

		changemonsternumbutton = new Button("" + MONSTERNUM + " monsters");
		changemonsternumbutton.setBounds(getInsets().left + 825,
				10 + getInsets().top, 150, 20);

		add(changemonsternumbutton);
		changemonsternumbutton.addActionListener(this);

		changebufferbutton = new Button(BUFFER_STRATEGY);
		changebufferbutton.setBounds(getInsets().left + 1000,
				10 + getInsets().top, 150, 20);

		add(changebufferbutton);
		changebufferbutton.addActionListener(this);

		drawcanvas = new Canvas();
		drawcanvas.setSize(W, H);
		drawcanvas.setLocation(XPOS + getInsets().left, YPOS + getInsets().top);
		add(drawcanvas);

		addKeyListener(this);
		drawcanvas.addKeyListener(this);
		changemonsternumbutton.addKeyListener(this);
		starttestbutton.addKeyListener(this);
		changebufferbutton.addKeyListener(this);
		requestFocus();

		drawcanvas.createBufferStrategy(2);

		bufferstrategy = drawcanvas.getBufferStrategy();

	}

	private void setupMonsters() {
		monsters = new FramedBitmap[MONSTERNUM];
		Image[] southWalk = { assets.get("EnemySouth1"),
				assets.get("EnemySouth2"), assets.get("EnemySouth3"),
				assets.get("EnemySouth2") };
		Image[] eastWalk = { assets.get("EnemyEast1"),
				assets.get("EnemyEast2"), assets.get("EnemyEast3"),
				assets.get("EnemyEast2") };
		Image[] westWalk = { assets.get("EnemyWest1"),
				assets.get("EnemyWest2"), assets.get("EnemyWest3"),
				assets.get("EnemyWest2") };
		Image[] northWalk = { assets.get("EnemyNorth1"),
				assets.get("EnemyNorth2"), assets.get("EnemyNorth3"),
				assets.get("EnemyNorth2") };

		double R = Math.PI / 180;

		// create monsters
		for (int i = 0; i < MONSTERNUM; i++) {
			double speed = 3 + (Math.random() * 2);
			double angle = Math.random() * 360;

			FramedBitmap fb = null;
			if (angle >= 45 && angle < 135) {
				fb = new FramedBitmap(southWalk);
			} else if (angle >= 135 && angle < 225) {
				fb = new FramedBitmap(westWalk);
			} else if (angle >= 225 && angle < 315) {
				fb = new FramedBitmap(northWalk);
			} else {
				fb = new FramedBitmap(eastWalk);
			}
			fb.x = (float) (Math.random() * W);
			fb.y = (float) (Math.random() * H);
			fb.xMove = (float) (Math.cos(angle * R) * speed);
			fb.yMove = (float) (Math.sin(angle * R) * speed);
			fb.scale = (float) (0.5 + (Math.random() * 0.7));
			fb.scalew = (int) (fb.bitmaps[0].getWidth(null) * fb.scale);
			fb.scaleh = (int) (fb.bitmaps[0].getHeight(null) * fb.scale);
			fb.randomize();
			monsters[i] = fb;
		}

		// create explosions
		for (int i = 0; i < 8; i++) {
			Image imgs[] = { assets.get("Bomb1"), assets.get("Bomb2"),
					assets.get("Bomb3"), assets.get("Bomb4"),
					assets.get("Bomb5"), assets.get("Bomb6"),
					assets.get("Bomb7"), assets.get("Bomb8") };
			FramedBitmap fb = new FramedBitmap(imgs);
			fb.frame = i;
			fb.x = -200;
			fb.y = -200;
			explosions[i] = fb;
		}
	}

	synchronized void processFrame() {
		Graphics2D bufferg;

		if (bufferstrategy != null) {
			bufferg = (Graphics2D) bufferstrategy.getDrawGraphics();
		} else {
			bufferg = (Graphics2D) buffer.getGraphics();
		}
		bufferg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		// Draw the tiled background image
		for (int x = 0; x < W; x += bgimagew) {
			for (int y = 0; y < H; y += bgimageh) {
				bufferg.drawImage(bgimage, x, y, null);
			}
		}
		// position monsters
		for (int i = 0; i < monsters.length; i++) {
			FramedBitmap fb = monsters[i];
			fb.x += fb.xMove;
			fb.y += fb.yMove;
			if (fb.x < -40) {
				fb.x += 1240;
			} else if (fb.x > W) {
				fb.x -= 1240;
			}
			if (fb.y < -40) {
				fb.y += 640;
			} else if (fb.y > H) {
				fb.y -= 640;
			}

			if (fb.y <= 330) {
				bufferg.drawImage(fb.getNextBitmap(), (int) (fb.x / fb.scale),
						(int) (fb.y / fb.scale), fb.scalew, fb.scaleh, null);
			}
		}

		// draw tower
		bufferg.drawImage(assets.get("Tower"), 550, 100, null);

		// draw monsters above tower
		for (int i = 0; i < monsters.length; i++) {
			FramedBitmap fb = monsters[i];
			if (fb.y > 330) {
				bufferg.drawImage(fb.getNextBitmap(), (int) (fb.x / fb.scale),
						(int) (fb.y / fb.scale), fb.scalew, fb.scaleh, null);
			}
		}

		// draw laser and explosions
		for (int i = 0; i < 8; i++) {
			FramedBitmap fb = explosions[i];
			if (fb.frame == 0) {
				double dist = 200;
				double angle = Math.random() * 360 * Math.PI / 180;
				fb.x = (float) (520 + (Math.cos(angle) * dist));
				fb.y = (float) (270 + (Math.sin(angle) * dist));

				bufferg.setColor(new Color(0xffff00));
				bufferg.setStroke(new BasicStroke(3));
				bufferg.drawLine(612, 180, (int) fb.x + 83, (int) fb.y + 70);
			}
			bufferg.drawImage(fb.getNextBitmap(), (int) fb.x, (int) fb.y, null);
		}

		// draw tower top
		bufferg.drawImage(assets.get("TowerTop"), 570, 124, null);

		// Buffer strategy version
		bufferg.dispose();
		if (bufferstrategy != null) {
			bufferstrategy.show();
		} else {
			drawcanvas.getGraphics().drawImage(buffer, 0, 0, null);
		}

		updatePerformance();
	}

	void updatePerformance() {
		meter.increment();
		if (testRunning) {
			continueTest();
		}
		meterlabel.setText("Current: " + meter.getFramerate() + " fps");
	}

	// test runner
	long testBegin = 0;
	Vector<Long> testData = new Vector<Long>();

	boolean testRunning = false;

	synchronized void startTest() {
		testBegin = System.currentTimeMillis();
		testRunning = true;
		// testData = [];
		testData.removeAllElements();
		testresult.setText("Running...");
	}

	synchronized void continueTest() {
		long time = System.currentTimeMillis();
		testData.add(time);
		if (time - testBegin > 10000) {
			testRunning = false;
			double output = ((double) testData.size() / (double) (time - testBegin)) * 1000;
			testresult.setText("Test Average: " + meter.formatNumber(output)
					+ " fps");
		}
	}

	// additional classes
	class FramedBitmap {
		public int scaleh;
		public int scalew;
		public float scale;
		public float yMove;
		public float xMove;
		public float y;
		public float x;
		Image bitmaps[];

		FramedBitmap(Image sourceArray[]) {
			bitmaps = sourceArray;
		}

		int frame = 0;

		void increment() {
			if (++frame >= bitmaps.length) {
				frame = 0;
			}
		}

		void randomize() {
			frame = (int) Math.floor(Math.random() * bitmaps.length);
		}

		Image getBitmap() {
			return bitmaps[frame];
		}

		Image getNextBitmap() {
			increment();
			return getBitmap();
		}
	}

	class FPSMeter {
		String sampleFPS = "0";
		long lastSampledTime = 0;
		int sampleFrames = 0;

		int sampleDuration = 500;

		void increment() {
			sampleFrames++;
		}

		String getFramerate() {
			double diff = System.currentTimeMillis() - lastSampledTime;
			if (diff >= sampleDuration) {
				double rawFPS = sampleFrames / (diff / 1000);
				sampleFPS = formatNumber(rawFPS);
				sampleFrames = 0;
				lastSampledTime = System.currentTimeMillis();
			}
			return sampleFPS;
		}

		String formatNumber(double rawFPS) {
			// format as XX.XX
			return "" + Math.floor(rawFPS * 100) / 100;
		}
	}

	long startTime = System.currentTimeMillis();

	synchronized long getTimer() {
		return System.currentTimeMillis() - startTime;
	}

	public void run() {
		initGraphics();
		while (true) {
			processFrame();
			synchronized (changemonsternum) {
				if (changemonsternum > 0) {
					MONSTERNUM = changemonsternum;
					changemonsternum = 0;
					changemonsternumbutton.setLabel("" + MONSTERNUM
							+ " monsters");
					changemonsternumbutton.doLayout();
					setupMonsters();
				}
			}
			synchronized (starttest) {
				if (starttest) {
					starttest = false;
					startTest();
				}
			}
			synchronized (changebuffer) {
				if (changebuffer) {
					if (bufferstrategy == null) {
						bufferstrategy = drawcanvas.getBufferStrategy();
						changebufferbutton.setLabel(BUFFER_STRATEGY);
					} else {
						bufferstrategy = null;
						changebufferbutton.setLabel(BUFFER_IMAGE);
					}
					changebuffer = false;
				}
			}
		}
	}

	// When a button is pushed
	public void actionPerformed(ActionEvent evt) {
		if (evt.getSource() == changemonsternumbutton) {
			synchronized (changemonsternum) {
				if (changemonsternum == 0) {
					int mn = MONSTERNUM * 5;
					if (mn > 75000)
						mn = 600;
					changemonsternum = mn;
					changemonsternumbutton.setLabel("Wait.");
					changemonsternumbutton.doLayout();
				}
			}
		} else if (evt.getSource() == starttestbutton) {
			synchronized (starttest) {
				starttest = true;
			}
		} else if (evt.getSource() == changebufferbutton) {
			synchronized (changebuffer) {
				changebufferbutton.setLabel("Wait.");
				changebuffer = true;
			}
		}

	}

	public void keyPressed(KeyEvent arg0) {

	}

	public void keyReleased(KeyEvent arg0) {

	}

	public void keyTyped(KeyEvent e) {
		switch (e.getKeyChar()) {
		case 'v':
			testresult.setText("Java " + System.getProperty("java.version"));
			break;
		}
	}

}

