package bugbeechat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;

import com.buglabs.bug.module.bugbee.pub.IBUGBeeControl;
import com.buglabs.bug.module.bugbee.pub.IBUGBeeLEDControl;
import com.buglabs.bug.module.bugbee.pub.IBUGBeePacket;
import com.buglabs.bug.module.bugbee.pub.IEEEAddress;

public class NetworkHandler {
	/**
	 * maximum size of the data portion of a packet
	 * TODO: This should be in the API.
	 */
	public static final int MAX_DATA_SIZE = 84;
	
	/*
	 * The various states of our state machine. The numerical values have no significance.
	 */
	private static final int STATE_INIT							= 0;
	private static final int STATE_START						= 1;
	private static final int STATE_JOINING						= 2;
	private static final int STATE_WAIT_FOR_PEER_TO_BIND_TO_US	= 3;
	private static final int STATE_BIND_REQUEST					= 4;
	private static final int STATE_WAIT_FOR_BINDING_RESPONSE	= 5;
	private static final int STATE_CAN_SEND_DATA				= 6;
	
	/**
	 * The minimum length of a command response, that we could
	 * receive via our command proc (1 byte data len, which would
	 * be set to zero plus 2 bytes for the command id) 
	 */
	private static final int MIN_COMMAND_RESPONSE_LENGTH = 3;
	
	/**
	 * The maximum length of a command response, that we could
	 * receive via our command proc.
	 * TODO: Figure out what this should really be. Around 300? 
	 */
	private static final int MAX_MESSAGE_LENGTH = 512;
	
	/**
	 * The default source and destination endpoint ID on the XBee ZBs
	 * (ATSE and ATDE commands).
	 * Used for interoperability with XBee ZBs
	 */
	private static final byte DIGI_DATA_ENDPOINT_ID = (byte) 0xE8;
	
	/**
	 * The default cluster ID on the XBee ZBs (ATCI command).
	 * This is the 'send or receive plain old character data' "function"
	 * Used for interoperability with XBee ZBs
	 */
	private static final short DIGI_TRANSPARENT_DATA_CLUSTER_ID = 0x0011;
	
	/**
	 * The profile id on the XBee ZBs (don't think you can change this one) 
	 * Used for interoperability with XBee ZBs
	 */
	private static final short DIGI_PROFILE_ID = (short) 0xC105;
	
	/**
	 * Our device id. This is arbitrary, and doesn't need to be unique.
	 */
	private static final short DEVICE_ID = 0x0003;
	
	/**
	 * Our version. This is arbitrary, and doesn't need to be unique.
	 */
	private static final byte DEVICE_VERSION = 1;
	
	/**
	 * When sending data, TRUE if requesting acknowledgment from the destination.
	 */
	private static final byte WANT_ACK = 1;
	
	/**
	 * When sending data, the max number of hops the packet can travel 
	 * through before it is dropped.
	 */
	
	private static final byte RADIUS = 7;
	
	/**
	 * This is the binding address and should be used when a
	 * binding entry has been previously created for this particular 
	 * CommandId/ClusterId. The destination address will be determined 
	 * from the binding table by the ZigBee chip.
	 */
	private static final short BINDING_ADDRESS = (short) 0xFFFE;
			
	/**
	 * the PAN ID we want to establish / join
	 */
	private static final short PAN_ID = 0x1111;
	
	/**
	 * the channel mask to use when establishing / joining the PAN. 
	 */
	private static final int CHANNEL_LIST = 0x00010000;

	/**
	 * If true the COORD will wait for the ED to bind to it before
	 * attempting to bind to the ED.
	 * TODO: figure out if binding to each other simultaneously
	 * is causing them to drown each other out.
	 */
	private static final boolean WAIT_FOR_ED = true;
	
	/*
	 * WARNING:
	 * 
	 * With PA enabled:
	 * To avoid excessive RF power to the front end, the BUGbees should be placed
	 * about 2 feet apart. In addition, to prevent data corruption, the units should be 
	 * operated with obstructions in between them if in a lab/office environment, 
	 * or about 5-10 meters away from each other if in line of sight.
	 * 
	 * The reason why the 100 mW device was chosen, was to compensate for the path
	 * losses associated with the directionality of the antenna. As a result, in
	 * our architecture, the PA should always be enabled when transmitting data.
	 * Range tests indicated that with the PA enabled, as long as we use the
	 * on_board printed antenna, the device will give us a range 50 to 75 feet
	 * indoors with obstructions, and 700 to 1000 feet line of sight.
	 * Any power level settings between 0 and 20 dBm will not provide any
	 * advantage. As a result, the settings for the PA should be enabled-full
	 * power or disabled.
	 */
	private static final boolean PA_ENABLED = true;
	
