import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import java.util.*;

public class TicTacToe  {     //A class just to startup the main window & do server-side init.
  static ServerSocket ssocket;
  static Socket csocket=null;
  static int size=3;

  public static void main(String[] args) {
    
    if (args.length==0) parseError();
    if(args[0].equals("-server"))
      if(args.length!=1) parseError();
      else serverStart();

    else {
      if(args.length!=2 && args.length!=4) parseError();
      if(args.length==4 && !args[2].equals("-size")) parseError();
      
      InetAddress i=null; int p=0;
      try {i=InetAddress.getByName(args[0]);}
      catch(Exception e) {System.out.println("Could not resolve hostname/IP."); System.exit(1);}
      try {p=Integer.valueOf(args[1]).intValue();}
      catch(Exception e) {System.out.println("Port number not valid."); System.exit(1);}
      if(args.length==4) {
	try {size=Integer.valueOf(args[3]).intValue();if(size<=0 || size>100) throw new Exception();}
	catch(Exception e) {System.out.println("Board size not valid"); System.exit(1);}
      }
      
      try {csocket=new Socket(i, p);}
      catch (IOException e) {System.out.println("Could not open connection to server."); System.exit(1);}

      try{                //Check and make sure the remote system is TicTacToe
	csocket.setSoTimeout(60000);
	PrintWriter oStream=new PrintWriter(csocket.getOutputStream(),true);
	BufferedReader iStream=new BufferedReader(new InputStreamReader(csocket.getInputStream()));
	String handshake=iStream.readLine();
	if(!handshake.equals("reset")) {                                //Check for correct response
	  System.out.println("Received unknown message: "+handshake+" from remote system.");
	  System.exit(1);
	}
	else {
	  oStream.println("ok");
	  if(size!=3) oStream.println("resetsize "+size);
	}
      }
      catch(Exception e) {System.out.println("Connection init error: "+e); System.exit(1);}

      MyWindow m=new MyWindow(size, csocket, false);
      m.show();
      
    }
  }
  
  static void parseError() {
    System.out.println("Usage: TicTacToe -server OR TicTacToe host port [-size s]\n"+
		       "\nAli's multiplayer TicTacToe client server app.\n"+
		       "Implements extra credit; See the README for more documentation.");
    System.exit(1);
  }

  //Starts up the server, waits for connections, sends reset, checks for appropriate
  //response. and then starts up the AWT.
  static void serverStart() {
    
    try { ssocket=new ServerSocket(4444);}
    catch (IOException e) { 
      try{ ssocket=new ServerSocket(0);
      System.out.println("Error: could not use port 4444; using port "+ssocket.getLocalPort()+" instead.");}
      catch (IOException f) {System.out.println("Fatal error: could not find an open port."); System.exit(1);}
    }
    String me;
    try { me=InetAddress.getLocalHost().getHostAddress();}
    catch(Exception e) {me="";System.out.println("Error: could not get local IP."); System.exit(1);}
    System.out.println("");
    System.out.println("TicTacToe server started on host "+me+":"+ssocket.getLocalPort());
    System.out.println("\nType \"java TicTacToe "+me+" "+ssocket.getLocalPort()+"\" to connect.");
    
    try { ssocket.setSoTimeout(300000);           //Set timeout.
    csocket=ssocket.accept();
    ssocket.close();
    System.out.println("Connection from "+csocket.getInetAddress().getHostName());}          //Accept connections.
    catch(IOException e) {
      csocket=null;
      System.out.println("Connection timeout or socket error.");
      System.exit(1);}
    try{                //Check and make sure the remote system is TicTacToe
      csocket.setSoTimeout(60000);             //Set a temp timeout for setup @ 60 secs.
      PrintWriter oStream=new PrintWriter(csocket.getOutputStream(),true);
      BufferedReader iStream=new BufferedReader(new InputStreamReader(csocket.getInputStream()));
      oStream.println("reset");
      String handshake=iStream.readLine();
      if(!handshake.equals("ok")) {                                //Check for correct response
	if(handshake.startsWith("error ")) {
	  System.out.println("Received error:"+handshake.substring(5)+" from remote system.");
	  System.exit(1);
	}
	else {
	  System.out.println("Received unknown message: "+handshake+" from remote system.");
	  System.exit(1);
	}
      }
    }
    catch(Exception e) {System.out.println("Connection init error: "+e); System.exit(1);}
    MyWindow m=new MyWindow(size, csocket, true);
    m.show();
  }

  static void clientStart() {

  }


}


class MyWindow extends Frame {  //The main program window.

