import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public abstract class RetroApplet extends Applet implements Runnable, KeyListener {
	private Image offScrImage = null;
	private Graphics offScrGr = null;
	private volatile Thread mainThread = null;
	private boolean UP = false, 
				   DOWN = false, 
				   LEFT = false, 
				   RIGHT = false;
	private boolean FIRE = false;
	private boolean PAUSED = false;
	private boolean showFPS = false;
	private int lastKey = -1;

	public static final int FIRE_KEY  = 0,
						  LEFT_KEY  = 1,
						  RIGHT_KEY = 2,
						  UP_KEY    = 3,
						  DOWN_KEY  = 4;

	public void keyPressed( KeyEvent ke ) {
		int key = ke.getKeyCode();
		switch( key ) {
			case ke.VK_UP:    UP    = true; lastKey = UP_KEY;    break;
			case ke.VK_DOWN:  DOWN  = true; lastKey = DOWN_KEY;  break;
			case ke.VK_LEFT:  LEFT  = true; lastKey = LEFT_KEY;  break;
			case ke.VK_RIGHT: RIGHT = true; lastKey = RIGHT_KEY; break;
			case ke.VK_SPACE: FIRE  = true; lastKey = FIRE_KEY;  break;
		}
	}

	public void keyReleased( KeyEvent ke ) {
		switch( ke.getKeyCode() ) {
			case ke.VK_UP:    UP    = false; break;
			case ke.VK_DOWN:  DOWN  = false; break;
			case ke.VK_LEFT:  LEFT  = false; break;
			case ke.VK_RIGHT: RIGHT = false; break;
			case ke.VK_SPACE: FIRE  = false; break;
			case ke.VK_P:     PAUSED = !PAUSED; break;
			case ke.VK_F:		showFPS = !showFPS; break;
		}
	}

	public void keyTyped( KeyEvent ke ) {}

	public int lastKeyPressed() {
		return lastKey;
	}

	// key states
	public boolean up() { return UP; }
	public boolean down() { return DOWN; }
	public boolean left() { return LEFT; }
	public boolean right() { return RIGHT; }
	public boolean fire() { return FIRE; }

	public void init() {
		Dimension size = getSize();
		offScrImage = createImage( size.width, size.height );
		offScrGr = offScrImage.getGraphics();
		addKeyListener( this );
		addMouseListener(
			new MouseAdapter() {
				public void mouseReleased( MouseEvent me ) {
					requestFocus();
				}
			} );
	}

	public Graphics getBackBuffer() {
		return offScrGr;
	}

	private Font fpsFont = new Font( "SansSerif", Font.PLAIN, 12 );

	public void flipBuffer() {
		Graphics g = getGraphics();
		if ( showFPS ) {
			offScrGr.setFont( fpsFont );
			offScrGr.setColor( Color.black );
			offScrGr.drawString( "fps: " + fps, 5, 20 );
		}
		if ( g != null )
			g.drawImage( offScrImage, 0, 0, this );
	}

	private Font pausedFont = new Font( "SansSerif", Font.BOLD, 20 );
	private void showPaused() {
		String str = "Paused";
		Dimension size = getSize();
		offScrGr.setFont( pausedFont );
		FontMetrics metrics = offScrGr.getFontMetrics();
		int w = metrics.stringWidth( str );
		int h = metrics.getHeight();
		int a = metrics.getAscent();
		offScrGr.setColor( Color.black );
		offScrGr.fillRect( (size.width - w)>>1, (size.height>>1) -a-1, w, h+1 );
		offScrGr.setColor( Color.white );
		offScrGr.drawString( str, (size.width - w)>>1, size.height>>1 );
		flipBuffer();
	}

	public void start() {
		(mainThread = new Thread( this )).start();
	}

	protected float fps = 0;

	public void run() {
		while( mainThread == Thread.currentThread() ) {
			long s = System.currentTimeMillis();
			for ( int i = 0; i < 25; i++ ) {
			long start = System.currentTimeMillis();
			if ( !PAUSED )
				tick();
			else
				showPaused();
			long end = System.currentTimeMillis();
			int time = 40 - (int)(end-start);
			time = time > 0? time : 0;
			try {
				Thread.sleep( time );
			}
			catch( InterruptedException ie ) {}
			}
			long e = System.currentTimeMillis();
			fps = 25000.0f/(e-s);
		}
	}

	public void stop() {
		mainThread = null;
	}

	public abstract void tick();

	public void update( Graphics g ) { paint( g ); };
	
	public void paint( Graphics g ) { 
		if ( offScrImage != null ) 
			g.drawImage( offScrImage, 0, 0, this );
	}

}