import java.net.Socket;
import java.io.PrintWriter;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.OutputStream;

public class TM
	implements TMdata
	{

// Communicates with tmumon and controls the Transmogrifier

public final static int CONTROLPORT = 3462;
public final static int DATAPORT = 9100;
public final String DEFAULT_HOSTNAME = "skynet.eecg.toronto.edu";

Socket controlSocket;
PrintWriter controlOut;
BufferedReader controlIn;

Socket dataSocket;
InputStream dataIn;
OutputStream dataOut;

public TM()
	throws java.io.IOException
	{
	this(null);
}



public TM(String hostname)
	throws java.io.IOException
	{
	if(hostname == null) {
		hostname = DEFAULT_HOSTNAME;
	}

	controlSocket = new Socket(hostname, CONTROLPORT);
	controlOut = new PrintWriter(controlSocket.getOutputStream());
	controlIn = new BufferedReader(
		new InputStreamReader(controlSocket.getInputStream()));

	dataSocket = new Socket(hostname, DATAPORT);
	dataIn = dataSocket.getInputStream();
	dataOut = dataSocket.getOutputStream();
}



public String getDesignDateString()
	throws java.io.IOException
	{

	// Example of how to send a request to the control port and read the
	// reply

	String s;
	String result;

	send("getdesigndate ");
	result = readLine();
// System.out.println("read " + s);
	s = readLine();		// Discard blank line
	return(result);
}


final static int TM_MAX_PACKET_DATA = 16383;
final static int TM_PACKET_WRITE =                     0x80000000;
final static int TM_PACKET_READ =                      0x00000000;
final static int TM_PACKET_NEEDS_ACK =                 0x40000000;
final static int TM_PACKET_PORT_MASK =                 0x03FC0000;
final static int TM_PACKET_LENGTH_LOW_BITS_MASK =      0x00000FFF;
final static int TM_PACKET_LENGTH_HIGH_BITS_MASK =     0x0C000000;
final static int TM_PACKET_PORT_WIDTH_HIGH_BITS_MASK = 0x0000F000;
final static int TM_PACKET_PORT_WIDTH_LOW_BITS_MASK =  0x00030000;


public int writePacket(
          byte[] b,
          int off,
          int len,
          int port,
          int port_width_bits) throws java.io.IOException {

   // Send len bytes starting from b[off] to the given port

   int port_bytes = (port_width_bits + 7) / 8;
   int originalLen = len;

   while(len > 0) {

      int nbytes = len;

      if(nbytes > TM_MAX_PACKET_DATA) {
         nbytes = (TM_MAX_PACKET_DATA / port_bytes) * port_bytes;
      }
   
      // Write nbytes bytes to the given port
   
      int header = TM_PACKET_WRITE;
   
      // For now, the tmu ports package only supports hand-shaked ports.
      header |= TM_PACKET_NEEDS_ACK;
   
      header |= (port << 18) & TM_PACKET_PORT_MASK; 
   
      // The header bit fields are a mess, due to backwards compatibility
      header |= (nbytes & TM_PACKET_LENGTH_LOW_BITS_MASK);
      header |= ((nbytes << 14) & TM_PACKET_LENGTH_HIGH_BITS_MASK);
   
      int port_width_bytes = (port_width_bits + 7) / 8;
      
      header |= ((port_width_bytes << 10) & TM_PACKET_PORT_WIDTH_HIGH_BITS_MASK);
      header |= ((port_width_bytes << 16) & TM_PACKET_PORT_WIDTH_LOW_BITS_MASK);
   
      writeData(intToByteArray(header), 0, 4);
      writeData(b, off, nbytes);
   
      byte[] result_code_bytes = new byte[4];
   
      int retval = readData(result_code_bytes, 0, 4);
      if(retval != 4) {
         return (-1);
      }
   
      int result_code = byteArrayToInt(result_code_bytes);

      if(result_code == 0) {
         break;
      }

      if(result_code == -1) {
         return(result_code);
      }

      len -= result_code;
      off += result_code;
   }

   return(originalLen - len);
}



public int readPacket(
          byte[] b,
          int off,
          int len,
          int port,
          int port_width_bits) throws java.io.IOException {

   // Read len bytes from the given port into b[], starting at off

   int total_bytes = 0;

   while(len > 0) {
      // Read len bytes from the given port

      int nbytes = len;

      if(nbytes > TM_MAX_PACKET_DATA) {
         nbytes = TM_MAX_PACKET_DATA;
      }
   
      int header = TM_PACKET_READ;
   
      // For now, the tmu ports package only supports hand-shaked ports.
      header |= TM_PACKET_NEEDS_ACK;
   
      header |= (port << 18) & TM_PACKET_PORT_MASK; 
   
      // The header bit fields are a mess, due to backwards compatibility
      header |= (nbytes & TM_PACKET_LENGTH_LOW_BITS_MASK);
      header |= ((nbytes << 14) & TM_PACKET_LENGTH_HIGH_BITS_MASK);
   
      int port_width_bytes = (port_width_bits + 7) / 8;
      
      header |= ((port_width_bytes << 10) & TM_PACKET_PORT_WIDTH_HIGH_BITS_MASK);
      header |= ((port_width_bytes << 16) & TM_PACKET_PORT_WIDTH_LOW_BITS_MASK);
   
      writeData(intToByteArray(header), 0, 4);
   
      byte[] result_code_bytes = new byte[4];
   
      int retval = readData(result_code_bytes, 0, 4);
      if(retval != 4) {
         return (-1);
      }
   
      int result_code = byteArrayToInt(result_code_bytes);
      if(result_code != nbytes) {
         return (-1);
      }
   
      retval = readData(b, off, nbytes);
      if(retval != nbytes ) {
         return (-1);
      }

      total_bytes += retval;
      len -= retval;
      off += retval;
   }

   return(total_bytes);
}



final byte[] intToByteArray(int value) {

   // Convert an integer to a byte array in big-endian order

   return new byte[] {
           (byte)(value >>> 24),
           (byte)(value >>> 16),
           (byte)(value >>> 8),
           (byte)value};
}


int byteArrayToInt(byte[] b) {

   // Create an int value from 4 bytes that are in big-endian order

   return(
	(((int)b[0] & 0xFF) << 24)
	| (((int)b[1] & 0xFF) << 16)
	| (((int)b[2] & 0xFF) << 8)
	| (((int)b[3] & 0xFF) << 0)
	);
}



int readData(byte[] b, int off, int len)
	throws java.io.IOException
	{

	// Read len bytes from the data socket

	int originalLen = len;

	while(len > 0) {
		int retval = dataIn.read(b, off, len);

		len -= retval;
		off += retval;
	}

	return(originalLen);
}



void writeData(byte[] b, int off, int len)
	throws java.io.IOException
	{

	// Write len bytes to the data socket

	dataOut.write(b, off, len);
}



void send(String command)
	throws java.io.IOException
	{
	controlOut.println(command);
	controlOut.flush();
}



void sendCommand(String command)
	throws java.io.IOException
	{
	String s;

	send(command);
	s = readLine();
}



String readLine()
	throws java.io.IOException
	{
	return(controlIn.readLine());
}

}
