import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.net.*;
import java.io.*;

/**
   DownPanel.java
   This class implements down panel of the userinterface.  This is where
   the messages and connection is exchange
   @author Sugiharto Widjaja  Contribution from Toan with integration testing and debugging
   @version 11.30.2001
   */
class DownPanel extends JPanel implements ActionListener, ConnectorTarget
{
	// The chat field where user will type in the message
	private JTextField chatline;
	// The chat area where important events and chat messages will occur at
	private JTextArea chatpane;
	// The scoll pane
	private JScrollPane scrollPane;
	// The scoll bar
	private JScrollBar scrollbar;
	// The button to send message
	private JButton send;
    // The TwoWayConnector object
    private TwoWayConnector myConnector;
    // The username of the player
    private String user;
    // The username of the opponent
    private String other;
    // The menu that handles with connection
    private JMenu m_menu;
    // The menu that handles with game
    private JMenu game;
	// Sub-menu to connect to a host
	private JMenuItem m_connect;
	// Sub-menu to disconnect from a host
	private JMenuItem m_disconnect;
	// Sub-menu to assign role
	private JMenuItem m_assign;
	// Sub-menu to print the board
	private JMenuItem m_print;
	// The UpPanel panel
	private UpPanel upPanel;
	// The BoardPanel panel
	private BoardPanel boardPanel;
	// The port number
	private int portNumber;
	// Is it currently my turn?
	private boolean myTurn;
	// Am I playing as host?
	private boolean isAHost;
	// The color role
	private int colorRole;
	// The number of clients connecting to the host
	private int numOfClients;
	// The maximum number of client the host can accept
	private static final int MAXCLIENT = 1;
	// The default port number
	private static final int DEFPORT = 8189;

    /**
       Constructor for the DownPanel class
       There are few things that will happen here:
       1. Two dialog boxes will appear, asking for username and the port number
       2. If the port number is non-digit, then default port number, 8189, weill be used instead
       3. Initialization of variables
       4. Adding all the sub-menus into the menubar
       5. Adding the chat panel and the text field
       6. Adding the send button
       7. Finally, the construction of TwoWayConnector object is done.
       @param bar - the JMenuBar
       */

    public DownPanel(JMenuBar bar)
    {
		String name = JOptionPane.showInputDialog("Enter your username : ");
		try
		{
		   String port = JOptionPane.showInputDialog("Enter desired port number : ");
		   portNumber = Integer.parseInt(port);
	    }
	    catch(NumberFormatException e)
	    {
			JOptionPane.showMessageDialog(null, "The port number is set to the default port: 8189",
			                                 "Invalid port number", JOptionPane.INFORMATION_MESSAGE);
			portNumber = DEFPORT;
		}
		user = name;
		myTurn = false;
		isAHost = false;
		colorRole = -1;
		numOfClients = 0;

		game = new JMenu("Game");
		m_print = new JMenuItem("Print");
		m_print.addActionListener(this);
		game.add(m_print);
		game.add(new AbstractAction("Exit")
		{
		  public void actionPerformed(ActionEvent event)
		  {
			System.exit(0);
		  }
		});
		bar.add(game);
        // Creating the menus and sub-menus
		m_menu = new JMenu("Connection");
		m_connect = new JMenuItem("Connect");
		m_connect.addActionListener(this);
		m_disconnect = new JMenuItem("Disconnect");
		m_disconnect.addActionListener(this);
		m_assign = new JMenuItem("Assign Role");
		disableAssign();
		m_assign.addActionListener(this);
		m_menu.add(m_connect);
		m_menu.add(m_disconnect);
		m_menu.add(m_assign);
		bar.add(m_menu);
        // Putting the chat panel and text field in GridBag layout
        setLayout(new GridBagLayout());
		GridBagConstraints gbc = new GridBagConstraints();
		gbc.insets.top = 5;
		gbc.insets.left = 10;
		gbc.insets.bottom = 5;
		gbc.insets.right = 10;
		chatpane = new JTextArea(20, 20);
		chatpane.setEditable(false);
		chatpane.setLineWrap(true);
		scrollPane = new JScrollPane(chatpane);
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		scrollbar = scrollPane.getVerticalScrollBar();
		chatline = new JTextField(20);
		send = new JButton("Send");
		send.addActionListener(this);
		send.setEnabled(false);
		gbc.fill = GridBagConstraints.REMAINDER;
		JLabel label = new JLabel("Chat Area");
        add(label, gbc);
		gbc.gridy = 1;
		gbc.gridx = GridBagConstraints.RELATIVE;
		add(scrollPane, gbc);
		gbc.gridx = GridBagConstraints.RELATIVE;
		gbc.gridy = 2;
		add(chatline, gbc);
		gbc.gridx = GridBagConstraints.RELATIVE;
		gbc.gridy = 3;
		add(send, gbc);
		myConnector = new TwoWayConnector(this, portNumber);

	}

