//import java.lang.*;
//import java.util.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import java.net.*;
import com.google.gson.Gson;

public class TCPChat implements Runnable {
	// Connect status constants
	public final static int NULL = 0;
	public final static int DISCONNECTED = 1;
	public final static int DISCONNECTING = 2;
	public final static int BEGIN_CONNECT = 3;
	public final static int CONNECTED = 4;

	// Other constants
	public final static String statusMessages[] = { " Error! Could not connect!", " Disconnected", " Disconnecting...", " Connecting...", " Connected" };
	public final static TCPChat tcpObj = new TCPChat();
	public final static String END_CHAT_SESSION = new Character((char) 0).toString(); // Indicates
																						// the
																						// end
																						// of
																						// a
																						// session

	// Connection atate info
	public static String hostIP = "localhost";
	public static int port = 9876;
	public static int connectionStatus = DISCONNECTED;
	public static boolean isHost = true;
	public static String statusString = statusMessages[connectionStatus];
	public static StringBuffer toAppend = new StringBuffer("");
	public static StringBuffer toSend = new StringBuffer("");

	// Various GUI components and info
	public static JFrame mainFrame = null;
	public static JTextArea chatText = null;
	public static JTextField chatLine = null;
	public static JPanel statusBar = null;
	public static JLabel statusField = null;
	public static JTextField statusColor = null;
	public static JTextField ipField = null;
	public static JTextField portField = null;
	public static JRadioButton hostOption = null;
	public static JRadioButton guestOption = null;
	public static JButton connectButton = null;
	public static JButton disconnectButton = null;

	// TCP Components
	public static ServerSocket hostServer = null;
	public static Socket socket = null;
	public static BufferedReader in = null;
	public static PrintWriter out = null;
	private static JTextField txtScaleWeight;

	private static JButton btnWeight;
	private static JCheckBox ckbScaleRead = null;
	private static Thread tmThread = null;
	private static JButton btnUnit;

	// ///////////////////////////////////////////////////////////////

