import java.applet.Applet;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;


public class N extends Applet implements Runnable
{
	int m[]; // mouse x,y,state
	
	public void init()
	{
		enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
		
		m = new int[3];
		
		new Thread(this).start();
	}
	
	public void run()
	{
		final int SCREEN_W = 800;
		final int SCREEN_H = 600;
		
		// Map format constants
		final int MAP_LENGTH = 4000;
		final int GROUND_SEGMENTS = 20 + 1;
		final int GROUND_SEGMENT_LENGTHS = MAP_LENGTH / (GROUND_SEGMENTS - 1);
		
		final int NUM_JEWELS = 20;
		final int NUM_RINGS = 16;
		final int NUM_LAPS = 3;
		
		final int LOAD_JEWEL_X_SCALE = 32;
		final int LOAD_JEWEL_Y_SCALE = 8;
		
		final int LOAD_RING_X_SCALE = 32;
		final int LOAD_RING_Y_SCALE = 8;
		
		final float LOAD_RING_RADIUS = 80;
		
		// Offsets into data string
		final int GROUND_START = 0;
		final int GROUND_END = ((GROUND_SEGMENTS-1) / 3);
		
		final int JEWELS_START = GROUND_END + 1;
		final int JEWELS_END = JEWELS_START + NUM_JEWELS;
		
		final int RINGS_START = JEWELS_END;
		final int RINGS_END = RINGS_START + NUM_RINGS;
		
		// Game states
		final int INIT_STATE = 0;
		final int TITLE_STATE = 1;
		final int GAME_STATE = 2;
		final int FINISH_STATE = 3;
		final int GAME_OVER = 4;
		int state = INIT_STATE;
		
		// Gameplay constants
		final float TURN_SPEED = 0.1f;
		final float FLY_SPEED = 8.0f;
		final float BOUNCE_ANGLE = 0.5f;
		
		final int RING_INACTIVE_TIME = 120;
		final int CHAIN_LINK_TIME = 60;
		
		final int TIME_PER_LEVEL = 120 * 60; // 2mins
		
		final int JEWEL_RADIUS = 30;
		
		final int POD_COLLISION_RADIUS = 50;
		final int POD_DRAW_RADIUS = 60;
		
		final float PI = (float)Math.PI;
		final float PI2 = PI * 2;
		final float HALF_PI = PI / 2;
		
		Image b = createImage(800, 600);
		
		/****** Player ******/
		
		float player_x = 0;
		float player_y = 0;
		float player_angle = 0;
		float[] player_angleHistory = new float[32];
		
		/****** Camera ******/
		
		float camera_x = 0;
		float camera_y = 0;
		
		/****** Ground ******/
		
		float[] ground = new float[GROUND_SEGMENTS];
		
		/****** Jewels ******/
		
		float[] jewels_x = new float[NUM_JEWELS];
		float[] jewels_y = new float[NUM_JEWELS];
		boolean[] jewels_active = new boolean[NUM_JEWELS];
		
		int num_trailing_jewels = 0;
		int num_collected_jewels = 0;
		
		/****** Rings ******/
		
		float[] rings_x1 = new float[NUM_RINGS];
		float[] rings_y1 = new float[NUM_RINGS];
		float[] rings_x2 = new float[NUM_RINGS];
		float[] rings_y2 = new float[NUM_RINGS];

		int[] rings_inactiveDecay = new int[NUM_RINGS];
		
		/****** Trail ******/
		
		float[] trail_x = new float[1024];
		float[] trail_y = new float[1024];
		
		int pod_x = 120;
		int pod_y = 500;
		
		/****** Chain ******/
		
		int chain_currentChain = 0;
		int chain_decay = 0;
		
		/****** Level ******/
		int level_currentLevel = 0;
		int level_timeLeft = 0;
		int level_completeLaps = 0;
		int level_score = 0;
		
		/****** Random ******/
		
		float[] random = new float[1024];
		for (int i=0; i<1024; i++)
			random[i] = (float)Math.random();
		
		/****** Level data ******/
		
		final int NUM_LEVELS = 4;
		final int LEVEL_DATA_SIZE = 43;
		
		// Level data, packed as one string to save space
		final String level_data =
			// Dream 4
			"\u52b8\u77fd\u7b53\u5a98\u5eaf\u3275\u6ed4\u04ca\u06d2\u0960\u0d6d\u1a31\u1168\u155e\u17c3\u1d1a\u1ba5\u2112\u233d\u22a5\u24d2\u28c5\u2cab\u31b1\u3dc3\u39cb\u3544\u9f91\ub7cc\ua6cc\uaf23\ubbc7\u8858\u8b69\u8f67\u9365\u56ce\u5939\ue21a\u2333\ue3c9\uf339\u6ab5"
			+
			// Dream 1
			"\u7bda\u6e36\u339e\u73d9\u6fc7\u2d3d\u5f3e\u04e3\u08da\u0ac6\u1651\u17e7\u1ae9\u2358\u27e5\u2940\u2a96\u2e11\u3118\u35dd\u38c6\u3c57\u03a5\u0724\u0aa3\u1311\u1f41\u875c\u49cf\u85a6\u8924\uface\ud6de\ue5dd\u6854\uaf97\uac93\u7751\u9969\u9323\u9f62\u8f2c\ub294"
			+
			// Dream 2
			"\u4a91\u566e\u3909\u7bfc\u7fdf\u7907\u62f2\u07a3\u38cc\u3831\u3717\u2b6e\u0abe\u0da3\u0a8f\u1419\u1a9c\u1c49\u1f6d\u25ed\u2f49\u253e\u1ea0\u2288\u26a4\u2b88\u3108\u9793\ua2e3\ua8ea\udde1\ue497\u6d65\u6094\u6896\uae87\u38be\u3825\uf60e\uc8b2\ucc94\u4cb2\u4894"
			+
			// Dream C
			"\u6b5d\u73fd\u6778\u4787\u629a\u773d\u6f7a\u04d2\u0a61\u1356\u1951\u1eba\u2009\u2488\u26bd\u2f61\u35e0\u0dbf\u0b01\u0e1d\u0916\u131d\u1a93\u2f30\u3329\u37a2\u3c38\ua288\u87db\u8e66\u965a\ub256\u1f25\u25ab\ue9c4\u8ba1\u9718\ub12c\ub5a5\u4cca\u0e33\ucd0a\ufa2d"
			;
		
		
		int[] level_highScores = new int[NUM_LEVELS];
		
		final int CHOOSE_LEVEL_START = 280;
		final int CHOOSE_LEVEL_INC = 80;
		
		boolean aaOn = true;
		
		Color black = new Color(0, 0, 0);
		Color white = new Color(255, 255, 255);
		Color purple = new Color(160, 0, 255);
		Color darkPurple = new Color(100, 0, 200);
		
		for(;;)
		{
			/****** Game flow logic ******/
			
			// Toggle AA?
			if (m[2] > 1)
			{
				aaOn = !aaOn;
			}
	        
			boolean reloadLevel = false;
			
			if (state == INIT_STATE)
			{
				reloadLevel = true;
				
				state = TITLE_STATE;
			}
			else if (state == TITLE_STATE)
			{
				if (m[2] == 1)
				{
					// Find out which level we picked
					for (int i=0; i<NUM_LEVELS; i++)
					{
						int h = CHOOSE_LEVEL_START + CHOOSE_LEVEL_INC*i;
						if (m[1] < h && m[1] > h - 50)
						{
							// Reload game state
							reloadLevel = true;
							level_currentLevel = i;
						}
					}
					
					state = GAME_STATE;
				}
			}
			else if (state == GAME_STATE)
			{
				if (num_collected_jewels == NUM_JEWELS)
				{
					// Completed a lap
					level_completeLaps++;
					
					if (level_completeLaps == NUM_LAPS)
					{
						// Completed all three laps, finish the level
						state = FINISH_STATE;
						
						level_score += level_timeLeft;
						
						if (level_score > level_highScores[level_currentLevel])
							level_highScores[level_currentLevel] = level_score;
					}
					else
					{
						// Reset for next lap
						num_collected_jewels = 0;
						for (int i=0; i<jewels_active.length; i++)
							jewels_active[i] = true;
					}
				}
				
				level_timeLeft--;
				if (level_timeLeft <= 0)
				{
					state = GAME_OVER;
				}
			}
			else if (state == FINISH_STATE || state == GAME_OVER)
			{
				if (m[2] == 1)
					state = TITLE_STATE;
			}
			
			// Clear button edge state
			m[2] = 0;
			
			/****** Level creation ******/
			
			if (reloadLevel)
			{
				/*
				// Load ground from data string
				for (int i=GROUND_START; i<=GROUND_END; i++)
				{
					final char ch = level_data[level_currentLevel].charAt(i);
					
					final int h1 = (ch >> 10) & 0x001F;
					final int h2 = (ch >> 5) & 0x001F;
					final int h3 = ch & 0x001F;
					
					// Using i like this is error prone
					//		should refactor to use i from 0-numGroundChars and a start offset into data?
					ground[i*3] = h1 * 32;
					ground[i*3+1] = h2 * 32;
					ground[i*3+2] = h3 * 32;
				}
				ground[GROUND_SEGMENTS-1] = ground[0];
				
				// Load rings from data string
				for (int i=RINGS_START; i<RINGS_END; i++)
				{
					final char ch = level_data[level_currentLevel].charAt(i);
					
					final int x = ((ch >> 7) & 0x7f) * LOAD_RING_X_SCALE;
					final int y = ((ch) & 0x7f) * LOAD_RING_Y_SCALE;
					
					final int angle = (ch >> 14) & 0x3;
					
					final float a = angle * PI / 4;
					
					final float cosA = (float)Math.sin(a+HALF_PI) * LOAD_RING_RADIUS;
					final float sinA = (float)Math.sin(a) * LOAD_RING_RADIUS;
					rings_x1[i-RINGS_START] = x + cosA;
					rings_y1[i-RINGS_START] = y + sinA;
					rings_x2[i-RINGS_START] = x - cosA;
					rings_y2[i-RINGS_START] = y - sinA;
				}
				
				// Load jewels from data string
				for (int i=JEWELS_START; i<JEWELS_END; i++)
				{
					final char ch = level_data[level_currentLevel].charAt(i);
					
					final int x = ((ch >> 7) & 0x7f) * LOAD_JEWEL_X_SCALE;
					final int y = ((ch) & 0x7f) * LOAD_JEWEL_Y_SCALE;
					
					jewels_x[i-JEWELS_START] = x;
					jewels_y[i-JEWELS_START] = y;
					jewels_active[i-JEWELS_START] = true;
				}
				*/
				
				// Single-loop loading method
				for (int i = GROUND_START; i < RINGS_END; i++)
				{
					final char ch = level_data.charAt(i + (level_currentLevel * LEVEL_DATA_SIZE));

					if (i <= GROUND_END)
					{
						final int h1 = (ch >> 10) & 0x001F;
						final int h2 = (ch >> 5) & 0x001F;
						final int h3 = ch & 0x001F;

						// Using i like this is error prone
						// should refactor to use i from 0-numGroundChars and a
						// start offset into data?
						ground[i * 3] = h1 * 32;
						ground[i * 3 + 1] = h2 * 32;
						ground[i * 3 + 2] = h3 * 32;
					}
					else if (i < JEWELS_END)
					{
						final int x = ((ch >> 7) & 0x7f) * LOAD_JEWEL_X_SCALE;
						final int y = ((ch) & 0x7f) * LOAD_JEWEL_Y_SCALE;

						jewels_x[i - JEWELS_START] = x;
						jewels_y[i - JEWELS_START] = y;
						jewels_active[i - JEWELS_START] = true;
					}
					else
					{
						final int x = ((ch >> 7) & 0x7f) * LOAD_RING_X_SCALE;
						final int y = ((ch) & 0x7f) * LOAD_RING_Y_SCALE;

						final int angle = (ch >> 14) & 0x3;

						final float a = angle * PI / 4;

						final float cosA = (float) Math.sin(a + HALF_PI) * LOAD_RING_RADIUS;
						final float sinA = (float) Math.sin(a) * LOAD_RING_RADIUS;
						rings_x1[i - RINGS_START] = x + cosA;
						rings_y1[i - RINGS_START] = y + sinA;
						rings_x2[i - RINGS_START] = x - cosA;
						rings_y2[i - RINGS_START] = y - sinA;
					}
				}
				ground[GROUND_SEGMENTS - 1] = ground[0];
                // --
                
				// Reset player
				player_x = 0;
				player_y = 500;
				player_angle = 0;
				
				camera_x = player_x;
				camera_y = player_y;
				
				// Reset jewels
				num_trailing_jewels = 0;
				num_collected_jewels = 0;
				
				// Reset chain
				chain_currentChain = 0;
				
				// Reset level
				level_timeLeft = TIME_PER_LEVEL;
				level_completeLaps = 0;
				level_score = 0;
				
				reloadLevel = false;
			}
			
			/****** Gameplay logic ******/
			
			/****** Player Input ******/
			{
				float targetAngle = player_angle;
				
				final float screen_x = (player_x - camera_x) + SCREEN_W / 2;
				final float screen_y = (player_y - camera_y) + SCREEN_H / 2;
				
				final float dx = m[0] - screen_x;
				final float dy = m[1] - screen_y;
				
				targetAngle = (float)Math.atan2(dy, dx);
				
				float normalisedAngle;
				float diff = targetAngle - player_angle;
				diff = diff < 0 ? -diff : diff;
			//	if (Math.abs(targetAngle - player_angle) > PI)
			//	if ((targetAngle - player_angle) > PI || player_angle - targetAngle > PI) // Non abs() version
			//	if ( (targetAngle > player_angle ? targetAngle - player_angle > PI : -(targetAngle - player_angle) > PI))
				if (diff > PI)
				{
					// targetAngle and angle more than 180 apart, normalise angle
					if (player_angle > targetAngle)
						normalisedAngle = player_angle - PI2;
					else
						normalisedAngle = player_angle + PI2;
				}
				else
					normalisedAngle = player_angle;
				
				// Took this out to save space, doesn't seem to actually make a difference when controlling
			//	if (Math.abs(targetAngle - player_angle) < TURN_SPEED)
			//	{
			//		player_angle = targetAngle;
			//	}
			//	else
					if (normalisedAngle < targetAngle)
				{
					// Need to add to angle
					player_angle += TURN_SPEED;
				}
				else
				{
					player_angle -= TURN_SPEED;
				}
				
				while (player_angle > PI2)
					player_angle -= PI2;
				
				// Don't seem to need this 'cos we do all are calculations in +itive angles
			//	while (player_angle < 0)
			//		player_angle += PI2;
				
				// Angle history
				for (int i=player_angleHistory.length-1; i>0; i--)
				{
					player_angleHistory[i] = player_angleHistory[i-1];
				}
				player_angleHistory[0] = player_angle;
			}
			
			/****** Player Movement ******/
			{
				if (state == GAME_STATE)
				{
					player_x += Math.sin(player_angle+HALF_PI) * FLY_SPEED;
					player_y += Math.sin(player_angle) * FLY_SPEED;
					
					// Wrap player and camera around the world
					if (player_x > MAP_LENGTH)
					{
						player_x -= MAP_LENGTH;
						camera_x -= MAP_LENGTH;
					}
					if (player_x < 0)
					{
						player_x += MAP_LENGTH;
						camera_x += MAP_LENGTH;
					}
				}
			}
			
			/****** Player-Jewels collision ******/
			for (int i=0; i<NUM_JEWELS; i++)
			{
				if (jewels_active[i])
				{
					final float jx = jewels_x[i];
					final float jy = jewels_y[i];
					if ( (player_x - jx)*(player_x - jx) + (player_y - jy)*(player_y - jy) < JEWEL_RADIUS * JEWEL_RADIUS )
					{
						jewels_active[i] = false;
						num_trailing_jewels++;
						
						// Add to chain
						chain_currentChain++;
						chain_decay = CHAIN_LINK_TIME;
						
						level_score += chain_currentChain;
					}
				}
			}
			
			/****** Player-Pod collision ******/
			if ( (player_x-pod_x)*(player_x-pod_x) + (player_y-pod_y)*(player_y-pod_y) < POD_COLLISION_RADIUS*POD_COLLISION_RADIUS )
			{
				num_collected_jewels += num_trailing_jewels;
				num_trailing_jewels = 0;
				
				// Pod is chain-able but doesn't actually contribute to chain
				// (workaround because chaining properly takes too much space)
				chain_decay = CHAIN_LINK_TIME;
			}
			
			/****** Player-Ground collision ******/
			{
				// Find current height of ground
				int lowerIndex = (int)(player_x / GROUND_SEGMENT_LENGTHS);
				
				final float lowerHeight = ground[lowerIndex];
				final float upperHeight = ground[lowerIndex+1];
			
				final float weight = (player_x - lowerIndex*GROUND_SEGMENT_LENGTHS) / GROUND_SEGMENT_LENGTHS;
				final float inv = 1f - weight;
				
				final float height = lowerHeight * inv + upperHeight * weight;
				if (player_y > height)
				{
					// Hit the ground
					
					player_y = height;
					// Snap angle to surface based on current angle
					if (player_angle < PI / 2 || player_angle > PI * 1.5f)
					{
						// Going right
						player_angle = (float)Math.atan2(upperHeight-lowerHeight, GROUND_SEGMENT_LENGTHS);
						player_angle -= BOUNCE_ANGLE;
					}
					else
					{
						// Going left
						player_angle = (float)Math.atan2(upperHeight-lowerHeight, -GROUND_SEGMENT_LENGTHS);
						player_angle += BOUNCE_ANGLE;
					}
					
					// Hitting the ground breaks the chain
					chain_currentChain = 0;
				}
			}
			
			/****** Player-Ring collision ******/
			{
				for (int i=0; i<NUM_RINGS; i++)
				{
					rings_inactiveDecay[i]--;
					
					// Check ring is active first
					if (rings_inactiveDecay[i] > 0)
						continue;
					
					final float x1 = rings_x1[i];
					final float y1 = rings_y1[i];
					final float x2 = rings_x2[i];
					final float y2 = rings_y2[i];
					
					// For each line, subdivide into 10 steps and check a circle around each
					final float len = (float)Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
					
					final int NUM_SUBDIVISIONS = 6;
					final float radius = (len / (NUM_SUBDIVISIONS - 1) ) / 2f;
					
					final float inc = 1f / NUM_SUBDIVISIONS;
					
					for (int j=0; j<NUM_SUBDIVISIONS; j++)
					{
						final float tVal = j * inc;
						
						// Find circle pos
						final float inv = 1f - tVal;
						
						final float x = x1 * inv + x2 * tVal;
						final float y = y1 * inv + y2 * tVal;
						
						final float distSquTo = (player_x-x)*(player_x-x) + (player_y-y)*(player_y-y);
						if (distSquTo < radius * radius)
						{
							// Have hit ring
							
							// Add to chain
							chain_currentChain++;
							chain_decay = CHAIN_LINK_TIME;
							
							// Deactivate ring
							rings_inactiveDecay[i] = RING_INACTIVE_TIME;
							
							break;
						}
					}
				}
				
				// Chain update
				chain_decay--;
				if (chain_decay <= 0)
				{
					chain_currentChain = 0;
				}
			}
			
			/****** Trail Update ******/
			for (int i=trail_x.length-1; i>0; i--)
			{
				trail_x[i] = trail_x[i-1];
				trail_y[i] = trail_y[i-1];
			}
			trail_x[0] = player_x;
			trail_y[0] = player_y;
			
			/****** Camera Movement ******/
			{
				if (state == GAME_STATE)
				{
					final float projX = player_x + (int)(Math.sin(player_angle+HALF_PI) * 100f);
					final float projY = player_y + (int)(Math.sin(player_angle) * 100f);
					
					final float weight = 0.95f;
					final float inv = 1f - weight;
					camera_x = camera_x * weight + projX * inv;
					camera_y = camera_y * weight + projY * inv;
				}
			}
			
			
			
			/****** Drawing logic ******/
			{
				Graphics2D gfx = (Graphics2D)b.getGraphics();
				
				
				/****** Draw Sky ******/
				{
					int strips = 12;
					int c = 220 - (10 * strips);
					int inc = SCREEN_H / strips;
					for (int y=0; y<=SCREEN_H+inc; y+=inc)
					{
						gfx.setColor( new Color(c, c, 255));
						c += 10;
						
						int r0 = (int)(random[y] * 80);
						int r1 = (int)(random[y+1] * 80);
						
						GeneralPath path = new GeneralPath();
						path.moveTo(0, y);
						path.lineTo(0, y-inc-r0);
						path.lineTo(SCREEN_W, y-inc-r1);
						path.lineTo(SCREEN_W, y);
						
						gfx.fill(path);
					}
				}
				
				if (aaOn)
					gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
				
				for (int pass=0; pass<2; pass++)
				{
					float camera_centerX = camera_x;
					
					// Apply camera translation
					int camOffsetX = 0;
					if (pass != 0)
					{
						// Filler pass, add translation and adjust camera center
						if (camera_x > MAP_LENGTH/2)
						{
							camOffsetX = MAP_LENGTH;
							camera_centerX = camera_x - MAP_LENGTH;
						}
						else
						{
							camOffsetX = -MAP_LENGTH;
							camera_centerX = camera_x + MAP_LENGTH;
						}
					}
					final float camTranslateX = -(camera_x - SCREEN_W/2) + camOffsetX;
					final float camTranslateY = -(camera_y - SCREEN_H/2);
					gfx.translate(camTranslateX, camTranslateY);
					
					
					/****** Draw Ground ******/
					{
						GeneralPath path = new GeneralPath();
						GeneralPath highlight = new GeneralPath();
						path.moveTo(-10, ground[0] + random[GROUND_SEGMENTS-1] * 50);
						highlight.moveTo(0, ground[0]);
						for (int i=1; i<GROUND_SEGMENTS; i++)
						{
							// Add a mid-point slightly raised to make everything look more rounded and hilly
							float mid = (ground[i-1] + ground[i]) / 2f;
							
							path.lineTo(i*GROUND_SEGMENT_LENGTHS-(GROUND_SEGMENT_LENGTHS/2), mid-20 + random[i]*50);
							path.lineTo(i*GROUND_SEGMENT_LENGTHS, ground[i] + random[i]*50);
							
							
							highlight.lineTo(i*GROUND_SEGMENT_LENGTHS-(GROUND_SEGMENT_LENGTHS/2), mid-20);
							highlight.lineTo(i*GROUND_SEGMENT_LENGTHS, ground[i]);
						}
						path.lineTo((GROUND_SEGMENTS)*GROUND_SEGMENT_LENGTHS, 2000);
						path.lineTo(0, 2000);
						
						highlight.lineTo((GROUND_SEGMENTS-1)*GROUND_SEGMENT_LENGTHS, 2000);
						highlight.lineTo(0, 2000);
						
						gfx.setColor( new Color(100, 250, 100));
						gfx.fill(highlight);
						
						gfx.setColor( new Color(50, 200, 50));
						gfx.fill(path);
					}
					
					
					/****** Draw Pod ******/
					final int r0 = POD_DRAW_RADIUS;
					final int r1 = POD_DRAW_RADIUS + 50;
					final int r2 = POD_DRAW_RADIUS + 70;
					
					gfx.setColor( new Color(0, 200, 255) );
					gfx.fillOval(pod_x-r2/2, pod_y-r2/2, r2, r2);
					
					gfx.setColor( new Color(0, 180, 220) );
					gfx.fillOval(pod_x-r1/2, pod_y-r1/2, r1, r1);
					
					gfx.setColor( new Color(0, 140, 180) );
					gfx.fillOval(pod_x-r0/2, pod_y-r0/2, r0, r0);
					

					
					final int jewelR0 = JEWEL_RADIUS / 2;
					final int jewelR1 = jewelR0 - 5;
					final int jewelR2 = jewelR1 - 5;
					
					// Crazy interlacing of ring depth drawing around player/jewels so we get proper draw ordering
					// For each ring dot:
					for (int j=0; j<10; j++)
					{
						
						for (int i=0; i<NUM_RINGS; i++)
						{
							final float t = HALF_PI + (PI2 / 10f) * j;
							
							int r = 6;
							if (rings_inactiveDecay[i] <= 0)
								r = 14;
							
							float weight = (float)Math.sin(t) / 2 + 0.5f;
							float depth = (float)Math.sin(t+HALF_PI);
							
							float inv = 1.0f - weight;
							
							int rx = (int)(rings_x1[i]*weight + rings_x2[i]*inv);
							int ry = (int)(rings_y1[i]*weight + rings_y2[i]*inv);
						
							float xBias = (rx - camera_centerX) * 0.1f * depth;
							float yBias = (ry - camera_y) * 0.1f * depth;
							
							int yellow = (int)(220 + (depth*30));
							
							gfx.setColor( new Color(yellow-20, yellow-20, 0) );
							gfx.fillOval(rx-r+(int)xBias, ry-r+(int)yBias, r*2, r*2);
							
							gfx.setColor( new Color(yellow, yellow, 0) );
							gfx.fillOval(rx-r+(int)xBias+4, ry-r+(int)yBias-4, r*2, r*2);
						}
						
						if (j == 5)
						{
							// Halfway, draw stuff between rings
							
							/****** Draw Jewels ******/
							for (int k=0; k<NUM_JEWELS; k++)
							{
								if (jewels_active[k])
								{
									int x = (int)jewels_x[k];
									int y = (int)jewels_y[k];
									
									gfx.setColor( new Color(0, 140, 180) );
									gfx.fillOval(x-jewelR0, y-jewelR0, jewelR0*2, jewelR0*2);
									
									gfx.setColor( new Color(0, 180, 220) );
									gfx.fillOval(x-jewelR1+2, y-jewelR1-2, jewelR1*2, jewelR1*2);
									
									gfx.setColor( new Color(0, 200, 255) );
									gfx.fillOval(x-jewelR2, y-jewelR2, jewelR2*2, jewelR2*2);
								}
							}
							
							/****** Draw Collected Jewels ******/
							for (int k=0; k<num_trailing_jewels; k++)
							{
								final int index = k * 8 + 20;
								final int x = (int)trail_x[index];
								final int y = (int)trail_y[index];
								
								gfx.setColor( new Color(0, 140, 180) );
								gfx.fillOval(x-jewelR0, y-jewelR0, jewelR0*2, jewelR0*2);
								
								gfx.setColor( new Color(0, 180, 220) );
								gfx.fillOval(x-jewelR1+2, y-jewelR1-2, jewelR1*2, jewelR1*2);
								
								gfx.setColor( new Color(0, 200, 255) );
								gfx.fillOval(x-jewelR2, y-jewelR2, jewelR2*2, jewelR2*2);
							}
							
							
							/****** Draw Player ******/
							if (state == GAME_STATE)
							{
								// Angle/setup stuff
								
								gfx.translate(player_x, player_y);
							
								// Shared stuff
								final int offsetX = -12;
								final int offsetY = -6;
								
								final int numAngles = 3;
								float[] angles = new float[numAngles];
								
								for (int k=0; k<numAngles; k++)
								{
									int base = 0;
									if (k == 1)
										base = 6;
									if (k == 2)
										base = 10;
									
									float dx, dy;
									// Calculate head angle
									dx = trail_x[base] - trail_x[base+2];
									// Handle discontinuity at map wrapping by detecting and compensating in dx dir
									if (dx > 100)
										dx -= MAP_LENGTH;
									if (dx < -100)
										dx += MAP_LENGTH;
									dy = trail_y[base] - trail_y[base+2];
									angles[k] = (float)Math.atan2(dy, dx);
									
								}
							
								// Arms
								gfx.rotate(angles[2]);
								
								GeneralPath arm = new GeneralPath();
								arm.moveTo(0, 0);
								arm.lineTo(-80, 10);
								arm.lineTo(-60, 40);
								
								GeneralPath armTrim = new GeneralPath();
								armTrim.moveTo(-80, 10);
								armTrim.lineTo(-60, 20);
								armTrim.lineTo(-60, 40);
								
								gfx.translate(offsetX, offsetY);
								
								// Back arm
								gfx.setColor( new Color(200, 200, 200) );
								gfx.fill(arm);
								
								gfx.setColor( new Color(180, 0, 0 ) ); // Red
								gfx.fill(armTrim);
								
								gfx.translate(-offsetX, -offsetY);
								gfx.rotate(-angles[2]);
								
								// Legs
								{
									gfx.rotate(angles[1]);
									
									
									GeneralPath leg = new GeneralPath();
									leg.moveTo(10, 0);
									leg.lineTo(-120, 20);
									leg.lineTo(-120, 45);
									leg.lineTo(10, 5);
									
									GeneralPath legTrim = new GeneralPath();
									legTrim.moveTo(-120, 20);
									legTrim.lineTo(-100, 25);
									legTrim.lineTo(-115, 30);
									legTrim.lineTo(-100, 33);
									legTrim.lineTo(-120, 45);
									
									// Back leg
									gfx.translate(offsetX, offsetY);
									
									gfx.setColor( darkPurple );
									gfx.fill(leg);
									
									gfx.setColor( new Color(200, 200, 0) );
									gfx.fill(legTrim);
									
									gfx.translate(-offsetX, -offsetY);
									
									// Front leg
									gfx.setColor(purple);
									gfx.fill(leg);
									
									gfx.setColor( new Color(255, 255, 0) );
									gfx.fill(legTrim);
									
									gfx.rotate(-angles[1]);
								}
								
								// Front arm
								{
									gfx.rotate(angles[2]);
									
									gfx.setColor(white);
									gfx.fill(arm);
									
									gfx.setColor( new Color(220, 0, 0 ) ); // red
									gfx.fill(armTrim);
									
									gfx.rotate(-angles[2]);
								}
								
								// Head
								{
									gfx.rotate(angles[0]);
									
									final int headRadius = 20;
									
									// Hat
									GeneralPath spike = new GeneralPath();
									spike.moveTo(14, -14);
									spike.lineTo(-20, -35);
									spike.lineTo(-80, -25);
									spike.lineTo(-35, -15);
									spike.lineTo(-15,  10);
									
									
									gfx.setColor( darkPurple );
									gfx.translate(offsetX, offsetY);
									gfx.fill(spike);
									gfx.translate(-offsetX, -offsetY);
									
									gfx.setColor( purple );
									gfx.fill(spike);
									
									// Head
									gfx.setColor( new Color(255, 210, 180) );
									gfx.fillOval(-headRadius, -headRadius, headRadius*2, headRadius*2);
									
									// Eye
									GeneralPath eye = new GeneralPath();
									eye.moveTo(-10, -10);
									eye.quadTo(12, -6, +12, +12);
									eye.quadTo(-6, +12, -10, -10);
									
									gfx.setColor(white);
									gfx.fill(eye);
									
									gfx.setColor(black);
									gfx.fillOval(0, 0, 8, 8);
									
									gfx.rotate(-angles[0]);
								}
							
								gfx.translate(-player_x, -player_y);
							} // end player drawing
						}
					}
					
					// Undo camera translation
					gfx.translate(-camTranslateX, -camTranslateY);
				}
				
				Font bigFont = gfx.getFont().deriveFont(3, 80f);
				Font mediumFont = gfx.getFont().deriveFont(3, 30f);
				Font smallFont = gfx.getFont().deriveFont(3, 20f);
				
				// Now draw HUD
				
				gfx.setFont(smallFont);
				gfx.setColor(darkPurple);
				
				gfx.drawString("Score", 20, 30);
				gfx.drawString(String.valueOf(level_score), 80, 30);
				
				gfx.drawString("Time", 350, 30);
				gfx.drawString(String.valueOf(level_timeLeft/60), 410, 30);
				
				gfx.drawString("Chain", 700, 30);
				gfx.drawString(String.valueOf(chain_currentChain), 760, 30);
				
				gfx.drawString("Wave", 360, 60);
				gfx.drawString(String.valueOf(level_completeLaps+1), 420, 60);
				
				
				gfx.setFont(bigFont);
				
				if (state == TITLE_STATE)
				{
					// TODO: Can we optimise this?
					
					int titleX = 210;
					int titleY = 200;
					int thickness = 8;
					for (int ix=-thickness; ix<thickness; ix+=2)
					{
						for (int iy=-thickness; iy<thickness; iy+=2)
						{
							gfx.setColor(white);
							gfx.drawString("NiGHTS 4K", titleX-ix, titleY+iy);
						}
					}
					
					gfx.setColor(darkPurple);
					gfx.drawString("NiGHTS 4K", 210, 200);
					{
						GeneralPath dimond = new GeneralPath();
						float x = 287;
						float y = 136;
						float s = 12;
						dimond.moveTo(x, y);
						dimond.lineTo(x+s, y+s);
						dimond.lineTo(x, y+s*2);
						dimond.lineTo(x-s, y+s);
						dimond.lineTo(x, y);
						
						gfx.setColor( new Color(255, 0, 0) );
						gfx.fill(dimond);
					}
					
					// Level text
					gfx.setFont(mediumFont);
					for (int i=0; i<NUM_LEVELS; i++)
					{
						final int h = CHOOSE_LEVEL_START + CHOOSE_LEVEL_INC*i;
						if (m[1] < h && m[1] > h - 50)
							gfx.setColor(purple);
						else
							gfx.setColor(darkPurple);
						gfx.drawString("Dream", 280, h);
						gfx.drawString(String.valueOf(i+1), 390, h);
						gfx.drawString(String.valueOf(level_highScores[i]), 540, h);
					}
				}
				else if (state == FINISH_STATE)
				{
					gfx.drawString("Finished!", 240, 300);
				}
				else if (state == GAME_OVER)
				{
					gfx.drawString("Game Over!", 180, 300);
				}

				if (getGraphics() != null)
					getGraphics().drawImage(b, 0, 0, null);
			}
			
			/****** Timing delay ******/
			try
			{
				Thread.sleep(16);
			}
			catch (Exception e) {}
		}
	}
	
    public void processEvent(AWTEvent e)
    {
    	MouseEvent me = (MouseEvent)e;
    	
    	if (e.getID() == MouseEvent.MOUSE_PRESSED)
    	{
        	m[2] = me.getButton();
    	}
    	
    	m[0] = me.getX();
    	m[1] = me.getY();
    }
    
}