	/**
	   Establishing communication with the UpPanel panel
	   @param thePanel - the UpPanel panel
	   <dt><b>Precondition:</b><dd>
	   thePanel is a valid UpPanel panel
	   <dt><b>Postcondition:</b><dd>
	   Communication between UpPanel and DownPanel panel is established.
	   */

	public void addUpPanel(UpPanel thePanel)
	{
	   upPanel = thePanel;
	}

    /**
	   Establishing communication with the BoardPanel panel
	   @param thePanel - the BoardPanel panel
	   <dt><b>Precondition:</b><dd>
	   thePanel is a valid BoardPanel panel
	   <dt><b>Postcondition:</b><dd>
	   Communication between BoardPanel and DownPanel panel is established.
	   */

    public void addBoardPanel(BoardPanel thePanel)
	{
	   boardPanel = thePanel;
	}

	/**
	   Detecting events that are created by a selection of a sub-menu.
	   There are three possible sub-menus that will create the event
	   the connect, the disconnect, the assign role, the print sub-menus.
	   The connect sub-menu will attempt a connection to a specified
	   ip address, the disconnect will attempt to disconnect from
	   an ip address. The assign role will decide who plays black
	   stone and vice versa. The print sub-menu will try to print the
	   current game board. The send button will also create an event. It
	   will try to send the message to other user. At the same time, the
	   message, that the user types, will appear on the chat area.
	   @param evt - the ActionEvent object
	   <dt><b>Precondition:</b><dd>
	   myConnector is not null
	   */

	public void actionPerformed(ActionEvent evt)
	{
	   Object source = evt.getSource();

	   if (source == m_connect)
	   {
		  // Dialog box asking the ip address of the host
		  String ip = JOptionPane.showInputDialog("IP to connect to: ");
		  if (ip != null)
		  {
		     chatpane.append("*** Attempting Connection to " + ip + "...\n");
		     myConnector.setIP(ip);
		     // Attempt to connect to host
		     boolean connected = myConnector.connect();
		     // Failure
		     if(! connected)
		        chatpane.append("*** Failed connecting to " + ip + "\n");
		     // Success
		     else
		     {
				 m_connect.setEnabled(false);
				 m_disconnect.setEnabled(true);
			 }
		  }
	   }
	   else if (source == m_disconnect)
	   {
		   chatpane.append("*** Disconnected from the other user\n");
		   // Disconnect from the host
		   myConnector.disconnect();
		   send.setEnabled(false);
		   disableAssign();
		   upPanel.disableButtons();
		   m_connect.setEnabled(true);
		   m_disconnect.setEnabled(false);
	   }
	   else if (source == send)
	   {
		  String msg = (chatline.getText()).trim();
		  {
			  chatpane.append("< " + user + " > : " + msg + "\n");
			  sendMsg("00", msg);
		  }
		  chatline.setText("");
		}
		else if(source == m_assign)
		{
			if (!boardPanel.gameProgress())
			{
				if (!isAHost)
				   JOptionPane.showMessageDialog(null, "Only host can assign role",
						"Can't assign role", JOptionPane.INFORMATION_MESSAGE);
				else
				{
					int role = JOptionPane.showOptionDialog(null, "Will you play as black or white",
					   "Assign role", JOptionPane.DEFAULT_OPTION,
					   JOptionPane.QUESTION_MESSAGE, null,
					   new String[]
					   {
						  "Black",
						  "White"
					   },
					   "Black");
					if (role == 0)
					{
					   sendMsg("04");
					   colorRole = 0;
					   myTurn = true;
					   JOptionPane.showMessageDialog(null, "You are now playing black stone",
						  "Play Black Stone", JOptionPane.INFORMATION_MESSAGE);
					   boardPanel.setRole(user, other);
					}
					else
					{
					   sendMsg("05");
					   colorRole = 1;
					   myTurn = false;
					   JOptionPane.showMessageDialog(null, "You are now playing white stone",
						  "Play White Stone", JOptionPane.INFORMATION_MESSAGE);
					   boardPanel.setRole(other, user);
					}
					boardPanel.start_message();
					boardPanel.repaint();
					upPanel.enableStart();
				}
			}
			else
			{   JOptionPane.showMessageDialog(null, "Game is in progress",
					"Can't assign role", JOptionPane.INFORMATION_MESSAGE);
			}
	    }
	    else if(source == m_print)
	    {
			boardPanel.print_board();
		}

	    update_ChatArea();
	}

