package org.apache.cordova.bluetooth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.lang.reflect.Method;

import java.util.Set;
import java.util.UUID;
import java.util.ArrayList;

import android.annotation.TargetApi;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothAdapter;

import android.content.Intent;
import android.content.Context;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;

import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.AsyncTask;
import android.os.Parcelable;

import android.util.Log;


/**
 * Wrapper for the standard Bluetooth API found in Android. Contains threads for
 * connecting and managing a connection. Please note that this is designed as a
 * thin wrapper around Android's native Bluetooth API and doesn't contain any
 * internal checks against overwriting active connections with new ones etc.
 *
 * @see BluetoothAdapter
 * @see BluetoothDevice
 * @see BluetoothSocket
 */
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
public class BluetoothWrapper
{
	private static final String LOG_TAG = "BluetoothWrapper";

	public static final int MSG_DISCOVERY_STARTED		= 0;
	public static final int MSG_DISCOVERY_FINISHED		= 1;
	public static final int MSG_DEVICE_FOUND			= 2;
	public static final int MSG_CONNECTION_ESTABLISHED	= 3;
	public static final int MSG_CONNECTION_FAILED		= 4;
	public static final int MSG_CONNECTION_STOPPED		= 5;
	public static final int MSG_CONNECTION_LOST			= 6;
	public static final int MSG_READ					= 8;
	public static final int MSG_BLUETOOTH_LOST			= 9;
	public static final int MSG_UUIDS_FOUND				= 10;
	public static final int MSG_DEVICE_BONDED			= 11;
	public static final int MSG_DEVICE_CONNECTED        = 12;

	public static final String DATA_DEVICE_ADDRESS 		= "DeviceAddress";
	public static final String DATA_DEVICE_NAME			= "DeviceName";
	public static final String DATA_DEVICE_BOND_STATE	= "BondState";
	public static final String DATA_BYTES				= "Bytes";
	public static final String DATA_BYTES_READ			= "BytesRead";
	public static final String DATA_UUIDS				= "Uuids";
	public static final String DATA_ERROR				= "Error";

	/**
	 * Is used to send messages back to the user of this class.
	 * Message types are specified above with the prefix MSG
	 */
	private Handler 			_handler;

	/**
	 * Android's BluetoothAdapter
	 */
	private BluetoothAdapter 	_adapter;

	/**
	 * Socket that is synchronized between threads to allow stopping ConnectionManager
	 * while retaining the connection itself.
	 */
	private BluetoothSocket		_socket;

	/**
	 * Thread for attempting a connection. When successful, initializes a connected socket to the
	 * <b>_socket</b> member of BluetoothWrapper.
	 */
	private ConnectionAttempt	_connectionAttempt;

	/**
	 * Thread for managing an active connection. Requires a connected socket to perform
	 * read/write operations.
	 */
	private ConnectionManager 	_connectionManager;

	/**
	 * Enumeration for various types of connections we can attempt.
	 *
	 * @see BluetoothDevice
	 * @see BluetoothSocket
	 */
	public enum EConnectionType
	{
		/**
		 * Create a standard secure connection.
		 */
		Secure,

		/**
		 * Create a standard insecure connection.
		 */
		Insecure,

		/**
		 * Use reflection to create the socket, seems very volatile.
		 */
		Hax
	}

	/**
	 * Constructor for the BluetoothWrapper class. Registers correct receivers for Bluetooth events.
	 *
	 * @param ctx       Application context, used to register receiver for various bluetooth related events.
	 * @param handler	A Handler that is sent Messages using the codes specified in this class.
	 *
	 * @see Context
	 * @see Handler
	 * @see Message
	 */
	public BluetoothWrapper(Context ctx, Handler handler)
	{
		_handler = handler;
		_adapter = BluetoothAdapter.getDefaultAdapter();

		IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
		ctx.registerReceiver(_receiver, filter);

		filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
		ctx.registerReceiver(_receiver, filter);

		filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
		ctx.registerReceiver(_receiver, filter);

		filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
		ctx.registerReceiver(_receiver, filter);

		filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
		ctx.registerReceiver(_receiver, filter);

		filter = new IntentFilter(BluetoothDevice.ACTION_UUID);
		ctx.registerReceiver(_receiver, filter);
	}