	/**
	 * current state we're in; used by {@link BUGBeeChatApplication#stateMachineProc()}
	 */
	private int state = STATE_INIT;
	
	private final IBUGBeeControl control;
	
	private final IBUGBeeLEDControl led;
	
	private final IBUGBeePacket packet;
	
	/**
	 * true if we are to be the coordinator, false if we are to just be an end device.
	 * (we could choose to be a router instead of an end device but, since we only care
	 * here about peer-to-peer, being an end-device is fine).
	 * We set this in our ctor based on {@link BUGBeeChatApplication#COORD_FILE} 
	 */
	private final boolean runAsCoord;
	
	private final DataReceiver dataReceiver;
	
	/**
	 *  A handle used to identify the send data request; a message id if you prefer.
	 *  We increment this for every message we send. 
	 */
	private byte handle = 0;
	
	private static final int MAX_BIND_ATTEMPTS = 10;
	
	private byte failedBindCount = 0;
	
	NetworkHandler(final IBUGBeeControl control,
			final IBUGBeeLEDControl led,
			final IBUGBeePacket packet,
			final boolean runAsCoord,
			final DataReceiver dataReceiver) {
		this.control = control;
		this.led = led;
		this.packet = packet;
		this.runAsCoord = runAsCoord;
		this.dataReceiver = dataReceiver;
	}
	
	public void networkSetup() throws IOException {		
		if (runAsCoord) {
			log("RUN: Setting up as an Coordinator");
			control.setLogicalTypeConfig(IBUGBeeControl.LOGICAL_TYPE_COORDINATOR);
			control.setUserDescConfig("BUGBeeChat Parent");
		} else {
			log("RUN: Setting up as an end device");
			control.setLogicalTypeConfig(IBUGBeeControl.LOGICAL_TYPE_END_DEVICE);
			control.setUserDescConfig("BUGBeeChat Child");
		}
		
		log("RUN: Setting channel list");
		control.setChannelListConfig(CHANNEL_LIST);
		
		log("RUN: Setting PANID");
		control.setPANIDConfig(PAN_ID);
		
		log("RUN: Clearing previous network state");
		control.setStartUpOptionConfig(IBUGBeeControl.START_OP_CLEAR_STATE);
	}
	