	/**
	   Update the scroll bar of the chat area
	   @param none
	   */

	public void update_ChatArea()
	{
		scrollbar.setValues(scrollbar.getMaximum(),
							scrollbar.getVisibleAmount(),
							scrollbar.getMinimum(),
							scrollbar.getMaximum());
	}

    /**
       Enable the assign role sub-menu
       @param none
       <dt><b>Postcondition:</b><dd>
       Player can now select the assign role sub-menu
       */

	public void enableAssign()
	{
		m_assign.setEnabled(true);
	}

	/**
	   Disable the assign role sub-menu
	   @param none
	   <dt><b>Postcondition:</b><dd>
	   Player cannot select the assign role sub-menu.
       */

	public void disableAssign()
	{
		m_assign.setEnabled(false);
	}

	/**
	   Method to send a message to the other player.
	   Note that sendMsg does not only send common chat message. Instead
	   it will be the main key of the communication between each elements
	   of the game.
	   @param opCode - the code that indicates the kind of message
	   msg - additional information that is needed with the opCode
	   */

	public void sendMsg(String opCode, String msg)
	{
		myConnector.sendString(user + " " + opCode + " " + msg);
	}

	/**
		   Method to send a message to the other player.
		   Note that sendMsg does not only send common chat message. Instead
		   it will be the main key of the communication between each elements
		   of the game.
		   @param opCode - the code that indicates the kind of message
	   */
	public void sendMsg(String opCode)
	{
		myConnector.sendString(user + " " + opCode);
	}

    /**
       This method implements the onConnect part of the ConnectorTarget interface.
       This method will be invoked whenever a host receives an incoming connection.
       If the host has already receive a connection before, then other connections will
       be rejected. If not, host will have a right to determine whether he wants to
       accept the connection or not.
       @param s - the incoming socket
       <dt><b>Postcondition:</b><dd>
       Rejection or acceptance of the incoming socket.
       */

	public void onConnect(Socket s)
	{
		myConnector.connect(s);
		// Is game in progress ? Is the maximum number of clients reached ?
		if (boardPanel.gameProgress() || numOfClients > 1)
		{
		   // Send the rejection message to the user
		   sendMsg("11");
		   myConnector.disconnect();
	    }
	    else
	    {
			// Accept the incoming connection ?
			int selection = JOptionPane.showConfirmDialog(null,
							    "Incoming connection request. Accept it?",
							    "Incoming Request", JOptionPane.YES_NO_OPTION,
				    JOptionPane.QUESTION_MESSAGE);
		    // Accept the connection
		    if (selection == JOptionPane.YES_OPTION)
		    {
			   chatpane.append("< " + user + " > : Now running in host mode. \n");
		       send.setEnabled(true);
		       m_assign.setEnabled(true);
		       m_connect.setEnabled(false);
		       m_disconnect.setEnabled(true);
		       isAHost = true;
		       numOfClients++;
		       // Send the acceptance message to client.
		       sendMsg("12");
		    }
		    // Reject the connection
		    else
		    {
				sendMsg("11");
				myConnector.disconnect();
			}
		}

		update_ChatArea();

	}

    /**
	       This method implements the onDisconnect part of the ConnectorTarget interface.
	       This method will be invoked whenever a host detects a disconnection of the client.
	       The upPanel buttons, the send button, connect and assign sub-menu will be disabled.
	       Number of clients will be decremented by 1. The chat area sroll bar will be updated.
	       <dt><b>Postcondition:</b><dd>
	       No client is connecting now. The host is ready to accept new connections.
       */
	public void onDisconnect()
	{
		chatpane.append("*** The other user has disconnected \n");
		myConnector.disconnect();
		send.setEnabled(false);
		upPanel.disableButtons();
		m_assign.setEnabled(false);
		m_connect.setEnabled(true);
		m_disconnect.setEnabled(false);
		// No client is connecting now
		if (isAHost)
		   numOfClients--;
		isAHost = false;

		update_ChatArea();
	}