  TicTac game;                 //Our data members--the two subframes.
  OpponentListener oList;      //A thread to listen on the network connection
  Button restartButton;
  Button sizeButton;
  TextField sizeField;
  MainListener L;
  Label label1;

  public MyWindow(int s, Socket socket, boolean isServer) {
    setTitle("TicTacToe");           //Do basic inits
    setLocation(200,200);
    setLayout(new BorderLayout());
    MainListener L=new MainListener();
    addWindowListener(L);

    try {socket.setSoTimeout(300000);}               //Set the correct timeout.
    catch(Exception e) {System.out.println("Socket error"); System.exit(1);}

    label1=new Label("");
    label1.setAlignment(Label.CENTER);

    game = new TicTac(s, socket, label1, isServer);   //Make tictactoe canvas
    game.addMouseListener(new GameListener());      //Add a mouse listener for the game.
    oList=new OpponentListener();                   //Start a thread to listen on the network.
    
    Panel utility=new Panel();
    Panel sizePane=new Panel();
    Panel bottom=new Panel();
    add("Center", game);
    add("South", utility);
    
    utility.setLayout(new BorderLayout());
    utility.add("North",bottom);
    utility.add("South",sizePane);
    bottom.setLayout(new BorderLayout());   //Our button panel.
    restartButton=new Button("Restart");
    sizeButton=new Button("Restart size");
    sizeField=new TextField(5);
    sizePane.add(sizeButton);
    sizePane.add(sizeField);

    if(isServer || s!=3) {restartButton.setEnabled(false);sizeButton.setEnabled(false);}
    Button b2=new Button("Quit");
    restartButton.addActionListener(L);       //this window will handle button events
    sizeButton.addActionListener(L);
    b2.addActionListener(L);
    bottom.add("West",restartButton);
    bottom.add("Center", label1);
    bottom.add("East",b2);
    pack();

    oList.setPriority(Thread.MIN_PRIORITY);
    oList.start();
  }

  //This class handles events for the main window:
  class MainListener extends WindowAdapter implements WindowListener, ActionListener {
    public void actionPerformed(ActionEvent e) {
      String cmd = e.getActionCommand();
      if(cmd.equals("Quit")) System.exit(1);
      
      if(cmd.startsWith("Restart")) {    

	if(cmd.equals("Restart")) {
	  game.setEnabled(false); restartButton.setEnabled(false);  //Disable components.
	  sizeButton.setEnabled(false);
	  game.awaitingAck=true; 
	  game.oStream.println("reset");
	}
	else {
	  int s=3;
	  try{
	    s=Integer.valueOf(sizeField.getText()).intValue();
	    if(s<0 || s>100) throw new Exception();
	    game.pendingX=-s;
	    game.pendingY=-s;
	    game.setEnabled(false); restartButton.setEnabled(false);  //Disable components.
	    sizeButton.setEnabled(false);
	    game.awaitingAck=true; 
	    game.oStream.println("resetsize "+s);
	  }
	  catch(Exception exc) {
	  label1.setText("Invalid size");                                          
	  }

	}
	
      }
    }
    public void windowClosing(WindowEvent e) {
      System.exit(1);
    } 
  } 

  //This class handles clicks, etc. for the game, generating the appropriate function calls.
  //It catches win/lose/etc. 
  class GameListener extends MouseAdapter implements MouseListener {
    public void mouseClicked(MouseEvent e) {
      TicTac t = (TicTac)e.getComponent();
      Dimension d = t.getSize();   //Get the dim of the board.
      
      int xc=e.getX()*t.size/d.width;
      int yc=e.getY()*t.size/d.height;

      if(t.isLegalMove(xc,yc)) {
	t.setEnabled(false); restartButton.setEnabled(false); sizeButton.setEnabled(false);
	t.pendingX=xc;
	t.pendingY=yc;
	t.awaitingAck=true;
	t.oStream.println("move "+yc+" "+xc);
      }
      else label1.setText("Invalid move");
    }
  }
  
