// dotslash.class
// dotslash.java

// Imports
import java.applet.*;
import java.awt.*;
import java.awt.geom.Line2D;
import java.util.Random;

public class dotslash extends Applet implements Runnable {
	Thread thread;
	MediaTracker mt;
	final int MAX_DOTS = 32;
	int nextLevel;
	final int RELEASE_SPEED = 10;	//How many thingiesKilled before adding another dot
	dot d[] = new dot[MAX_DOTS];
	Image enmImgs[] = new Image[1];	//right now 1 frame for enemy
	Image enmExpl[] = new Image[8];	//8 frames for explode
	Image dieExpl[] = new Image[9]; //9 frames for die
	Image station;	// Or whatever you're supposed to defend
	Graphics buffer;	//double buffering
	Image bufferimage;	//double buffering
	int randSeed = 0;
	int numActive;
	int level;
	int oldLevel;
	int score;
	int thingiesKilled;
	int headx, heady, tailx, taily;
	int lastDirection;
	boolean paused;
	int dieCounter;
	String debugString = "";

	final int DOWN_RIGHT = 0;
	final int DOWN = 1;
	final int DOWN_LEFT = 2;
	final int LEFT = 3;
	final int UP_LEFT = 4;
	final int UP = 5;
	final int UP_RIGHT = 6;
	final int RIGHT = 7;

	public void init() {
		mt = new MediaTracker(this);
		enmImgs[0] = getImage(getDocumentBase(), "res/rock.gif");
		mt.addImage(enmImgs[0], 0);
		for(int i=0; i<8; i++) {
			enmExpl[i] = getImage(getDocumentBase(), "res/explode" + i + ".gif");
			mt.addImage(enmExpl[i], 0);
		}
		for(int i=0; i<9; i++) {
			dieExpl[i] = getImage(getDocumentBase(), "res/die" + i + ".gif");
			mt.addImage(dieExpl[i], 0);
		}
		station = getImage(getDocumentBase(), "res/station.gif");
		mt.addImage(station, 0);
		for(int i=0; i<MAX_DOTS; i++) {
			d[i] = new dot(new Point(100, 100), new Point(1, 0), 1);
		}
		headx = heady = tailx = taily = 0;
		numActive = 0;
		level = 0;
		thingiesKilled = 0;
		lastDirection = 0;
		nextLevel = 0;
		paused = false;
		score = 0;
		oldLevel = 0;
		dieCounter = 0;
		debugString += "init called ";
	}

	public void start() {
		if(thread == null) {
			thread = new Thread(this);
			thread.start();
		}
		thread.run();
		debugString += "start called ";
	}

	public void stop() {
		if(thread != null) {
			thread.stop();
			thread = null;
		}
		debugString += "stopped ";
	}

	public void run() {

		/*repaint();
		try {
			mt.waitForID(0);
		}
		catch(InterruptedException e) {
			System.out.println(e.getMessage());
			return;
		}*/
		while((Thread.currentThread() == thread) || true) {
				debugString += "Run like the wind! ";

	//Update everything
		if(paused) repaint();
		long t = System.currentTimeMillis();
		while(Thread.currentThread() == thread) {
			for(int i=0; i<MAX_DOTS; i++) {
				d[i].updateMe();
			}
	// Spawn a new dot
			Random numGen = new Random(randSeed++);
			for(int i=0; numActive < level && i<MAX_DOTS; i++) {
				if(d[i].getActive() == false) {
					int direction = (numGen.nextInt(32) + 5 ^ 346245 / 9 * 3 ) % 8;
					while(direction == lastDirection) {
						randSeed++;
						direction = (numGen.nextInt(32) + 5 ^ 346245 / 9 * 3 ) % 8;
					}
					lastDirection = direction;
					switch(direction) {
						case DOWN_RIGHT:
							d[i].spawn(new Point(0 + numGen.nextInt(30) - 15, 0 + numGen.nextInt(30) - 15), new Point(1, 1), i);
							randSeed++;
							break;
						case DOWN:
							d[i].spawn(new Point(250 + numGen.nextInt(30) - 15, -20 + numGen.nextInt(30) - 20), new Point(0, 1), i);
							break;
						case DOWN_LEFT:
							d[i].spawn(new Point(500 + numGen.nextInt(30) - 15, 0 + numGen.nextInt(30) - 15), new Point(-1, 1), i);
							break;
						case LEFT:
							d[i].spawn(new Point(500 + numGen.nextInt(30) - 15, 250 + numGen.nextInt(30) - 15), new Point(-1, 0), i);
							break;
						case UP_LEFT:
							d[i].spawn(new Point(500 + numGen.nextInt(30) - 15, 500 + numGen.nextInt(30) - 15), new Point(-1, -1), i);
							break;
						case UP:
							d[i].spawn(new Point(250 + numGen.nextInt(30) - 15, 500 + numGen.nextInt(30) - 15), new Point(0, -1), i);
							break;
						case UP_RIGHT:
							d[i].spawn(new Point(0 + numGen.nextInt(30) - 15, 500 + numGen.nextInt(30) - 15), new Point(1, -1), i);
							randSeed++;
							break;
						case RIGHT:
							d[i].spawn(new Point(-20 + numGen.nextInt(30) - 20, 250 + numGen.nextInt(30) - 15), new Point(1, 0), i);
							break;
					}
					numActive++;
				}
				randSeed++;
			}

			//Collision detection
			for(int i=0; i<MAX_DOTS; i++) {
				if((d[i].isAlive()) && (new Rectangle(250-24, 250-19, 48, 38).intersects(d[i].getPosition()))) {
					gameOver();
				}
			}
			//Die counter
			if(dieCounter < 50 && dieCounter != 0) {
				dieCounter++;
			}

			repaint();
			try {
				t += 35;
				thread.sleep(Math.max(0, t-System.currentTimeMillis()));
			}
			catch (InterruptedException e) {
				System.out.println(e.getMessage());
				break;
			}
		}
		}
	}