	private static JPanel initOptionsPane() {
		JPanel pane = null;
		ActionAdapter buttonListener = null;

		// Create an options pane
		JPanel optionsPane = new JPanel(new GridLayout(6, 1));

		// IP address input
		pane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		pane.add(new JLabel("Host IP:"));
		ipField = new JTextField(10);
		ipField.setText(hostIP);
		ipField.setEnabled(false);
		ipField.addFocusListener(new FocusAdapter() {
			public void focusLost(FocusEvent e) {
				ipField.selectAll();
				// Should be editable only when disconnected
				if (connectionStatus != DISCONNECTED) {
					changeStatusNTS(NULL, true);
				} else {
					hostIP = ipField.getText();
				}
			}
		});

		JPanel panel = new JPanel();
		FlowLayout flowLayout = (FlowLayout) panel.getLayout();
		flowLayout.setAlignment(FlowLayout.RIGHT);
		optionsPane.add(panel);

		JLabel lblNewLabel = new JLabel("Scale:");
		panel.add(lblNewLabel);

		txtScaleWeight = new JTextField();
		txtScaleWeight.setHorizontalAlignment(SwingConstants.RIGHT);
		txtScaleWeight.setEditable(false);
		txtScaleWeight.setText("0.00");
		txtScaleWeight.setFont(new Font("Tahoma", Font.BOLD, 16));
		panel.add(txtScaleWeight);
		txtScaleWeight.setColumns(9);
		pane.add(ipField);
		optionsPane.add(pane);

		// Port input
		pane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
		pane.add(new JLabel("Port:"));
		portField = new JTextField(10);
		portField.setEditable(true);
		portField.setText((new Integer(port)).toString());
		portField.addFocusListener(new FocusAdapter() {
			public void focusLost(FocusEvent e) {
				// should be editable only when disconnected
				if (connectionStatus != DISCONNECTED) {
					changeStatusNTS(NULL, true);
				} else {
					int temp;
					try {
						temp = Integer.parseInt(portField.getText());
						port = temp;
					} catch (NumberFormatException nfe) {
						portField.setText((new Integer(port)).toString());
						mainFrame.repaint();
					}
				}
			}
		});
		pane.add(portField);
		optionsPane.add(pane);

		// Host/guest option
		buttonListener = new ActionAdapter() {
			public void actionPerformed(ActionEvent e) {
				if (connectionStatus != DISCONNECTED) {
					changeStatusNTS(NULL, true);
				} else {
					isHost = e.getActionCommand().equals("host");

					// Cannot supply host IP if host option is chosen
					if (isHost) {
						ipField.setEnabled(false);
						ipField.setText("localhost");
						hostIP = "localhost";
					} else {
						ipField.setEnabled(true);
					}
				}
			}
		};
		ButtonGroup bg = new ButtonGroup();
		hostOption = new JRadioButton("Host", true);
		hostOption.setMnemonic(KeyEvent.VK_H);
		hostOption.setActionCommand("host");
		hostOption.addActionListener(buttonListener);
		guestOption = new JRadioButton("Guest", false);
		guestOption.setMnemonic(KeyEvent.VK_G);
		guestOption.setActionCommand("guest");
		guestOption.addActionListener(buttonListener);
		bg.add(hostOption);
		bg.add(guestOption);
		pane = new JPanel(new GridLayout(1, 2));
		pane.add(hostOption);
		pane.add(guestOption);
		optionsPane.add(pane);
		buttonListener = new ActionAdapter() {
			public void actionPerformed(ActionEvent e) {
				// Request a connection initiation
				if (e.getActionCommand().equals("connect")) {
					changeStatusNTS(BEGIN_CONNECT, true);
				}
				// Disconnect
				else {
					changeStatusNTS(DISCONNECTING, true);
				}
			}
		};

		// Connect/disconnect buttons
		JPanel buttonPane = new JPanel(new GridLayout(1, 2));
		connectButton = new JButton("Connect");
		connectButton.setMnemonic(KeyEvent.VK_C);
		connectButton.setActionCommand("connect");
		connectButton.addActionListener(buttonListener);
		connectButton.setEnabled(true);
		disconnectButton = new JButton("Disconnect");
		disconnectButton.setMnemonic(KeyEvent.VK_D);
		disconnectButton.setActionCommand("disconnect");
		disconnectButton.addActionListener(buttonListener);
		disconnectButton.setEnabled(false);
		buttonPane.add(connectButton);
		buttonPane.add(disconnectButton);
		optionsPane.add(buttonPane);

		JPanel panel_Scale = new JPanel();
		optionsPane.add(panel_Scale);
		panel_Scale.setLayout(null);

		ckbScaleRead = new JCheckBox("continue read");
		ckbScaleRead.setBounds(0, 40, 119, 23);
		panel_Scale.add(ckbScaleRead);

		btnUnit = new JButton("UNIT");
		btnUnit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (arg0.getActionCommand().equals("UNIT")) {
					// Send the string
					sendString("~*U*~");

				}
			}
		});
		btnUnit.setBounds(119, 5, 65, 50);
		panel_Scale.add(btnUnit);

		btnWeight = new JButton("Weight");
		btnWeight.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (arg0.getActionCommand().equals("Weight")) {
					// Send the string
					sendString("~*W*~");

				}
			}
		});
		btnWeight.setBounds(0, 5, 98, 35);
		panel_Scale.add(btnWeight);
		ckbScaleRead.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				JCheckBox cb = (JCheckBox) e.getSource();
				if (cb.isSelected()) {
					if (tmThread == null) {
						tmThread = new MThread(new Runnable() {
							int cid = 0;
							String sid = "";

							public void run() {
								while (!tmThread.isInterrupted()) {
									if (connectionStatus == CONNECTED) {
										if (cid == '}') {
											sid = "}m";
										} else if (cid == '~') {
											sid = "}n";
										} else {
											sid = Character.toString((char) cid);
										}
										sendString("~*W" + sid + "~");
										String ch = "~*W" + sid + "~";
										System.out.print(ch + "\n");
										cid++;
										cid = cid & 0x7f;
									}
									try {
										Thread.sleep(333);
									} catch (InterruptedException e) {
										// TODO Auto-generated catch block
										// e.printStackTrace();
									}
								}
								tmThread = null;
							}
						});
						tmThread.start();
					} else {
						// tmThread.start();
					}
				} else {
					if (tmThread != null) {
						tmThread.interrupt();
					}
				}
			}
		});

		return optionsPane;
	}

	// ///////////////////////////////////////////////////////////////

	// Initialize all the GUI components and display the frame
	private static void initGUI() {
		// Set up the status bar
		statusField = new JLabel();
		statusField.setText(statusMessages[DISCONNECTED]);
		statusColor = new JTextField(1);
		statusColor.setBackground(Color.red);
		statusColor.setEditable(false);
		statusBar = new JPanel(new BorderLayout());
		statusBar.add(statusColor, BorderLayout.WEST);
		statusBar.add(statusField, BorderLayout.CENTER);

		// Set up the options pane
		JPanel optionsPane = initOptionsPane();

		// Set up the chat pane
		JPanel chatPane = new JPanel(new BorderLayout());
		chatText = new JTextArea(10, 10);
		chatText.setLineWrap(true);
		chatText.setEditable(false);
		chatText.setForeground(Color.blue);
		JScrollPane chatTextPane = new JScrollPane(chatText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		chatLine = new JTextField();
		chatLine.setEnabled(false);
		chatLine.addActionListener(new ActionAdapter() {
			public void actionPerformed(ActionEvent e) {
				String s = chatLine.getText();
				if (!s.equals("")) {
					appendToChatBox("OUTGOING: " + s + "\n");
					chatLine.selectAll();

					// Send the string
					sendString(s);
				}
			}
		});
		chatPane.add(chatLine, BorderLayout.SOUTH);
		chatPane.add(chatTextPane, BorderLayout.CENTER);
		chatPane.setPreferredSize(new Dimension(800, 400));

		// Set up the main pane
		JPanel mainPane = new JPanel(new BorderLayout());
		mainPane.add(statusBar, BorderLayout.SOUTH);
		mainPane.add(optionsPane, BorderLayout.WEST);
		mainPane.add(chatPane, BorderLayout.CENTER);

		// Set up the main frame
		mainFrame = new JFrame("Simple TCP Chat");
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainFrame.setContentPane(mainPane);
		mainFrame.setSize(mainFrame.getPreferredSize());
		mainFrame.setLocation(100, 200);
		mainFrame.pack();
		mainFrame.setVisible(true);

	}

	// ///////////////////////////////////////////////////////////////

	// The thread-safe way to change the GUI components while
	// changing state
	private static void changeStatusTS(int newConnectStatus, boolean noError) {
		// Change state if valid state
		if (newConnectStatus != NULL) {
			connectionStatus = newConnectStatus;
		}

		// If there is no error, display the appropriate status message
		if (noError) {
			statusString = statusMessages[connectionStatus];
		}
		// Otherwise, display error message
		else {
			statusString = statusMessages[NULL];
		}

		// Call the run() routine (Runnable interface) on the
		// error-handling and GUI-update thread
		SwingUtilities.invokeLater(tcpObj);
	}

	// ///////////////////////////////////////////////////////////////

	// The non-thread-safe way to change the GUI components while
	// changing state
	private static void changeStatusNTS(int newConnectStatus, boolean noError) {
		// Change state if valid state
		if (newConnectStatus != NULL) {
			connectionStatus = newConnectStatus;
		}

		// If there is no error, display the appropriate status message
		if (noError) {
			statusString = statusMessages[connectionStatus];
		}
		// Otherwise, display error message
		else {
			statusString = statusMessages[NULL];
		}

		// Call the run() routine (Runnable interface) on the
		// current thread
		tcpObj.run();
	}

	// ///////////////////////////////////////////////////////////////

	// Thread-safe way to append to the chat box
	private static void appendToChatBox(String s) {
		synchronized (toAppend) {
			toAppend.append(s);
		}
	}

	// ///////////////////////////////////////////////////////////////

	// Add text to send-buffer
	private static void sendString(String s) {
		synchronized (toSend) {
			toSend.append(s + "\n");
		}
	}

	// ///////////////////////////////////////////////////////////////

	// Cleanup for disconnect
	private static void cleanUp() {
		try {
			if (hostServer != null) {
				hostServer.close();
				hostServer = null;
			}
		} catch (IOException e) {
			hostServer = null;
		}

		try {
			if (socket != null) {
				socket.close();
				socket = null;
			}
		} catch (IOException e) {
			socket = null;
		}

		try {
			if (in != null) {
				in.close();
				in = null;
			}
		} catch (IOException e) {
			in = null;
		}

		if (out != null) {
			out.close();
			out = null;
		}
	}

	// ///////////////////////////////////////////////////////////////

	// Checks the current state and sets the enables/disables
	// accordingly
	public void run() {
		switch (connectionStatus) {
		case DISCONNECTED:
			connectButton.setEnabled(true);
			disconnectButton.setEnabled(false);
			ipField.setEnabled(true);
			portField.setEnabled(true);
			hostOption.setEnabled(true);
			guestOption.setEnabled(true);
			chatLine.setText("");
			chatLine.setEnabled(false);
			statusColor.setBackground(Color.red);
			break;

		case DISCONNECTING:
			connectButton.setEnabled(false);
			disconnectButton.setEnabled(false);
			ipField.setEnabled(false);
			portField.setEnabled(false);
			hostOption.setEnabled(false);
			guestOption.setEnabled(false);
			chatLine.setEnabled(false);
			statusColor.setBackground(Color.orange);

			btnUnit.setEnabled(false);
			btnWeight.setEnabled(false);
			break;

		case CONNECTED:
			connectButton.setEnabled(false);
			disconnectButton.setEnabled(true);
			ipField.setEnabled(false);
			portField.setEnabled(false);
			hostOption.setEnabled(false);
			guestOption.setEnabled(false);
			chatLine.setEnabled(true);
			statusColor.setBackground(Color.green);

			btnUnit.setEnabled(true);
			btnWeight.setEnabled(true);
			break;

		case BEGIN_CONNECT:
			connectButton.setEnabled(false);
			disconnectButton.setEnabled(false);
			ipField.setEnabled(false);
			portField.setEnabled(false);
			hostOption.setEnabled(false);
			guestOption.setEnabled(false);
			chatLine.setEnabled(false);
			chatLine.grabFocus();
			statusColor.setBackground(Color.orange);
			break;
		}

		// Make sure that the button/text field states are consistent
		// with the internal states
		ipField.setText(hostIP);
		portField.setText((new Integer(port)).toString());
		hostOption.setSelected(isHost);
		guestOption.setSelected(!isHost);
		statusField.setText(statusString);
		if (chatText.getLineCount() > 150) {
			chatText.select(0, 600);
			chatText.setText(chatText.getText().replace(chatText.getSelectedText(), ""));
		}
		chatText.append(toAppend.toString());
		toAppend.setLength(0);

		mainFrame.repaint();
	}

	// ///////////////////////////////////////////////////////////////

	// The main procedure
	public static void main(String args[]) {
		String s;

		initGUI();

		initScale();

		while (true) {
			try { // Poll every ~10 ms
				Thread.sleep(10);
			} catch (InterruptedException e) {
			}

			switch (connectionStatus) {
			case BEGIN_CONNECT:
				try {
					// Try to set up a server if host
					if (isHost) {
						hostServer = new ServerSocket(port);
						socket = hostServer.accept();
					}

					// If guest, try to connect to the server
					else {
						socket = new Socket(hostIP, port);
					}

					in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
					out = new PrintWriter(socket.getOutputStream(), true);
					changeStatusTS(CONNECTED, true);
				}
				// If error, clean up and output an error message
				catch (IOException e) {
					cleanUp();
					changeStatusTS(DISCONNECTED, false);
				}
				break;

			case CONNECTED:
				try {
					// Send data
					if (toSend.length() != 0) {
						out.print(toSend);
						out.flush();
						toSend.setLength(0);
						changeStatusTS(NULL, true);
					}

					// Receive data
					if (in.ready()) {
						s = in.readLine();
						if ((s != null) && (s.length() != 0)) {
							// Check if it is the end of a trasmission
							if (s.equals(END_CHAT_SESSION)) {
								changeStatusTS(DISCONNECTING, true);
							}

							// Otherwise, receive what text
							else {
								appendToChatBox("INCOMING: " + s + "\n");
								changeStatusTS(NULL, true);

								// V9 Scale TCPIP
								if (s.startsWith("{") && s.endsWith("}")) {
									Gson json = new Gson();
									final AXScaleWeight objW;
									objW = json.fromJson((String) s.subSequence(0, s.length()), AXScaleWeight.class);
									// System.out.print("W=" +
									// objW.getsWeight().toString() + "\n");
									SwingUtilities.invokeLater(new Runnable() {
										public void run() {
											txtScaleWeight.setText(objW.getsWeight().toString());
										}
									});
								}
							}
						}
					}
				} catch (IOException e) {
					cleanUp();
					changeStatusTS(DISCONNECTED, false);
				}
				break;

			case DISCONNECTING:
				// Tell other chatter to disconnect as well
				out.print(END_CHAT_SESSION);
				out.flush();

				// Clean up (close all streams/sockets)
				cleanUp();
				changeStatusTS(DISCONNECTED, true);
				break;

			default:
				break; // do nothing
			}
		}
	}

	// V9 Scale TCPIP
	private static void initScale() {
		// enter the guest mode
		guestOption.doClick();
		hostIP = "192.168.1.191";
		ipField.setText(hostIP);

		btnUnit.setEnabled(false);
		btnWeight.setEnabled(false);

		// disable the timer
		ckbScaleRead.setSelected(false);

	}
}

