import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;


public class TwoTron extends JPanel {
	private static final int WIDTH = 640;
	private static final int HEIGHT = 480;
	
	public static final int CELL_BLANK = 0;
	public static final int CELL_CLIENT = 1;
	public static final int CELL_SERVER = 2;
	public static final int CELL_BORDER = 3;
	
	public static final int SIGNAL_STRAIGHT = 0;
	public static final int SIGNAL_LEFT = 1;
	public static final int SIGNAL_RIGHT = 2;
	
	public static final int SERVER_PORT = 5643;
	
	public static final int SPEED_MULTIPLIER = 5;
	
	private int[][] grid;
	private Snake clientPlayer;
	private Snake serverPlayer;
	private boolean server;
	private int pendingTurn;
	private boolean lost, won;
	
	public static void main(String[] args) {
		JFrame frame = new JFrame("Two-player Tron Game");
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.setLocationRelativeTo(null);
		frame.setSize(WIDTH, HEIGHT);

		TwoTron panel = new TwoTron();

		panel.setBackground(Color.black);

		frame.setContentPane(panel);

		frame.setVisible(true);
		
		panel.initThreads();
	}
	
	public TwoTron() {
		grid = new int[WIDTH][HEIGHT];
		for (int i = 0; i < WIDTH; i++) {
			grid[i][0] = CELL_BORDER;
			grid[i][HEIGHT - 1] = CELL_BORDER;
		}
		for (int j = 0; j < HEIGHT; j++) {
			grid[0][j] = CELL_BORDER;
			grid[WIDTH - 1][j] = CELL_BORDER;
		}
		clientPlayer = new Snake(WIDTH / 2 - 15, HEIGHT / 2, Color.red, new Point(0, 1));
		serverPlayer = new Snake(WIDTH / 2 + 15, HEIGHT / 2, Color.blue, new Point(0, 1));
	}
	