	/**
	 * Check whether Bluetooth is on or off.
	 *
	 * @return Flag indicating if Bluetooth is enabled on this device.
	 * @throws Exception When there is an error deducing adapter state.
	 */
	public boolean isEnabled() throws Exception
	{
		try
		{
			return _adapter.getState() == BluetoothAdapter.STATE_ON;
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Enable Bluetooth without direct user consent. Careful!
	 *
	 * @throws Exception When there is an error enabling Bluetooth.
	 */
	public void enable() throws Exception
	{
		try
		{
			if(!_adapter.enable())
			{
				if(isEnabled())
				{
					throw new Exception("Bluetooth is already on.");
				}
				else
				{
					throw new Exception("Error enabling Bluetooth.");
				}
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Disable Bluetooth without direct user consent.
	 *
	 * @throws Exception When there is an error disabling Bluetooth.
	 */
	public void disable() throws Exception
	{
		try
		{
			if(!_adapter.disable())
			{
				if(!isEnabled())
				{
					throw new Exception("Bluetooth is already off.");
				}
				else
				{
					throw new Exception("Error disabling Bluetooth.");
				}
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * See if there is an ongoing device discovery process going on.
	 *
	 * @return True if Bluetooth is on and device discovery is in progress. Otherwise false.
	 * @throws Exception If there is an error checking whether the discovery process is in progress.
	 */
	public boolean isDiscovering() throws Exception
	{
		try
		{
			return _adapter.isEnabled() && _adapter.isDiscovering();
		}
		catch(Exception e)
		{
			throw e;
		}
	}

	/**
	 * Start a device discovery process. Results are broadcasted to the
	 * Handler registered to this class. This will not cancel any current
	 * discovery process, but you should do it anyways.
	 *
	 * @throws Exception If there is an error starting the discovery process.
	 *
	 * @see BluetoothDevice
	 */
	public void startDiscovery() throws Exception
	{
		try
		{
			if(!_adapter.startDiscovery())
			{
				throw new Exception("Error starting discovery.");
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Cancel the current discovery process.
	 *
	 * @throws Exception If there is an error with canceling the current discovery process.
	 */
	public void stopDiscovery() throws Exception
	{
		try
		{
			if(!_adapter.cancelDiscovery())
			{
				if(!_adapter.isDiscovering())
				{
					throw new Exception("There is no discovery process in progress.");
				}
				else
				{
					throw new Exception("Error canceling the discovery process.");
				}
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Check if the device at given address is bonded with this device.
	 *
	 * @param address The device we want to check against.
	 * @return Flag indicating whether the devices are bonded.
	 * @throws Exception If there is a problem deducing the bond state. A wrong address might also cause this. :)
	 *
	 * @see BluetoothDevice
	 */
	public boolean isBonded(String address) throws Exception
	{
		try
		{
			BluetoothDevice device = _adapter.getRemoteDevice(address);
			return device.getBondState() == BluetoothDevice.BOND_BONDED;
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Get the devices bonded with this device.
	 *
	 * @return a list of Pairs. Each pair contains members <b>a</b> and <b>b</b>:
	 * <b>a</b> is the device name and <b>b</b> the device address.
	 *
	 * @see Pair
	 */
	public ArrayList<Pair<String>> getBondedDevices()
	{
		Set<BluetoothDevice> bondedDevices 	= _adapter.getBondedDevices();
		ArrayList<Pair<String>> devices 	= new ArrayList<Pair<String>>();

		for(BluetoothDevice device : bondedDevices)
		{
			devices.add(new Pair<String>(device.getName(), device.getAddress()));
		}

		return devices;
	}


	/**
	 * Attempt to bond with the device at given address.
	 *
	 * @param address The address of the device to bond with.
	 * @throws Exception If there is an error bonding with the device.
	 *
	 * @see BluetoothDevice
	 */
	public void createBond(String address) throws Exception
	{
		try
		{
			BluetoothDevice device = _adapter.getRemoteDevice(address);
			if(device.getBondState() == BluetoothDevice.BOND_BONDED)
			{
				throw new Exception("The device is alraedy paired.");
			}

			Method createBond = device.getClass().getMethod("createBond");
			if(!(Boolean)createBond.invoke(device))
			{
				throw new Exception("Failed to start the bonding process with given device.");
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Remove the bond between the device at given address and this device.
	 *
	 * @param address The address of the device to be unbound.
	 * @throws Exception If there is an error removing the bond between this and that device.
	 *
	 * @see BluetoothDevice
	 */
	public void removeBond(String address) throws Exception
	{
		try
		{
			BluetoothDevice device = _adapter.getRemoteDevice(address);
			if(device.getBondState() != BluetoothDevice.BOND_BONDED)
			{
				throw new Exception("Device at given address is not bonded.");
			}

			Method removeBond = device.getClass().getMethod("removeBond");
			if(!(Boolean)removeBond.invoke(device))
			{
				throw new Exception("Failed to remove bond with given device.");
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Fetch the UUID's of the device at given address.
	 *
	 * @param address The address of the device to fetch UUIDs from.
	 * @throws Exception If there was an error starting the fetching process.
	 *
	 * @see UUID
	 */
	public void fetchUuids(String address) throws Exception
	{
		try
		{
			BluetoothDevice device = _adapter.getRemoteDevice(address);
			if(!device.fetchUuidsWithSdp())
			{
				throw new Exception("Failed to start fetching UUIDs for the device at given address.");
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}

	/**
	 * Check if there is an ongoing connection attempt.
	 *
	 * @return True if a connection attempt is in progress.
	 */
	public boolean isConnecting()
	{
		if(_connectionAttempt != null)
		{
			return _connectionAttempt.getStatus() == AsyncTask.Status.RUNNING;
		}
		return false;
	}

	/**
	 * Check if there is a connected socket.
	 *
	 * @return A flag indicating whether there is a Connected Socket
	 *
	 * @see BluetoothSocket
	 */
	public boolean isConnected()
	{
		if(_socket != null)
		{
			return true;
		}
		return false;
	}


	/**
	 * Check if there is a connected socket that is managed (allows read/write operations).
	 *
	 * @return Flag indicating whether there is an active managed connection
	 */
	public boolean isConnectionManaged()
	{
		if(_connectionManager != null)
		{
			return _connectionManager.isAlive();
		}
		return false;
	}


	/**
	 * Attempts a connection to the specified address. Please note that this does not disconnect
	 * any current connections, and you have to do that manually.
	 *
	 * @param address The address of the device you want to connect to.
	 * @param uuidStr The UUID you want to connect with, or to.
	 * @param connTypeStr The type of connection you want to attempt.
	 * @throws Exception If there is an error starting the connection attempt.
	 *
	 * @see ConnectionAttempt
	 * @see ConnectionManager
	 */
	public void connect(String address, String uuidStr, String connTypeStr) throws Exception
	{
		try
		{
			BluetoothDevice device 		= _adapter.getRemoteDevice(address);
			UUID uuid					= UUID.fromString(uuidStr);
			EConnectionType connType 	= EConnectionType.valueOf(connTypeStr);

			_connectionAttempt = new ConnectionAttempt(device, uuid, connType);
			_connectionAttempt.execute();
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Attempts to disconnect the current connection. Closes the socket if it is open.
	 *
	 * @throws Exception If there is an error disconnecting (no connection to close).
	 */
	public void disconnect() throws Exception
	{
		try
		{
			if(isConnecting() || isConnected())
			{
				if(_connectionAttempt != null)
				{
					if(_connectionAttempt.getStatus() != AsyncTask.Status.FINISHED)
					{
						_connectionAttempt.cancel(true);
					}
				}

				if(_connectionManager != null)
				{
					if(_connectionManager.isAlive())
					{
						_connectionManager.kill();
					}
				}

				_handler.obtainMessage(MSG_CONNECTION_STOPPED).sendToTarget();
			}
			else
			{
				throw new Exception("Nothing to disconnect from.");
			}
		}
		catch(Exception e)
		{
			throw e;
		}
		finally
		{
			try
			{
				if(_socket != null)
				{
					synchronized(_socket)
					{
						_socket.close();
                        _socket = null;
					}
				}
			}
			catch(IOException ioe)
			{
				Log.e(LOG_TAG, "Failed to close socket. " + ioe.getMessage());
				throw ioe;
			}
		}
	}


	/**
	 * Starts a thread which manages the connected socket.
	 *
	 * @throws Exception If there is an error starting the managed connection.
	 *
	 * @see ConnectionManager
	 */
	public void startConnectionManager() throws Exception
	{
		try
		{
			if(_socket == null)
			{
				throw new Exception("There is no socket.");
			}
			else
			{
				_connectionManager = new ConnectionManager(_socket);
				_connectionManager.start();
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Stops the thread managing a connected socket.
	 *
	 * @throws Exception If there is a problem stopping the thread (it doesn't exist).
	 */
	public void stopConnectionManager() throws Exception
	{
		try
		{
			if(_connectionManager != null)
			{
				if(_connectionManager.isAlive())
				{
					_connectionManager.kill();
				}
				else
				{
					throw new Exception("There is no active ConnectionManager to stop.");
				}
			}
			else
			{
				throw new Exception("There is no ConnectionManager to stop.");
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Writes data to the managed connection.
	 *
	 * @param data The data you want to write. Will be converted into a byte array (byte[]).
	 * @throws Exception If there is an error writing the data.
	 */
	public void write(byte[] bytes) throws Exception
	{
		try
		{
			if(_connectionManager == null)
			{
				throw new Exception("There is no managed connection to write to.");
			}
			else if(!_connectionManager.isAlive())
			{
				throw new Exception("There is no active managed connection to write to.");
			}
			else
			{
				_connectionManager.write(bytes);
			}
		}
		catch(Exception e)
		{
			throw e;
		}
	}


	/**
	 * Receiver registered for various Bluetooth based events.
	 */
	private final BroadcastReceiver _receiver = new BroadcastReceiver()
	{
		@Override
		public void onReceive(Context ctx, Intent intent)
		{
			String action = intent.getAction();

			if(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED.equals(action))
			{
				int connState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
				if(connState == BluetoothAdapter.STATE_TURNING_OFF || connState == BluetoothAdapter.STATE_OFF)
				{
					_handler.obtainMessage(MSG_BLUETOOTH_LOST).sendToTarget();
				}
			}
			else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action))
			{
				int bondState 			= intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 0);
				BluetoothDevice device 	= intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

				String name 	= device.getName();
				String address 	= device.getAddress();
				Bundle bundle = new Bundle();
				bundle.putString(DATA_DEVICE_NAME, name);
				bundle.putString(DATA_DEVICE_ADDRESS, address);
				if(bondState == BluetoothDevice.BOND_BONDED)
				{
					bundle.putString(DATA_DEVICE_BOND_STATE,"BONDED");
				} 
				else if(bondState == BluetoothDevice.BOND_BONDING)
				{
					bundle.putString(DATA_DEVICE_BOND_STATE,"BONDING");
				}
				else 
				{
					bundle.putString(DATA_DEVICE_BOND_STATE,"NONE");
				}
				Message msg = _handler.obtainMessage(MSG_DEVICE_BONDED);
				msg.setData(bundle);
				msg.sendToTarget();
			}
			else if(BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action))
			{
				_handler.obtainMessage(MSG_DISCOVERY_STARTED).sendToTarget();
			}
			else if(BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
			{
				_handler.obtainMessage(MSG_DISCOVERY_FINISHED).sendToTarget();
			}
			else if(BluetoothDevice.ACTION_FOUND.equals(action))
			{
				try
				{
					BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

					Bundle bundle = new Bundle();
					bundle.putString(DATA_DEVICE_NAME, device.getName());
					bundle.putString(DATA_DEVICE_ADDRESS, device.getAddress());

					Message msg = _handler.obtainMessage(MSG_DEVICE_FOUND);
					msg.setData(bundle);
					msg.sendToTarget();
				}
				catch(Exception e)
				{
					Log.e(LOG_TAG, "Exception" + e.getMessage());
				}
			}
			else if(BluetoothDevice.ACTION_UUID.equals(action))
			{
				BluetoothDevice device 			= intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
				Parcelable[] uuids 				= intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
				ArrayList<String> uuidStrings 	= new ArrayList<String>();

				if(uuids != null)
				{
					for(Parcelable uuid : uuids)
					{
						uuidStrings.add(uuid.toString());
					}
				}

				Bundle bundle = new Bundle();
				bundle.putString(DATA_DEVICE_NAME, device.getName());
				bundle.putString(DATA_DEVICE_ADDRESS, device.getAddress());
				bundle.putStringArrayList(DATA_UUIDS, uuidStrings);

				Message msg = _handler.obtainMessage(MSG_UUIDS_FOUND);
				msg.setData(bundle);
				msg.sendToTarget();
			}
		}
	};

	/**
	 * Attempts a connection at the specified device. Sets the private field <b>_socket</b>
	 * for BluetoothWrapper on successful connection attempt.
	 *
	 * @see AsyncTask
	 * @see BluetoothWrapper
	 * @see BluetoothSocket
	 */
	private class ConnectionAttempt extends AsyncTask<Void, Void, BluetoothSocket>
	{
		private static final String LOG_TAG = "[BluetoothService]ConnectTask";

		private final UUID 				_uuid;
		private final BluetoothSocket 	_socket;

		private String _error;

		/**
		 * Constructor for ConnectionAttempt: creates a socket for the given device and other parameters.
		 *
		 * @param device Target of this connection attempt
		 * @param uuid UUID for creating the socket
		 * @param connType Type of connection, eg. secure or insecure
		 * @throws Exception If there is a problem creating the socket with given parameters.
		 */
		public ConnectionAttempt(BluetoothDevice device, UUID uuid, EConnectionType connType) throws Exception
		{
			UUID 			tmpUuid 	= null;
			BluetoothSocket tmpSocket 	= null;

			try
			{
				if(uuid == null)
				{
					throw new Exception("No UUID given for ConnectionAttempt.");
				}
				else
				{
					tmpUuid = uuid;
				}
			}
			catch(Exception e)
			{
				throw e;
			}

			_uuid = tmpUuid;

			try
			{
				switch(connType)
				{
				case Secure:
					tmpSocket = device.createRfcommSocketToServiceRecord(_uuid);
					break;

				case Insecure:
					tmpSocket = device.createInsecureRfcommSocketToServiceRecord(_uuid);
					break;

				case Hax:
					Method createSocket = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class});
			        tmpSocket = (BluetoothSocket)createSocket.invoke(device, Integer.valueOf(1));
					break;
				}
			}
			catch(Exception e)
			{
				throw e;
			}
			finally
			{
				this._socket = tmpSocket;
			}
		}

		@Override
		protected BluetoothSocket doInBackground(Void... params)
		{
			if(this._socket == null)
			{
				this._error = "Socket not created correctly.";
				return null;
			}

			try
			{
				_socket.connect();
				return _socket;
			}
			catch(IOException eConnect)
			{
				this._error = eConnect.getMessage();

				try
				{
					_socket.close();
				}
				catch(IOException eClose)
				{
					this._error += " " + eClose.getMessage();
					Log.e(LOG_TAG, "Failed to close socket. " + eClose.getMessage());
				}
				return null;
			}
		}

		@Override
		protected void onPostExecute(BluetoothSocket resultingSocket)
		{
			if(resultingSocket == null)
			{
				Bundle bundle = new Bundle();
				bundle.putString(DATA_ERROR, this._error);

				Message msg = _handler.obtainMessage(MSG_CONNECTION_FAILED);
				msg.setData(bundle);
				msg.sendToTarget();
			}
			else
			{
				if(BluetoothWrapper.this._socket != null)
				{
					synchronized(BluetoothWrapper.this._socket)
					{
						BluetoothWrapper.this._socket = resultingSocket;
					}
				}
				else
				{
					BluetoothWrapper.this._socket = resultingSocket;
				}

				_handler.obtainMessage(MSG_CONNECTION_ESTABLISHED).sendToTarget();
			}
		}
	}

	/**
	 * Manages an active connection, allowing read and write operations.
	 */
	private class ConnectionManager extends Thread
	{
		private static final String LOG_TAG		= "[BluetoothWrapper]ConnectionManager";
		private static final int BUFFER_SIZE 	= 1024;

		private final BluetoothSocket 	_socket;
		private final InputStream 		_input;
		private final OutputStream 		_output;

		private volatile boolean _isAlive;

		/**
		 * Constructor for ConnectionManager, retrieves input and output streams from given socket.
		 *
		 * @param socket A connected socket.
		 * @throws IOException If there is an error retrieving streams from the socket.
		 */
		public ConnectionManager(BluetoothSocket socket) throws IOException
		{
			_socket				= socket;
			InputStream input	= null;
			OutputStream output = null;

			try
			{
				input 	= _socket.getInputStream();
				output 	= _socket.getOutputStream();
			}
			catch(IOException e)
			{
				throw e;
			}

			_input 	= input;
			_output = output;

			_isAlive = true;
		}

		@Override
		public void run()
		{
			int bytes;
			byte[] buffer = new byte[BUFFER_SIZE];

			while(_isAlive)
			{
				try
				{
					bytes 		= _input.read(buffer);
					byte[] data = new byte[bytes];

					for(int i = 0; i < bytes; i++)
					{
						data[i] = buffer[i];
					}

					Bundle bundle = new Bundle();
					bundle.putByteArray(DATA_BYTES, data);

					Message msg = _handler.obtainMessage(MSG_READ);
					msg.setData(bundle);
					msg.sendToTarget();
				}
				catch(Exception e)
				{
					try
					{
						if(BluetoothWrapper.this._socket != null)
						{
							synchronized(BluetoothWrapper.this._socket)
							{
								BluetoothWrapper.this._socket.close();
								BluetoothWrapper.this._socket = null;
							}
						}
					}
					catch(Exception ioe)
					{
						Log.e(LOG_TAG, "Failed to close socket after connection error." + ioe.getMessage());
					}

					Bundle bundle = new Bundle();
					bundle.putString(DATA_ERROR, "Error reading InputStream. " + e.getMessage());

					Message msg = _handler.obtainMessage(MSG_CONNECTION_LOST);
					msg.setData(bundle);
					msg.sendToTarget();

					break;
				}
			}
		}

		/**
		 * Write given data to the output stream.
		 *
		 * @param bytes The data you wish to transmit to the output stream.
		 * @throws IOException If there is an error writing to the output stream.
		 */
		public void write(byte[] bytes) throws IOException
		{
			try
			{
				_output.write(bytes);
			}
			catch(IOException e)
			{
				throw e;
			}
		}

		/**
		 * Flags the thread so that it will not continue execution, essentially killing it.
		 */
		public void kill()
		{
			_isAlive = false;
		}
	}
}