	public void newGame() {
		headx = heady = tailx = taily = 0;
		numActive = 0;
		level = 1;
		thingiesKilled = 0;
		lastDirection = 0;
		nextLevel = RELEASE_SPEED;
		paused = false;
		score = 0;
		oldLevel = 1;
		dieCounter = 0;
	}

	public boolean mouseDown(Event evt, int x, int y) {
		if(level == 0 && dieCounter == 0)
			newGame();
		headx = x;
		heady = y;
		tailx = x;
		taily = y;
		return true;
	}

	public boolean mouseDrag(Event evt, int x, int y) {
		tailx = x;
		taily = y;
		return true;
	}

	public boolean mouseUp(Event evt, int x, int y) {
		Line2D.Float slash = new Line2D.Float(headx, heady, tailx, taily);
		int thingiesHit = 0;
		for(int i=0; i<MAX_DOTS; i++) {
			if(d[i].isAlive() && slash.intersects(d[i].getPosition())) {
				d[i].die();
				numActive--;
				thingiesHit++;
				if(++thingiesKilled >= nextLevel) {
					level++;
					oldLevel = level;
					nextLevel += RELEASE_SPEED * level;
				}
			}
		}
		score += thingiesHit * thingiesHit;
		headx = tailx;
		heady = taily;
		return true;
	}

	public void paint(Graphics g) {
		Font f = new Font("Helvetica", Font.BOLD, 22);
		FontMetrics fm = g.getFontMetrics(f);
		String str = new String("Loading...");
		Color white = new Color(1.0f, 1.0f, 1.0f);
		Color black = new Color(0.0f, 0.0f, 0.0f);

		g.setColor(black);
		g.fillRect(0, 0, size().width, size().height);
		if((mt.statusID(0, true) & mt.COMPLETE) == 0) {
			g.setColor(white);
			g.setFont(f);
			g.drawString(str, (size().width - fm.stringWidth(str)) / 2,
				((size().height - fm.getHeight()) / 2) + fm.getAscent());
		}

		update(g);
		debugString += "paint called ";
	}