	/**
	   Return the username of the player
	   @param none
	   @return the username
	   */

	public String getUser()
	{
		return user;
	}

	/**
	   Check whether it is the player's turn at the moment.
	   @param none
	   @return true if it is the player's turn, otherwise return false
	   */

	public boolean returnTurn()
	{
		return myTurn;
	}

	/**
	   Change the turn. If it is the player's turn at the moment, then it will
	   be his opponent's turn now.
	   @param none
	   <dt><b>Postcondition:</b><dd>
	   The user will now be unable to click on the board game.
	   However, he may still request a takeback
	   */

	public void setTurn()
	{
		if(myTurn)
		   myTurn = false;
		else
		   myTurn = true;
    }

	/**
	   Set the color of the stone played for a player
	   @param col - the color code
	   */

	public void setColorRole(int col)
	{
		colorRole = col;
	}

	public int getColorRole()
	{
		return colorRole;
	}

	/**
	   Check whether the player is currently acting as a host
	   @param none
	   @return true if the player is the host, otherwise return false
	   */

	public boolean isHost()
	{
		return isAHost;
	}

    /**
	   This method implements the receive part of the ConnectorTarget interface.
	   This method will be invoked whenever a player receives message from opponent.
	   The kinds of actions that will be performed is determined from the opCode and
	   the additional information that is accepted by the user.
	   @param msg - the message received. It will be consisted of an opCode and
	   additional message. Additional message is not always needed.
	   <dt><b>Postcondition:</b><dd>
	   Specific actions will be performed depending on the opCode and additional
	   message
       */
    public void receive(String msg)
	{
		try
		{
			int index0 = msg.indexOf(" ");
			String handle = msg.substring(0, index0);
			int index1 = msg.indexOf(" ", index0 + 1);
			String op = "";
			if (index1 != -1)
				op = msg.substring(index0 + 1, index1);
			else
				op = msg.substring(index0 + 1);
			// Chat message
			if (op.equals("00"))
			{
				String actMsg = msg.substring(index1 + 1);
				chatpane.append("< " + handle + " > : " + actMsg + "\n");
			}
			// Receiving the move of the opponent who plays black stone.
			//It will repaint the boardPanel to reflect the move of the opponent
			else if (op.equals("06"))
			{
				String coord = msg.substring(index1 + 1);
				int gap = coord.indexOf(",");
				int xcord = Integer.parseInt(coord.substring(0, gap));
				int ycord = Integer.parseInt(coord.substring(gap + 1));
				boardPanel.move(xcord, ycord, 0);
				// Change turn
				myTurn = true;
				boardPanel.repaint();
			}
			// Receiving the move of the opponent who plays white stone.
			//It will repaint the boardPanel to reflect the move of the opponent
			else if (op.equals("07"))
			{
				String coord = msg.substring(index1 + 1);
				int gap = coord.indexOf(",");
				int xcord = Integer.parseInt(coord.substring(0, gap));
				int ycord = Integer.parseInt(coord.substring(gap + 1));
				boardPanel.move(xcord, ycord, 1);
				// Change turn
				myTurn = true;
				boardPanel.repaint();
			}
            // Will the user accept the takeback request from opponent?
			else if(op.equals("01"))
			{
				String actMsg = msg.substring(index1 + 1);
				int num = Integer.parseInt(actMsg);
				if (num <= 0)
					throw new NumberFormatException();
				int selection = JOptionPane.showConfirmDialog(null,
				    handle + " has requested " + num + " takebacks. Accept it?",
				    "Takebacks Request", JOptionPane.YES_NO_OPTION,
				    JOptionPane.QUESTION_MESSAGE);
				if (selection == JOptionPane.YES_OPTION)
				{
					// Agree to the takeback request
					sendMsg("02", actMsg);
					chatpane.append("*** Granting takebacks request from " + handle +
									"\n");
					// Do the takeback
					boardPanel.doTakeBack1(num);
					// repaint the board
					boardPanel.repaint();
					// Update the move list window
					(upPanel.getHisList()).update_t();
			    }
			    else
			    {
					// Reject the takeback request
					sendMsg("03");
					chatpane.append("*** Rejecting takebacks request from " + handle +
							"\n");
				}
			}
			// Informing the user that opponent has accepted the takeback request
			else if (op.equals("02"))
			{
				String actMsg = msg.substring(index1 + 1);
				int num = Integer.parseInt(actMsg);
				if (num <= 0)
					throw new NumberFormatException();
				JOptionPane.showMessageDialog(null, handle + " agrees to the takebacks request",
				   "Takebacks Agreement", JOptionPane.INFORMATION_MESSAGE);
				// Do the takeback
				boardPanel.doTakeBack2(num);
				// Update the move list window
				(upPanel.getHisList()).update_t();
				// repaint the board
				boardPanel.repaint();
		    }
		    // Opponent has rejected our takeback request
		    else if (op.equals("03"))
		    {
				JOptionPane.showMessageDialog(null, handle + " rejects the takebacks request",
				    "Takebacks Rejection", JOptionPane.INFORMATION_MESSAGE);
		    }
		    // Informing the user that he has been assigned to play white stone
		    else if(op.equals("04"))
		    {
				JOptionPane.showMessageDialog(null, "You have been assigned to play white stone",
				    "Play White Stone", JOptionPane.INFORMATION_MESSAGE);
				colorRole = 1;
				// Since player who plays black stone will have first turn
				myTurn = false;
				upPanel.enableStart();
				boardPanel.start_message();
				other = handle;
				boardPanel.setRole(other, user);
				boardPanel.repaint();

		    }
		    // Informing the user that he has been assigned to play black stone
		    else if(op.equals("05"))
		    {
				JOptionPane.showMessageDialog(null, "You have been assigned to play black stone",
								    "Play Black Stone", JOptionPane.INFORMATION_MESSAGE);
				colorRole = 0;
				myTurn = true;
				upPanel.enableStart();
				boardPanel.start_message();

				other = handle;
				boardPanel.setRole(user, other);
				boardPanel.repaint();

			}
			// Informing the user that the opponent has forfeited.
			else if(op.equals("08"))
			{
				JOptionPane.showMessageDialog(null, "Other player has forfeited. You win!!",
								    "Winning", JOptionPane.INFORMATION_MESSAGE);
				// Stop the current game
				boardPanel.stopGame();
				// User is winning
				boardPanel.losingFalse();
				upPanel.disableButtons();
		  	    enableAssign();
			}
            // Informing the user that the opponent has started the game
			else if(op.equals("09"))
			{
				boardPanel.startGame();
				boardPanel.repaint();
				upPanel.enableButtons();
		        upPanel.disableStart();
			}
            // Opponent has requested a pass
			else if(op.equals("10"))
			{
				int color;
				if (colorRole == 0)
					color = 1;
				else
					color = 0;
				boardPanel.move(-1, -1, color);
				boardPanel.repaint();
			}
            // Informing the user that his connection has been rejected by host
			else if(op.equals("11"))
			{
				JOptionPane.showMessageDialog(null, "Try to connect later",
								    "Connection rejected", JOptionPane.INFORMATION_MESSAGE);
                myConnector.disconnect();
			}
			// Informing the user that the host has accepted his connection
			else if(op.equals("12"))
			{
			    chatpane.append("*** Succesfully connecting to the host \n");
				send.setEnabled(true);
				enableAssign();
				// User is now a client
				isAHost = false;
			    // Send the opponent our username
			    sendMsg("20", user);
			}
            // Get the username of the opponent
			else if(op.equals("20"))
			{
				String actMsg = msg.substring(index1 + 1);
				other = actMsg;
			}
            // Receive the status of highligheted stone(s)
			else if(op.equals("30"))
			{
				boardPanel.receiveMessage();
				String actMsg = msg.substring(index1 + 1);
				int other1 = Integer.parseInt(actMsg);

				boardPanel.setOther(other1);
				boardPanel.scoreMode_message1();
				boolean k = boardPanel.scoreMode();
			}
            // Receive the status of highligheted stone(s)
			else if(op.equals("40"))
			{
				boardPanel.receiveMessage();
				String actMsg = msg.substring(index1 + 1);
				int other1 = Integer.parseInt(actMsg);

				boardPanel.setOther(other1);

				boolean k = boardPanel.scoreMode();
				if (k == true)
					boardPanel.scoreMode_message();
			}
            // The game has ended
			else if(op.equals("50"))
			{
				boardPanel.endgame();
			}
            // Error in message transmission
		    else
				chatpane.append("*** Unrecognized opCode\n");
		}
		catch (NumberFormatException e)
		{
		}
        // Update the scroll bars of chat area
		update_ChatArea();
	}
}