  class OpponentListener extends Thread {     //This class accepts messages and passes them to the game.
    String command;
    public void run() {
      while(true) {
	try {command=game.iStream.readLine();}      //Gets a new command from the network.
	catch(IOException e) {connTerm();}
	if(command==null) connTerm();
	if(!game.myTurn) {                          //It's their turn
	  if(command.equals("reset")) {
	    game.resetRemote();
	    game.myTurn=true;
	    game.setEnabled(true); restartButton.setEnabled(true); sizeButton.setEnabled(true);
	    game.oStream.println("ok");
	  }
	  else if(command.startsWith("resetsize ")) {
	    try{
	      StringTokenizer t=new StringTokenizer(command);
	      if(t.countTokens()!=2) throw new Exception();
	      t.nextToken();
	      int a=Integer.valueOf(t.nextToken()).intValue();
	      if(a<=0 || a>100) throw new Exception();
	      game.size=a;
	      game.Board=new int[game.size][game.size];
	      game.resetRemote();
	      game.myTurn=true;
	      game.setEnabled(true); restartButton.setEnabled(true); sizeButton.setEnabled(true);
	      game.oStream.println("ok");
	    }
	    catch(Exception e) {          //Garbage
	      System.out.println("Error: received unknown command "+command+" from remote host");
	      game.oStream.println("error You're not making any sense. Try again.");
	    }
	  }
	  else if(command.startsWith("move ")) {       //A move command.
	    try {
	      StringTokenizer t=new StringTokenizer(command);
	      if(t.countTokens()!=3) throw new Exception();
	      t.nextToken();
	      int a=Integer.valueOf(t.nextToken()).intValue();
	      int b=Integer.valueOf(t.nextToken()).intValue();
	      String outcome=game.gameEvent(b,a);
	      if(outcome.equals("ok")) {
		game.myTurn=true;
		game.setEnabled(true); restartButton.setEnabled(true); sizeButton.setEnabled(true);
		game.oStream.println("ok");
	      }
	      if(outcome.equals("bounds")) game.oStream.println("error That's not a square!");
	      if(outcome.equals("taken")) game.oStream.println("error That's my square, and you can't have it.");
	      if(outcome.equals("usedalready")) game.oStream.println("error You already played that square.");
	      if(outcome.equals("gameover")) game.oStream.println("error Sorry, but the game is over.");
	    }
	    catch(Exception e) {          //Garbage
	      System.out.println("Error: received unknown command "+command+" from remote host"+e);
	      game.oStream.println("error You're not making any sense. Try again.");
	    }
	  }
	  else {                          //Garbage
	    System.out.println("Error: received unknown command "+command+" from remote host");
	    game.oStream.println("error You're not making any sense.");
	  } 
	}
	
	else {                               //It's our turn.
	  if(game.awaitingAck) {             //We're waiting for acknowledgement
	    if(command.equals("ok")) {       //Command ok.
	      if(game.pendingX==-game.size) {game.resetLocal();}
	      else if(game.pendingX<0) {
		game.size=-game.pendingX;
		game.Board=new int[game.size][game.size];
		game.resetLocal();
	      }
	      else game.gameEvent(game.pendingX, game.pendingY);
	      game.awaitingAck=false; game.myTurn=false;
	    }
	    else if(command.startsWith("error ")) {          //We got an error
	      game.awaitingAck=false;
	      game.setEnabled(true); restartButton.setEnabled(true); sizeButton.setEnabled(true);
	      label1.setText(command.substring(5));
	    }
	    else {                                         //Garbage.
	      System.out.println("Error, unknown message: "+command+" from remote system.");
	      System.exit(1);
	    }   
	  }
	  else game.oStream.println("error It's not your turn, fool!");  //We're not listening!
	}
	
      }
    }
    
    void connTerm() {
      System.out.println("Connection terminated remotely");
      System.exit(1);
    }

  }
  
}

class TicTac extends Canvas {         //This is the main TicTacToe board.
  Image x, o;
  Label status;
  int size;                           //The size of the board
  boolean myTurn;                     //Whose turn it is
  boolean awaitingAck;                //Whether we are waiting for acknowledgement
  int pendingX,pendingY;              //The pending move.
  int whoAmI;                         //Whether I am X's or O's
  int finished;                       //The victory flag
  int Board[][];                      //The board

  int lastChangedx;                   //Keeps track of board area needing update.
  int lastChangedy;
  
  Socket socket;  
  PrintWriter oStream;
  BufferedReader iStream;


  public TicTac(int si, Socket so, Label sbar, boolean isServer) {
    size=3;
    socket=so;
    status=sbar;
    setBackground(Color.white);
    lastChangedx=-1;
    Toolkit kit = Toolkit.getDefaultToolkit();   //Get the images from disk...
    x = kit.getImage("x.gif");
    o = kit.getImage("o.gif");

    if(isServer) {
      whoAmI=2;                             //Set the current turn
      myTurn=false;
      awaitingAck=false;
      setEnabled(false);
      
    }
    else {
      if(si==3) {
	whoAmI=1;
	myTurn=true;
	awaitingAck=false;
      }
      else {
	whoAmI=1;
	myTurn=true;
	awaitingAck=true;
	setEnabled(false);
	pendingX=-si;
	pendingY=-si;
      }
    }
      
    finished=0;
    Board=new int[size][size];
    for(int i=0;i<size;i++)
      for(int j=0; j<size;j++)
	Board[i][j]=0;
    try {
      oStream=new PrintWriter(socket.getOutputStream(),true);
      iStream=new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }
    catch(IOException e) {System.out.println("Socket init error."); System.exit(1);}
  }