	public void update(Graphics g) {
		if(buffer == null) {
			bufferimage = createImage(size().width, size().height);
			buffer = bufferimage.getGraphics();
		}
		Font f = new Font("Helvetica", Font.BOLD, 22);
		FontMetrics fm = buffer.getFontMetrics(f);
		Font smallFont = new Font("Helvetica", Font.BOLD, 8);
		FontMetrics fmsm = buffer.getFontMetrics(smallFont);
		String str = new String("DotSlash Beta 1");
		String start = new String("Click to start");
		Color white = new Color(1.0f, 1.0f, 1.0f);
		Color black = new Color(0.0f, 0.0f, 0.0f);

		buffer.setColor(black);
		buffer.fillRect(0, 0, size().width, size().height);

		if(((mt.statusID(0, true) & mt.COMPLETE) == 0 || level == 0) && (dieCounter == 0 || dieCounter >= 40)) {
			buffer.setColor(white);
			buffer.setFont(f);
			buffer.drawString(str, (size().width - fm.stringWidth(str)) / 2,
				((size().height - fm.getHeight()) / 2) + fm.getAscent());
			buffer.setFont(smallFont);
			buffer.drawString(start, (size().width - fmsm.stringWidth(start)) / 2,
				((size().height - fmsm.getHeight()) / 2) + fmsm.getAscent() + 17);
			buffer.drawString("Level: " + oldLevel + "  Destroyed: " + thingiesKilled + "  Next level: " + nextLevel
			+ "  Score: " + score, 5, 15);
		}
		else {
			if(dieCounter > 0 && dieCounter < 10) {
				buffer.drawImage(dieExpl[dieCounter-1], 250-24, 250-24, this);
			}
			else if(dieCounter == 0) {
				buffer.drawImage(station, 250-24, 250-24, this);
			}

			for(int i=0; i<MAX_DOTS; i++) {
				if(d[i].getActive() == true && d[i].isAlive()) {
					buffer.drawImage(enmImgs[0], d[i].getPosition().x, d[i].getPosition().y, this);
				}
				else if(d[i].getActive() == true && !d[i].isAlive()) {
					buffer.drawImage(enmExpl[d[i].getFrameIndex()], d[i].getPosition().x, d[i].getPosition().y, this);
				}
			}
			buffer.setColor(white);
			buffer.setFont(smallFont);
			buffer.drawString("Level: " + oldLevel + "  Destroyed: " + thingiesKilled + "  Next level: " + nextLevel
			+ "  Score: " + score, 5, 15);
		}
		if(headx != tailx && heady != taily) {
			buffer.setColor(white);
			buffer.drawLine(headx, heady, tailx, taily);
		}
		debugString += "Update called ";
		buffer.setColor(white);
		buffer.setFont(smallFont);
		buffer.drawString(debugString, 10, 40);
		g.drawImage(bufferimage, 0, 0, null);
	}

	public void gameOver() {
		for(int i=0; i<MAX_DOTS; i++) {
			d[i].die();
		}
		oldLevel = level;
		dieCounter = 1;
		level = 0;
	}
}

// dot class
class dot {	
	protected int frameIndex, zOrder;
	protected boolean active;
	protected int hp, dieCount;
	protected Rectangle myRect;
	protected Point velocity;

	public dot() {
		myRect = new Rectangle(250, 250, 29, 28);
		velocity = new Point(0, 0);
		frameIndex = 0;
		hp = 1;
		active = false;
	}

	public dot(Point pos, Point vel, int z) {
		setPosition(new Rectangle(pos.x, pos.y, 29, 28));
		setVelocity(vel);
		frameIndex = 0;
		zOrder = z;
		dieCount = 0;
		hp = 1;
		active = false;
	}

	public void spawn(Point pos, Point vel, int z) {
		setPosition(pos);
		setVelocity(vel);
		zOrder = z;
		active = true;
		hp = 1;
		dieCount = 0;
		frameIndex = 0;
	}

	public void setPosition(Rectangle pos) {
		myRect = pos;
	}

	public void setPosition(Point pos) {
		myRect.move(pos.x, pos.y);
	}

	public Rectangle getPosition() {
		return myRect;
	}

	public Point getVelocity() {
		return velocity;
	}

	public void setVelocity(Point vel) {
		velocity = vel;
	}

	public boolean isPointInside(Point p) {
		return myRect.inside(p.x, p.y);
	}

	public boolean isAlive() {
		if(dieCount == 0 && active == true)
			return true;
		else
			return false;
	}

	public boolean getActive() {
		return active;
	}

	public int getFrameIndex() {
		return frameIndex;
	}

	public void updateMe() {
	//Check if it's supposed to be updating
		if(!active)
			return;
	//Change frame for animation
		frameIndex++;
		if(frameIndex == 1 && dieCount == 0)	//Switch frame for moving
			frameIndex = 0;
		else if(frameIndex >= 8 && dieCount != 0)	//If it's exploding, use that instead
			frameIndex = 0;
	//Move
		if(dieCount == 0) {	//Means it's alive
			myRect.x += velocity.x;
			myRect.y += velocity.y;
		}
		else {			//Means it's dead but exploding
			dieCount++;
			if(dieCount >= 10) {
				dieCount = 0;
				active = false;
			}
		}
	}

	public void die() {
		dieCount = 1;
		frameIndex = -1;
	}
}
