package client;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.*;
import java.rmi.*;
import remifc.*;

public class SlimeV5 extends JPanel implements Runnable, Constants {
	int nWidth;
	int nHeight;
	int nPointsScored;
	String promptMsg;
	boolean fInPlay;
	boolean fEndGame;
	long startTime;
	long gameTime;
	Side sides[];
	Player players[];
	Ball balls[];

	int localPlayer;

	/***********************************************************************/

	public SlimeV5(SlimeEndpoint se, int local_player) {
		PlayerController pc = null;

		/* Player information */
		this.localPlayer = local_player;

		/* 1 ball */
		balls = new Ball[1];
		balls[0] = new Ball(this, 200, 400);

		/* 2 teams */
		sides = new Side[2];
		sides[0] = new Side(this, true, 50, 445, Color.red, "Big Red Slime");
		sides[1] =
			new Side(this, false, 555, 950, Color.green, "Magic Green Slime");

		/* 1 player on each team */
		players = new Player[2];
		if (local_player == 0) {
			players[0] = new LocalNetworkPlayer(sides[0], 200, se);
			pc =
				new KeyboardPlayerController(
					KeyEvent.VK_A,
					KeyEvent.VK_D,
					KeyEvent.VK_W);
			pc.assignToPlayer(players[0]);
		} else {
			players[0] = new RemoteNetworkPlayer(sides[0], 200, se);
		}

		if (local_player == 1) {
			players[1] = new LocalNetworkPlayer(sides[1], 800, se);
			pc =
				new KeyboardPlayerController(
					KeyEvent.VK_J,
					KeyEvent.VK_L,
					KeyEvent.VK_I);
			pc.assignToPlayer(players[1]);
		} else {
			players[1] = new RemoteNetworkPlayer(sides[1], 800, se);
		}

		/* Add a mouse listener to detect start-of-game */
		addMouseListener(new SlimeMouseInput());

		/* Receive notifications when the playing window is resized */
		addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				Dimension size;
				size = getSize();
				if (size.width != nWidth || size.height != nHeight) {
					nWidth = size.width;
					nHeight = size.height;
				}
			}
		});
	}

	/***********************************************************************/

	public Dimension getPreferredSize() {
		return new Dimension(640, 480);
	}

	/***********************************************************************/

	public int getGroundPos(int height) {
		return (4 * height) / 5;
	}

	public int getGroundSize(int height) {
		return height / 5;
	}

	public int getNetPos(int height) {
		return (7 * height) / 10;
	}

	public int getNetHeight(int height) {
		return height / 10 + 5;
	}

	public int getNetWidth(int width) {
		return 4;
	}

	/***********************************************************************/

	/* Mouse input used to start the game */

	class SlimeMouseInput extends MouseAdapter {
		public void mousePressed(MouseEvent e) {
			if (!fInPlay) {
				startGame();
			}
		}
	}

	/***********************************************************************/

	public void drawStringCentered(Graphics g, String s, int y) {
		FontMetrics fontmetrics = g.getFontMetrics();

		g.drawString(s, (nWidth / 2) - (fontmetrics.stringWidth(s) / 2), y);
	}

	/***********************************************************************/

	public void drawBackground(Graphics g) {
		int ground_pos;
		int ground_size;
		int net_pos;
		int net_height;
		int net_width;

		ground_pos = getGroundPos(nHeight);
		ground_size = getGroundSize(nHeight);
		net_pos = getNetPos(nHeight);
		net_height = getNetHeight(nHeight);
		net_width = getNetWidth(nWidth);

		/* Sky */
		g.setColor(Color.blue);
		g.fillRect(0, 0, nWidth, ground_pos);

		/* Ground */
		g.setColor(Color.gray);
		g.fillRect(0, ground_pos, nWidth, nHeight / 5);

		/* Net */
		g.setColor(Color.white);
		g.fillRect(
			nWidth / 2 - (net_width / 2),
			net_pos,
			net_width,
			net_height);
	}

	/***********************************************************************/

	public void drawStartScreen(Graphics g) {
		FontMetrics fm;

		g.setFont(new Font(g.getFont().getName(), 1, 15));
		g.setColor(Color.white);
		fm = g.getFontMetrics();
		drawStringCentered(
			g,
			"Slime Volleyball!",
			nHeight / 2 - fm.getHeight());

		g.setFont(new Font(g.getFont().getName(), 1, 12));
		g.setColor(Color.white);
		fm = g.getFontMetrics();
		drawStringCentered(
			g,
			"Original by Quin Pendragon",
			nHeight / 2 + fm.getHeight() * 2);
	}

	/***********************************************************************/

	public void drawPrompt(Graphics g) {
		FontMetrics fm = g.getFontMetrics();
		g.setColor(Color.lightGray);
		drawStringCentered(
			g,
			promptMsg,
			(nHeight * 4) / 5 + fm.getHeight() + 10);
	}

	/***********************************************************************/

	void drawInfo(Graphics g) {
		FontMetrics fm = g.getFontMetrics();
		int i = nHeight / 20;
		g.setColor(Color.blue);
		int j = nWidth / 2 + ((sides[0].score - 5) * nWidth) / 24;

		String s =
			("Points: "
				+ nPointsScored
				+ "   Elapsed: "
				+ microsecsToTime(gameTime));

		int k = fm.stringWidth(s);

		g.fillRect(j - k / 2 - 5, 0, k + 10, i + 22);
		g.setColor(Color.white);
		g.drawString(s, j - k / 2, fm.getAscent() + 20);
	}

	/***********************************************************************/

	void drawScores(Graphics g) {
		int i;
		int k = nHeight / 20;

		g.setColor(Color.blue);
		g.fillRect(0, 0, nWidth, k + 22);
		for (i = 0; i < sides.length; i++) {
			sides[i].drawScoreOnto(g);
		}
	}

	/***********************************************************************/

	public void drawParticipants(Graphics g) {
		int i;
		for (i = 0; i < players.length; i++) {
			players[i].drawOnto(g);
		}
		for (i = 0; i < balls.length; i++) {
			balls[i].drawOnto(g);
		}
	}

	/***********************************************************************/

	public void paintComponent(Graphics g) {
		/* Draw sky and ground */
		drawBackground(g);

		/* Draw other details */
		drawScores(g);
		drawPrompt(g);

		if (!fInPlay) {
			drawStartScreen(g);
		} else {
			drawParticipants(g);
			drawInfo(g);
		}
	}

	/***********************************************************************/

	String microsecsToTime(long l) {
		long l1 = (l / 10L) % 100L;
		long l2 = (l / 1000L) % 60L;
		long l3 = (l / 60000L) % 60L;
		long l4 = l / 0x36ee80L;
		String s = "";
		if (l4 < 10L)
			s += "0";
		s += l4;
		s += ":";
		if (l3 < 10L)
			s += "0";
		s += l3;
		s += ":";
		if (l2 < 10L)
			s += "0";
		s += l2;
		s += ":";
		if (l1 < 10L)
			s += "0";
		s += l1;
		return s;
	}

	/***********************************************************************/

	public void updateParticipantsState(int round) {
		int i;
		for (i = 0; i < players.length; i++) {
			players[i].updateState(round);
		}
		for (i = 0; i < balls.length; i++) {
			balls[i].updateState();
		}
	}

	/***********************************************************************/

	public boolean ballLostAt(Ball b, int lost_at) {
		boolean game_over;
		boolean ball_touched;
		int i;

		/* Updates scores */
		nPointsScored++;
		if (lost_at <= 500) {
			sides[0].awardPoints(-1);
			sides[1].awardPoints(1);
		} else {
			sides[0].awardPoints(1);
			sides[1].awardPoints(-1);
		}

		/* Check if any side touched the ball or if any side is out
		 * of lives. */
		ball_touched = false;
		game_over = false;
		for (i = 0; i < sides.length; i++) {
			ball_touched |= sides[i].didTouchBall();
			game_over |= (sides[i].getScore() == 0);
		}

		/* Build prompt */
		promptMsg =
			(lost_at <= 500 ? sides[1].toString() : sides[0].toString());
		promptMsg += " ";

		if (!ball_touched)
			promptMsg = "What can I say?";
		else if (
			sides[0].scoringRun == SCORING_RUN_FOR_SUPER
				|| sides[1].scoringRun == SCORING_RUN_FOR_SUPER)
			promptMsg += "is on fire!";
		else if (
			(lost_at > 500 && sides[0].touched && !sides[1].touched)
				|| (lost_at <= 500 && !sides[0].touched && sides[1].touched))
			promptMsg += "aces the serve!";
		else if (
			(lost_at > 500 && !sides[0].touched && sides[1].touched)
				|| (lost_at <= 500 && sides[0].touched && !sides[1].touched))
			promptMsg += "dies laughing! :P";
		else
			switch (sides[0].score) {
				case 0 :
				case 10 :
					if (nPointsScored == 5)
						promptMsg += "Wins with a QUICK FIVE!!!";
					else if (
						sides[0].scoringRun == 8 || sides[1].scoringRun == 8)
						promptMsg += "Wins with a BIG NINE!!!";
					else
						promptMsg += "Wins!!!";
					break;

				case 4 :
					promptMsg += (lost_at >= 500)
						? "Scores!"
						: "takes the lead!!";
					break;

				case 6 :
					promptMsg += (lost_at <= 500)
						? "Scores!"
						: "takes the lead!!";
					break;

				case 5 :
					promptMsg += "Equalizes!";
					break;

				default :
					promptMsg += "Scores!";
					break;
			}

		/* Update the prompt */
		repaint();
		try {
			Thread.sleep(2500L);
		} catch (InterruptedException _ex) {
		}
		promptMsg = "";
		repaint();

		/* Restore state for the next point */
		if (!game_over) {
			for (i = 0; i < players.length; i++) {
				players[i].resetState();
			}
			for (i = 0; i < balls.length; i++) {
				balls[i].resetState();
			}
			b.ballX = (lost_at >= 500) ? 200 : 800;
			repaint();
		}

		return game_over;
	}

	/***********************************************************************/

	public void run() {
		boolean game_over = false;
		int i;
		int computed = 0;
		int round = 0;

		startTime = System.currentTimeMillis();
		try {
			while (!game_over) {
				gameTime = System.currentTimeMillis() - startTime;

				/* Update the internal state of the players */
				updateParticipantsState(round);

				/* Update the display on screen */
				repaint();

				/* Check if any of the balls have hit the floor */
				for (i = 0; i < balls.length; i++) {
					boolean is_lost;
					Ball b;

					b = balls[i];
					is_lost = b.isLost();
					if (is_lost) {
						int lost_at = b.ballX;
						long l;

						l = System.currentTimeMillis();
						game_over = ballLostAt(b, lost_at);
						startTime += System.currentTimeMillis() - l;
					}

					round++;
				}

				Thread.sleep(MS_PER_ROUND);
			}
		} catch (InterruptedException ie) {
			System.err.println("Game interrupted");
		}
		fEndGame = true;
		fInPlay = false;
		promptMsg = "Click the mouse to play...";
		repaint();
	}

	/***********************************************************************/

	public void startGame() {
		Thread game_thread;

		fEndGame = false;
		fInPlay = true;
		nPointsScored = 0;
		promptMsg = "";
		gameTime = 0;
		for (int i = 0; i < sides.length; i++) {
			sides[i].resetState();
		}
		for (int i = 0; i < players.length; i++) {
			players[i].resetState();
		}
		for (int i = 0; i < balls.length; i++) {
			balls[i].resetState();
		}
		repaint();
		game_thread = new Thread(this);
		game_thread.start();
	}

	/***********************************************************************/

	public static void main(String args[]) {
		SlimeV5 p;
		Container c;
		JFrame f;
		Graphics g;
		Dimension size;
		SlimeEndpoint se = null;
		int local_player_number = 0;

		try {
			/* Find opponent using the RMI service */
			System.out.println("Looking for " + PlayerFinder.NAME);
			PlayerFinder pf = (PlayerFinder) Naming.lookup(PlayerFinder.NAME);
			ServerSocket ss =
				new ServerSocket(0, 1, InetAddress.getLocalHost());
			PlayerLocation my_location =
				new PlayerLocation(ss.getInetAddress(), ss.getLocalPort());
			System.out.println("My location is " + my_location);
			PlayerLocation op_location = pf.getOpponent(my_location);
			System.out.println("Opponent location is " + op_location);

			/* 
			 * If we've got the lower address then wait for our opponent to
			 * connect to us.  
			 */
			if (my_location.isLessThan(op_location)) {
				se = new SlimeEndpoint(ss);
				local_player_number = 0;
			} else {
				se =
					new SlimeEndpoint(
						op_location.getPlayerAddr(),
						op_location.getPlayerPort());
				local_player_number = 1;
			}
		} catch (Exception e) {
			System.err.println("Caught " + e);
			e.printStackTrace();
			System.exit(1);
		}

		p = new SlimeV5(se, local_player_number);
		f = new JFrame();
		c = f.getContentPane();
		c.add(p);
		f.pack();
		size = p.getSize();
		p.nWidth = size.width;
		p.nHeight = size.height;
		p.fInPlay = p.fEndGame = false;
		p.promptMsg = "Click the mouse to play...";
		f.show();
		p.requestFocus();
		p.setDoubleBuffered(true);
	}
}
