/* ------------------------------------------------------------------------

File:		SoarClient.java
Synopsis:	A simple client applet that talks to a remote Soar
Created By:	Christopher R. Waterson <waterson@eecs.umich.edu>
Created On:	05 Sep 1996
Modified By:	Christopher R. Waterson <waterson@eecs.umich.edu>
Modified On:	11 Sep 1996

This file contains an applet that connects to a Soar7 server, pipes
all of its input to a console window, and sends commmands to the
server for execution.
	
------------------------------------------------------------------------ */


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


/**
 * A simple applet that connects to a Soar7 server, sending it commands
 * and receiving its output via a socket.
 *
 * @author Christopher R. Waterson
 * @version 1.0.0
 */

public class SoarClientApplet
extends java.applet.Applet
implements Runnable {

	/**
	 * The default port on which to attempt to connect
	 */

	protected static final int DEFAULT_PORT = 1212;


	/**
	 * The layout manager for the applet.
	 */

	GridBagLayout m_layoutManager;


	/**
	 * The window where soar's output appears.
	 */

	TextArea m_output;


	/**
	 * A field for entering commands into soar
	 */

	TextField m_input;


	/**
	 * A button to send commands to soar
	 */

	Button m_send;


	/**
	 * The socket to which we're connected to the Soar server
	 */

	Socket m_socket;


	/**
	 * Initialize the applet: create controls, connect to Soar,
	 * and start a thread to monitor Soar's responses.
	 */

	public void
	init() {
		// Create the controls to run the applet
		m_layoutManager = new GridBagLayout();
		setLayout(m_layoutManager);

		m_output = new TextArea();
		add(m_output, 0, 0, 2, 1, GridBagConstraints.BOTH, 1, 1);
		m_output.setFont(new Font("Courier", Font.PLAIN, 12) );
		m_output.setEditable(false);

		m_input = new TextField();
		add(m_input, 0, 1, 1, 1, GridBagConstraints.HORIZONTAL, 1, 0);

		m_send = new Button("Send");
		add(m_send, 1, 1, 1, 1, GridBagConstraints.NONE, 0, 0);

		// Disable until the connection is alive.
		m_input.enable(false);
		m_send.enable(false);
	}



	/**
	 * A helper to create components in the GridBagLayout
	 */

	protected void
	add(Component cmp, int gridx, int gridy,
		int gridwidth, int gridheight,
		int fill, int weightx, int weighty) {

		GridBagConstraints c = new GridBagConstraints();

		c.gridx		= gridx;
		c.gridy		= gridy;
		c.gridwidth	= gridwidth;
		c.gridheight	= gridheight;
		c.fill		= fill;
		c.anchor	= GridBagConstraints.CENTER;
		c.weightx	= weightx;
		c.weighty	= weighty;

		m_layoutManager.setConstraints(cmp, c);
		add(cmp);
	}


	/**
	 * Connect to the Soar server and enable the user
	 * interface.
	 */

	public void
	start() {
		// Connect
		try {
			URL url = getDocumentBase();

			int port = DEFAULT_PORT;

			String param = getParameter("Port");
			if (param != null && param.length() > 0)
				port = Integer.parseInt(param);

			m_socket = new Socket(url.getHost(), port);
		} catch (java.io.IOException e) {
			m_output.setText("Unable to connect to Soar:\n" + e);

			e.printStackTrace();
			return;
		}

		// Ok, if we get here, we're set!
		m_input.enable(true);
		m_send.enable(true);

		m_input.requestFocus();

		// Start the input monitor thread.
		Thread inputMonitor = new Thread(this);
		inputMonitor.start();
	}



	/**
	 * Kill the server process by sending the "exit" command
	 * to Soar.
	 */

	public void
	stop() {
		if (m_socket == null) return;


		try {
			sendString("exit\n");
			m_socket.getOutputStream().flush();
			m_socket.close();
		} catch (java.io.IOException e) {
			// Not much else we can do now...
			e.printStackTrace();
		}
	}


	/**
	 * Handle user input events.
	 */

	public boolean
	action(Event event, Object target) {
		if (event.target == m_send) {
			sendCommand(m_input.getText());
			m_input.setText("");
		}

		return true;
	}


	/**
	 * Handle user input events. Traps the "enter" key
	 * to issue a send to soar.
	 */

	public boolean
	handleEvent(Event event) {
		if (event.id == Event.KEY_PRESS
		&& event.target == m_input
		&& event.key == 10) { // Enter
			sendCommand(m_input.getText());
			m_input.setText("");

			return true;
		}

		return super.handleEvent(event);
	}


	/**
	 * Send the specified command string to soar. Adds additional
	 * preamble and postamble to ensure that the command gets processed
	 * and output gets sent back to us.
	 *
	 * @param command The command to send to soar.
	 */

	protected void
	sendCommand(String command) {
		try {
			//System.out.println("Sending \"" + command + "\"");

			// Print the command first
			sendString("puts \"" + command + "\"\n");

			// ...then send it for real
			sendString(command + "\n");

			// Add another carriage return
			sendString("puts \\n\n");

			// ...and force stdout to flush
			sendString("flush stdout\n");
			sendString("flush stderr\n");


			// Now flush our *own* output
			m_socket.getOutputStream().flush();
		} catch (java.io.IOException e) {
			e.printStackTrace();
		}
	}


	/**
	 * Send a single string to soar.
	 * @param s The string to send to Soar.
	 * @exception java.io.IOException If there is a problem sending
	 * the output to Soar.
	 */

	protected void
	sendString(String s)
	throws java.io.IOException {
		byte[] buffer = new byte[s.length()];

		for (int i = 0; i < s.length(); ++i) {
			buffer[i] = (byte) s.charAt(i);
		}

		m_socket.getOutputStream().write(buffer);
	}



	/**
	 * Hand around in the background waiting for Soar to send us
	 * output. When it does, display it in the output window.
	 * Kinda inefficient, but there's no blocking I/O that I
	 * could find.
	 */

	public void run() {
	try {
		byte[] buffer = new byte[1024];
		for ( ; ; ) {
			int bytesRead = m_socket.getInputStream().read(buffer);

			if (bytesRead > 0) {
				String soarOutput
					= new String(buffer, 0, 0, bytesRead);

				m_output.appendText(soarOutput);
			}
		}
	}
	catch (java.io.IOException e) {
		// Display an error message to the user
		m_output.appendText("There was an error reading ");
		m_output.appendText("input from Soar:\n" + e);
		m_output.appendText("\nThe connection has been closed.");

		// Disable so they don't try to send more input
		m_send.enable(false);
		m_input.enable(false);

		// In case someone cares...
		e.printStackTrace();
	} }

}