  public void paint(Graphics g) {
    int dx = getSize().width;
    int dy = getSize().height;
    g.setColor(getBackground());
    g.fillRect(0,0,dx-1,dy-1);
    g.setColor(new Color(248,0,248));
    for(int i=0;i<size;i++) {
      g.drawLine(i*dx/size,0,i*dx/size,dy-1);
      g.drawLine(0,i*dy/size,dx-1,i*dy/size);
      for(int j=0; j<size;j++) {
	if(Board[i][j]==1)
	  g.drawImage(x, i*dx/size+2, j*dy/size+2, dx/size-4, dy/size-4, this);
	else if(Board[i][j]==2)
	  g.drawImage(o, i*dx/size+2, j*dy/size+2, dx/size-4, dy/size-4, this);
      }
    }
  }

  public void update(Graphics g) {
    if(lastChangedx==-1) paint(g);
    else {
      int dx = getSize().width;
      int dy = getSize().height;
      if(Board[lastChangedx][lastChangedy]==1)
	g.drawImage(x, lastChangedx*dx/size+2, lastChangedy*dy/size+2, dx/size-4, dy/size-4, this);
      else if(Board[lastChangedx][lastChangedy]==2)
	g.drawImage(o, lastChangedx*dx/size+2, lastChangedy*dy/size+2, dx/size-4, dy/size-4, this);
    } 
  }

  public Dimension getPreferredSize() {
    return new Dimension(60*size,60*size);
  }

  public Dimension getMinimumSize() {
    return new Dimension(5*size,5*size);
  }

  public boolean isLegalMove(int a, int b) {
    return (Board[a][b]==0 && finished==0);
  }

  public String gameEvent(int a, int b) {       //Do the appropriate event
    
    if(a>=size || b>=size) return "bounds";
    if (Board[a][b]==0 && finished==0) {     //A legal move.
      int cTurn;
      if(myTurn) cTurn=whoAmI;
      else if(whoAmI==1) cTurn=2;
      else cTurn=1;
      Board[a][b]=cTurn;                    //Mark the space
      lastChangedx=a;lastChangedy=b;

      boolean utility=true;             //Used in victory checking
      
      for(int i=0; i<size; i++)         //Check row victory
	if(Board[i][b]!=cTurn) utility=false;
      if(utility) finished=cTurn;
      
      utility=true;                     //Check column victory
      for(int j=0; j<size; j++)
	if (Board[a][j]!=cTurn) utility=false;
      if(utility) finished=cTurn;
      
      utility=true;                     //Check forward diagonal victory
      if(a==b) {
	for(int i=0; i<size; i++)
	  if (Board[i][i]!=cTurn) utility=false;
	if(utility) finished=cTurn;
      }

      utility=true;                     //Check backward diagonal victory
      if(a==size-1-b || b==size-1-a) {
	for(int i=0; i<size; i++)
	  if (Board[i][size-1-i]!=cTurn) utility=false;
	if(utility) finished=cTurn;
      }

      utility=true;
      if(finished==0) {                //Now we check for stalemate
	for(int i=0;i<size;i++)
	  for(int j=0; j<size;j++)
	    if(Board[i][j]==0) utility=false;
	if(utility) finished=3;
      }
      repaint();
      if (finished==0) status.setText("");
      if (finished==1) status.setText("X wins");
      if (finished==2) status.setText("O wins");
      if (finished==3) status.setText("Draw");
    }
    else {
      if(Board[a][b]==whoAmI) return "taken";
      if(Board[a][b]==0) return "gameover";
      return "usedalready";
    }
    return "ok";
  }
  
  void resetRemote() {                      //Resets the board and gives us the next move.
    finished=0;
    myTurn=true;
    awaitingAck=false;
    whoAmI=1;
    for(int i=0;i<size;i++)
      for(int j=0; j<size;j++)
	Board[i][j]=0;
    lastChangedx=-1;lastChangedy=-1;
    repaint();
    status.setText("");
  }

  //This checks whether its our turn, resets the board, 
  void resetLocal() {                    //Resets the board and gives them the next move.
      resetRemote();
      myTurn=false;
      awaitingAck=false;
      whoAmI=2;
  }

  
}