	public void networkTearDown() {
		/* 
	 	 * If we're able, undo our changes to the configuration and reset.
		 */
		try {
			led.LEDRedOff();
			led.LEDGreenOff();
			
			control.setStartUpOptionConfig((byte) 
					(IBUGBeeControl.START_OP_CLEAR_STATE |
					 IBUGBeeControl.START_OP_CLEAR_CONFIG));
			control.reset(IBUGBeeControl.HW_RESET);
			control.setStartUpOptionConfig(IBUGBeeControl.START_OP_CURRENT);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void poll() {
		try {
			controlMessageProc();
			dataMessageProc();
			stateMachineProc();			
		} catch (IOException e) {
			log("State Machine Proc Exception:");
			e.printStackTrace();
		}
	}
	
	/**
	 * Our state machine that gets us through the stages of starting up the
	 * stack on the ZigBee device, establishing or joining a PAN and binding
	 * to our peer so that we can send data to them.
	 */
	private void stateMachineProc() throws IOException {
		switch(state)
		{
		case STATE_INIT:
			dataReceiver.updateLocal2RemoteStatus("resetting");
			log("STATE_INIT: issuing a hw reset");
			control.reset(IBUGBeeControl.HW_RESET);
						
			dataReceiver.updateLocal2RemoteStatus("starting i/f");
			log("STATE_START_INTERFACE: Bringing up the ZigBee network interface");
			control.setInterfaceState(true);
			
			if (PA_ENABLED) {
				// WARNING: See note where PA_ENABLED is defined, but basically
				// don't have 2 BUGbees near each other if you turn on the PA
				
				// MAX power as (again, see note) other settings don't make
				// much difference.
				control.configurePA(true, (byte) 0);
			}
			
			state = STATE_START;
			break;
			
		case STATE_START:
			led.LEDRedOn();
						
			// need to register our application profile so that our peer
			// will be able to bind to it, and so that the ZigBee device
			// will pass along the data messages for us. We use the same
			// cluster id / command id for inbound and outbound since
			// we can both send and receive text messages.
			log("STATE_START: Registering our application profile");
			control.registerAppRequest(
					DIGI_DATA_ENDPOINT_ID,
					DIGI_PROFILE_ID,
					DEVICE_ID,
					DEVICE_VERSION,
					new short[] {DIGI_TRANSPARENT_DATA_CLUSTER_ID},
					new short[] {DIGI_TRANSPARENT_DATA_CLUSTER_ID});
			dataReceiver.updateRemote2LocalStatus("registered app profile");
			
			log("STATE_START: Requesting that the ZigBee stack start");
			control.startRequest();
			state = STATE_JOINING;
			break;
			
		case STATE_JOINING:
			final int deviceState = control.getState();
			switch (deviceState) {
			case IBUGBeeControl.DEV_ZB_COORD:
				log("COORD is up");
				log("Permitting joining");
				control.permitJoining(IBUGBeeControl.ALL_ROUTERS_AND_COORD_ADDRESS,
						IBUGBeeControl.JOINING_ON_INDEFINITELY_TIMEOUT);
				dataReceiver.updateRemote2LocalStatus("COORD: permitted joining");
				
				// arrange so ED will be able to bind to us
				allowBinding();
				
				if (WAIT_FOR_ED) {
					// we know our peer can't be around yet so we just need to wait
					state = STATE_WAIT_FOR_PEER_TO_BIND_TO_US;					
				} else {
					// let's move on
					state = STATE_BIND_REQUEST;									
				}
				break;
				
			case IBUGBeeControl.DEV_END_DEVICE:
				log("ED is up");
				log("Our short address is 0x" + Integer.toHexString(control.getShortAddress() & 0xFFFF));
				// arrange so COORD will be able to bind to us
				allowBinding();
				state = STATE_BIND_REQUEST;
				break;
				
			default:
				// probably not up on network yet
				log("Device state: " + deviceState);
				break;
			}
			break;
			
		case STATE_WAIT_FOR_PEER_TO_BIND_TO_US:
			dataReceiver.updateLocal2RemoteStatus("waiting for peer to bind to us");
			log("STATE_WAIT_FOR_PEER_TO_BIND_TO_US");
			break;
			
		case STATE_BIND_REQUEST:
			dataReceiver.updateLocal2RemoteStatus("attempting bind");
			log("STATE_BIND_REQUEST");
			control.bindDeviceRequest(
					IBUGBeeControl.CREATE_BINDING_REQUEST,
					DIGI_TRANSPARENT_DATA_CLUSTER_ID,
					// all-zeros address for destination means binding will
					// be established with another device that is in
					// Allow Bind mode (if there is one of course).
					// Replace this with the IEEE address of a peer
					// device if you want to bind to a specific one
					new IEEEAddress(new byte[] {
							
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00
							
							/*
							// address(es) of my BUGbees, possibly backwards 
							(byte) 0x46,
							(byte) 0x81,
							(byte) 0x7E,
							(byte) 0x96,
							(byte) 0x00,
							(byte) 0x00,
							(byte) 0x00,
							(byte) (RUN_AS_COORD ? 0x35 : 0x57)
							*/
							/*
							// example IEEE address for one of my XBee ZB devices
							(byte) 0x00,
							(byte) 0x13,
							(byte) 0xA2,
							(byte) 0x00,
							(byte) 0x40,
							(byte) 0x2D,
							(byte) 0x34,
							(byte) 0x29
							*/
							}));
			state = STATE_WAIT_FOR_BINDING_RESPONSE;
			break;

		case STATE_WAIT_FOR_BINDING_RESPONSE:
			dataReceiver.updateLocal2RemoteStatus("waiting for binding response");
			log("STATE_WAIT_FOR_BINDING_RESPONSE");
			break;

		case STATE_CAN_SEND_DATA:
			//System.out.println("STATE_CAN_SEND_DATA");
			break;

		default:
			break;
		}
	}

	private void allowBinding() throws IOException {
		// need to permit binding so our peer can bind to us to be
		// able to send us messages
		log("STATE_START: Permitting binding");
		// timeout value is wrong (65) - should be 0xFF; this is
		// an error in the CC2480 and will be corrected in IBUGBeeControl
		control.allowBind((byte)0xFF /*IBUGBeeControl.BINDING_ALLOWED_INDEFINITELY_TIMEOUT*/);
		dataReceiver.updateRemote2LocalStatus("permitted binding");
	}

	/**
	 * poll for a data packet coming in on the packet socket and process
	 * it if there's one there.
	 * Packet format:
	 * 0,1   (2 bytes) source device short address
	 * 2,3   (2 bytes) command id
	 * 4,5   (2 bytes) data length (for our expected packet type anyway)
	 * 6,... (x bytes) character data to display
	 * @throws IOException
	 */
	private void dataMessageProc() throws IOException
	{
		final byte [] buf = new byte[MAX_MESSAGE_LENGTH];

		//System.out.println("DATA: waiting for message");
		final int len = packet.read(buf, buf.length, IBUGBeeControl.MSG_DONTWAIT);
		//System.out.println("CONTROL: received " + len + " byte message:");
		for (int i = 0; i < len; ++i) {
			log("datamsg[" + i + "] = 0x" + Integer.toHexString(buf[i] & 0xFF));
		}
		
		if (len <= 0) {
			//System.out.println("DATA: no message");
			return;
		}
		
		// use red led to indicate that we're processing a received data packet
		led.LEDRedOn();
		
		final short fromShortAddress = (short) (((buf[1] & 0xFF) << 8) | (buf[0] & 0xFF) & 0xFFFF);
		log("DATA: Message received from device with short address " + fromShortAddress);
		
		final short commandId = (short) (((buf[3] & 0xFF) << 8) | (buf[2] & 0xFF) & 0xFFFF);
		log("DATA: Message received from device with command " + commandId);
		
		if (commandId != DIGI_TRANSPARENT_DATA_CLUSTER_ID) {
			log("DATA: Unexpected command id");
		} else {
			// it's what we wanted
			final short dataLen = (short) (((buf[5] & 0xFF) << 8) | (buf[4] & 0xFF) & 0xFFFF);
			final String msg = new String(buf, 6, dataLen);
			log("DATA: Msg is '" + msg + "'");
			dataReceiver.receiveData(msg);
			dataReceiver.updateRemote2LocalStatus("rcvd data");
		}
		led.LEDRedOff();
	}

	public void sendDataMessage(final String msg) throws IOException {
		final StringBuffer sb = new StringBuffer(msg);
		
		// do it in chunks if we need to
		while (sb.length() > NetworkHandler.MAX_DATA_SIZE) {
			final String msgChunk = sb.substring(0, NetworkHandler.MAX_DATA_SIZE);
			sendDataChunk(msgChunk);
			sb.delete(0, NetworkHandler.MAX_DATA_SIZE);
		}
		
		// send the leftovers
		sendDataChunk(sb.toString());		
	}
	
	/**
	 * Send a data chunk to our peer.
	 * @param msg the message to send. must be no longer than {@link #MAX_DATA_SIZE}.
	 * @throws IOException
	 */
	private void sendDataChunk(final String msg) throws IOException {
		if (msg.length() > MAX_DATA_SIZE) {
			throw new IOException("Data request too large at " + msg.length());
		}

		/*
		 * Build our send-data-request packet.
		 */
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		out.write(new byte[] {
				// 16-bit short address of the destination device
				(byte) ( BINDING_ADDRESS       & 0xFF),
				(byte) ((BINDING_ADDRESS >> 8) & 0xFF),
				// 16-bit CommandID
				(byte) ( DIGI_TRANSPARENT_DATA_CLUSTER_ID       & 0xFF),
				(byte) ((DIGI_TRANSPARENT_DATA_CLUSTER_ID >> 8) & 0xFF),
				// handle used to identify the send data request
				handle++,
				// whether we want an acknowledgment back from the destination
				WANT_ACK,
				// The max number of hops the packet can travel through before it is dropped.
				RADIUS});
		
		// the size of the Data buffer in bytes
		out.write(msg.length());
		
		// the data
		out.write(msg.getBytes());
		
		final byte [] buf = out.toByteArray();
		
		// ship it!
		log("Sending message to partner");
		packet.write(buf, buf.length, 0);
	}
	
	
	
	/**
	 * wait for and handle control messages
	 * TODO: Parsing the data into message objects needs to
	 * move down into the API.
	 * TODO: This polling is for the birds. Make it block for the 1-byte
	 * length header then block for the commandid's 2 bytes plus the msg len.
	 * @throws IOException
	 */
	private void controlMessageProc() throws IOException {
		final byte [] buf = new byte[MAX_MESSAGE_LENGTH];
		byte status;

		//System.out.println("CONTROL: waiting for message");
		final int len = control.read(buf, buf.length, IBUGBeeControl.MSG_DONTWAIT);
		//System.out.println("CONTROL: received " + len + " byte message:");
		for (int i = 0; i < len; ++i) {
			log("controlmsg[" + i + "] = 0x" + Integer.toHexString(buf[i] & 0xFF));
		}
		
		if (len < MIN_COMMAND_RESPONSE_LENGTH) {
			//System.out.println("CONTROL: message is too short; skipping it.");
			return;
		}
		
		final DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf, 0, len));
		
		final int dataLen = in.readByte();		
		log("CONTROL: message has " + dataLen + " data bytes");

		final short commandId = byteSwap(in.readShort());		
		log("control message command id = 0x" + Integer.toHexString(commandId & 0xFFFF));
		
		switch (commandId)
		{
			case IBUGBeeControl.ZB_ALLOW_BIND_CONFIRM:
				/*
				 * This command is issued by the ZigBee device when it responds to a bind 
				 * request from a remote device.
				 * Data: (2 bytes) short address of the source device.
				 */
				final short sourceShortAddress = byteSwap(in.readShort());
				log("CONTROL: Rcvd Bind Request from 0x" + Integer.toHexString(sourceShortAddress & 0xFFFF));
				dataReceiver.updateRemote2LocalStatus("rcvd bind request from 0x" + Integer.toHexString(sourceShortAddress & 0xFFFF));

				// TODO: figure out if it's okay we're turning off binding
				// leave binding on for now; TODO: figure out when we've lost contact then
				// we could be smarter about keeping bind off the rest of the time
				//disallowBinding();
				
				if (runAsCoord) {
					if (WAIT_FOR_ED) {
						// time to try binding to our peer, since we just let it bind to us
						state = STATE_BIND_REQUEST;
					}
				}
				break;
	
			case IBUGBeeControl.ZB_BIND_CONFIRM:
				/*
				 * This command is issued by the ZigBee device to return the results 
				 * from a ZB_BIND_DEVICE command.
				 * Data: (2 bytes) command ID of the binding being confirmed.
				 *       (1 byte)  status (IBUGBeeControl.ZSUCCESS etc)
				 */
				if (state != STATE_WAIT_FOR_BINDING_RESPONSE) {
					log("CONTROL: didn't expect to be in this state " + state +
							" - ignoring bind confirm");
				}
				else {
					final short confirmedCommandId = byteSwap(in.readShort());
					log("CONTROL: Rcvd Bind Confirm for command id 0x" + Integer.toHexString(confirmedCommandId & 0xFFFF));
					
					status = in.readByte();
					if (status != IBUGBeeControl.ZSUCCESS) {
						log("CONTROL: Unable to bind to peer, status is 0x" + Integer.toHexString(status & 0xFF));
						dataReceiver.updateLocal2RemoteStatus("bind failed - 0x" + Integer.toHexString(status & 0xFF));
						
						if (++failedBindCount > MAX_BIND_ATTEMPTS) {
							log("CONTROL: Exceeded max bind attempts, reinit");
							failedBindCount = 0;
							state = STATE_INIT;
						} else {
							// try again
							state = STATE_JOINING;
						}
						led.LEDGreenOff();
					}
					else {
						log("CONTROL: Our peer confirmed that we are bound to it.");
						dataReceiver.updateLocal2RemoteStatus("bind accepted");
						
						if (!runAsCoord) {
							// let coord call us now
							allowBinding();
						}
						
						state = STATE_CAN_SEND_DATA;
						led.LEDGreenOn();
					}
					
				}
				break;

			case IBUGBeeControl.ZB_SEND_DATA_CONFIRM:
			case IBUGBeeControl.AF_DATA_CNF:
				/*
				 * This command is issued by the ZigBee device to return 
				 * the results from a SEND_DATA_REQUEST command.
				 * Data: (1 byte) handle
				 *       (1 byte) status
				 */
				if (commandId == IBUGBeeControl.ZB_SEND_DATA_CONFIRM) {
					in.skipBytes(1); // TODO: confirm throwing this byte away is ok
				}

				status = in.readByte();
				if (status != IBUGBeeControl.ZSUCCESS) {
					log("CONTROL: Unable to send data to our peer, status was 0x" + Integer.toHexString(status & 0xFF));
					dataReceiver.updateLocal2RemoteStatus("send failed (0x" + Integer.toHexString(status & 0xFF) + ") rebinding");
					// drop and go back to trying to bind again
					state = STATE_JOINING;
				} else if (state != STATE_CAN_SEND_DATA) {
					log("CONTROL: Got send-data response but we were in state " + state);
				}
				
				led.LEDGreenOff();
				break;

			case IBUGBeeControl.ZB_RESET_IND:
				/*
				 * This command is generated by the ZigBee device automatically 
				 * immediately after a reset.
				 * Data: (1 byte) reason (0x00==Power-up, 0x01==External, 0x02==Watch-dog)
				 *       (1 byte) transport rev
				 *       (1 byte) product id
				 *       (1 byte) major rel
				 *       (1 byte) minor rel
				 *       (1 byte) hw rev
				 */
				status = in.readByte();
				log("CONTROL: Reset reason  0x" + Integer.toHexString(status & 0xFF));
				log("transport rev " + in.readByte());
				log("product id    " + in.readByte());
				log("major rel     " + in.readByte());
				log("minor rel     " + in.readByte());
				log("hw rev        " + in.readByte());

				/*
				 * received internal reset.
				 * Start over.
				 */
				if (status == IBUGBeeControl.ZB_RESET_BY_WATCHDOG) {
					// we didn't trigger this one (on purpose anyway) so start from scratch
					state = STATE_INIT;
				}
				break;

			case IBUGBeeControl.ZB_START_CONFIRM:
				/*
				 * This command is issued by the ZigBee device to return the results 
				 * from a start request command.
				 */
				log("CONTROL: Start Confirm received");
				
				// IBUGBeeControl.startRequest() blocks for this to come back anyway, 
				// so nothing to do here (and we could have just ignored it)
				// TODO: does this really make it up to app-level, or does the
				// driver swallow it.
				break;
	
			case IBUGBeeControl.ZDO_END_DEVICE_ANNCE_IND:
				/*
				 * This command is issued by the ZigBee device when it has 
				 * received an “End device announce” packet from a remote device
				 * Data: (2 bytes) SrcAddr - source address of the message.
				 *       (2 bytes) NwkAddr - device's short address
				 *       (8 bytes) IEEEAddr - 64-bit IEEE address of source device
				 *       (1 byte) Capabilities - MAC capabilities of the device (bitmask, see docs)
				 */
				final short srcAddr = byteSwap(in.readShort());
				final short nwkAddr = byteSwap(in.readShort());
				final byte [] ieBytes = new byte[IEEEAddress.LENGTH];
				in.readFully(ieBytes);
				
				final IEEEAddress ieeeAddr = new IEEEAddress(ieBytes);
				final byte capabilities = in.readByte();
				
				log("CONTROL: Got End Device Annce Ind:");
				log("srcAddr: 0x" + Integer.toHexString(srcAddr & 0xFFFF));
				log("nwkAddr: 0x" + Integer.toHexString(nwkAddr & 0xFFFF));
				log("IEEE Addr: " + ieeeAddr);
				log("capabilities: 0x" + Integer.toHexString(capabilities & 0xFF));
				break;
				
			default:
				log("CONTROL: Ignoring unrecognized command id of 0x" + 
						Integer.toHexString(commandId & 0xFFFF));
				break;			
		}
	}

	private void disallowBinding() throws IOException {
		log("CONTROL: Disallowing future binds since we've got a peer now");
		control.allowBind(IBUGBeeControl.BINDING_DISALLOWED_TIMEOUT);
	}
	
	private String lastMsg = "";
	private synchronized void log(final String msg) {
		if (!msg.equals(lastMsg)) {
			System.out.println(msg);
			lastMsg = msg;
		}
	}
	
	/**
	 * convenience function to change the endianness of a short.
	 * @param in the short to byte swap
	 * @return the byte-swapped short
	 */
	private static short byteSwap(final short in) {
		return (short) (((in & 0xFF) << 8) | ((in >> 8) & 0xFF) & 0xFFFF); 
	}
	
	public boolean isConnected() {
		return state == STATE_CAN_SEND_DATA;
	}
}