// //////////////////////////////////////////////////////////////////

// Action adapter for easy event-listener coding
class ActionAdapter implements ActionListener {
	public void actionPerformed(ActionEvent e) {
	}
}

// //////////////////////////////////////////////////////////////////

/**
 * Arlyn Exchange Scale Weight
 * 
 * @author lf
 *
 */
class AXScaleWeight {
	// private int _id;
	// private String _name = "";
	private String _CMD_ACK = "";
	private int _sSNo;
	private int _rSNo;

	private String sWeight;
	private String sUnit;
	private String sNet;
	private boolean bSIndi;

	public String get_CMD_ACK() {
		return _CMD_ACK;
	}

	public int get_sSNo() {
		return _sSNo;
	}

	public int get_rSNo() {
		return _rSNo;
	}

	public String getsWeight() {
		return sWeight;
	}

	public String getsUnit() {
		return sUnit;
	}

	public String getsNet() {
		return sNet;
	}

	public boolean isbSIndi() {
		return bSIndi;
	}
}

class MThread extends Thread {
	@Override
	public synchronized void start() {
		minterrupt = false;
		super.start();
	}

	public MThread(Runnable target) {
		super(target);
		// TODO Auto-generated constructor stub
	}

	boolean minterrupt = false;

	@Override
	public boolean isInterrupted() {
		return minterrupt;
	}

	@Override
	public void interrupt() {
		super.interrupt();
		minterrupt = true;
	}
}