	public void initThreads() {
		JPanel firstPanel = new JPanel();
		BoxLayout box = new BoxLayout(firstPanel, BoxLayout.Y_AXIS);
		firstPanel.setLayout(box);
		
		JRadioButton clientOpt = new JRadioButton("Client");
		firstPanel.add(clientOpt);
		JRadioButton serverOpt = new JRadioButton("Server");
		firstPanel.add(serverOpt);
		
		ButtonGroup clientOrServer = new ButtonGroup();
		clientOrServer.add(clientOpt);
		clientOrServer.add(serverOpt);
		
		JOptionPane.showMessageDialog(this, firstPanel);
		
		if (clientOpt.isSelected()) {
			server = false;
		} else {
			server = true;
		}
		
		if (server) {
			Thread serverThread = new Thread(new Runnable() {
				public void run() {
					try {
						ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
						Socket socket = serverSocket.accept();
						serverSocket.close();
						
						InputStream in = socket.getInputStream();
						OutputStream out = socket.getOutputStream();
						
						while (!lost && !won) {
							out.write(pendingTurn);
							pendingTurn = SIGNAL_STRAIGHT;
							
							for (int i = 0; i < SPEED_MULTIPLIER; i++) {
								Point newPosClient = clientPlayer.move();
								Point newPosServer = serverPlayer.move();
								
								if (grid[newPosClient.x][newPosClient.y] != CELL_BLANK) {
									won = true;
									break;
								} else {
									grid[newPosClient.x][newPosClient.y] = CELL_CLIENT;
								}
								
								if (grid[newPosServer.x][newPosServer.y] != CELL_BLANK) {
									lost = true;
									break;
								} else {
									grid[newPosServer.x][newPosServer.y] = CELL_SERVER;
								}
							}
							
							int otherPlayerTurn = in.read();
							if (otherPlayerTurn == SIGNAL_LEFT) {
								clientPlayer.turnLeft();
							} else if (otherPlayerTurn == SIGNAL_RIGHT) {
								clientPlayer.turnRight();
							}
							repaint();
							Thread.sleep(20);
						} 
						in.close();
						out.close();
						socket.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
			serverThread.start();
		} else {
			JPanel addressPanel = new JPanel(new GridLayout(2, 2));
			
			addressPanel.add(new JLabel("Address:"));
			JTextField addressText = new JTextField("128.227.176.52");
			addressPanel.add(addressText);
			addressPanel.add(new JLabel("Port:"));
			JTextField portText = new JTextField(String.valueOf(SERVER_PORT));
			addressPanel.add(portText);
			
			JOptionPane.showMessageDialog(this, addressPanel);
			final String address = addressText.getText();
			final int port = Integer.parseInt(portText.getText());
			
			Thread clientThread = new Thread(new Runnable() {
				public void run() {
					try {
						Socket socket = new Socket(address, port);
						InputStream in = socket.getInputStream();
						OutputStream out = socket.getOutputStream();
						
						while (!lost && !won) {
							out.write(pendingTurn);
							pendingTurn = SIGNAL_STRAIGHT;
							
							for (int i = 0; i < SPEED_MULTIPLIER; i++) {
								Point newPosClient = clientPlayer.move();
								Point newPosServer = serverPlayer.move();
							
								if (grid[newPosClient.x][newPosClient.y] != CELL_BLANK) {
									lost = true;
								} else {
									grid[newPosClient.x][newPosClient.y] = CELL_CLIENT;
								}
								
								if (grid[newPosServer.x][newPosServer.y] != CELL_BLANK) {
									won = true;
								} else {
									grid[newPosServer.x][newPosServer.y] = CELL_SERVER;
								}
							}
								
							int otherPlayerTurn = in.read();
							if (otherPlayerTurn == SIGNAL_LEFT) {
								serverPlayer.turnLeft();
							} else if (otherPlayerTurn == SIGNAL_RIGHT) {
								serverPlayer.turnRight();
							}
							repaint();
							Thread.sleep(20);
						}
						in.close();
						out.close();
						socket.close();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			});
			clientThread.start();
		}
		
		this.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON1) {
					if (server) {
						serverPlayer.turnLeft();
					} else {
						clientPlayer.turnLeft();
					}
					pendingTurn = SIGNAL_LEFT;
				} else {
					if (server) {
						serverPlayer.turnRight();
					} else {
						clientPlayer.turnRight();
					}
					pendingTurn = SIGNAL_RIGHT;
				}
			}
		});
		
		this.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_LEFT) {
					if (server) {
						serverPlayer.turnLeft();
					} else {
						clientPlayer.turnLeft();
					}
					pendingTurn = SIGNAL_LEFT;
				} else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
					if (server) {
						serverPlayer.turnRight();
					} else {
						clientPlayer.turnRight();
					}
					pendingTurn = SIGNAL_RIGHT;
				}
			}
		});
		this.grabFocus();
	}
	
	public void paint(Graphics g) {
		super.paint(g);
		for (int i = 0; i < WIDTH; i++) {
			for (int j = 0; j < HEIGHT; j++) {
				if (grid[i][j] == CELL_CLIENT) {
					g.setColor(clientPlayer.getColor());
				} else if (grid[i][j] == CELL_SERVER) {
					g.setColor(serverPlayer.getColor());
				} else if (grid[i][j] == CELL_BORDER) {
					g.setColor(Color.white);
				}
				if (grid[i][j] != CELL_BLANK) {
					g.drawLine(i, j, i, j);
				}
			}
		}
		
		if (won) {
			g.drawString("Congratulations!  You've Won!", WIDTH / 2 - 50, HEIGHT / 2);
		} else if (lost) {
			g.drawString("Sorry, you lost.", WIDTH / 2 - 50, HEIGHT / 2);
		}
	}
}

class Snake {
	private int x;
	private int y;
	private Color color;
	private Point direction;
	
	public Snake(int x, int y, Color color, Point direction) {
		this.x = x;
		this.y = y;
		this.color = color;
		this.direction = direction;
	}
	
	public void turnLeft() {
		if (direction.x == 0) {
			if (direction.y == 1) {
				direction.x = 1;
				direction.y = 0;
			} else {
				direction.x = -1;
				direction.y = 0;
			}
		} else if (direction.x == 1) {
			direction.x = 0;
			direction.y = -1;
		} else {
			direction.x = 0;
			direction.y = 1;
		}
	}
	
	public void turnRight() {
		if (direction.x == 0) {
			if (direction.y == 1) {
				direction.x = -1;
				direction.y = 0;
			} else {
				direction.x = 1;
				direction.y = 0;
			}
		} else if (direction.x == 1) {
			direction.x = 0;
			direction.y = 1;
		} else {
			direction.x = 0;
			direction.y = -1;
		}
	}
	
	public Point move() {
		x += direction.x;
		y += direction.y;
		return new Point(x, y);
	}
	
	public int getX() {
		return x;
	}
	
	public int getY() {
		return y;
	}
	
	public Color getColor() {
		return color;
	}
}

