/*****************************************************************************
 * Copyright (c) 2014 Laird Technologies. All Rights Reserved.
 *
 * The information contained herein is property of Laird Technologies.
 * Licensees are granted free, non-transferable use of the information. NO WARRANTY of ANY KIND is provided. 
 * This heading must NOT be removed from the file.
 ******************************************************************************/

package com.eddbc;

import android.app.Activity;
import android.bluetooth.*;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import com.lairdtech.bt.ble.DefinedBleUUIDs;
import com.lairdtech.misc.FifoByteQueue;
import com.megster.cordova.ble.central.Peripheral;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.PluginResult;

import java.util.*;

//import org.apache.commons.lang3.StringEscapeUtils;

/**
 * Responsible for the communication between the android device and a module
 * that has the Virtual Serial Port (VSP) service
 *
 * <br>
 * Include's the error codes that the module can send in case of an error and
 * the UUIDs of the service and of the characteristics
 *
 */
public class VSPBytePeripheral extends Peripheral
{
	private static final String TAG = "VirtualSerialPortDevice";

	public final static UUID VSP_SERVICE = UUID
			.fromString("569a1101-b87f-490c-92cb-11ba5ea5167c");
	/**
	 * for receiving data from the remote device
	 */
	public final static UUID VSP_CHAR_TX = UUID
			.fromString("569a2000-b87f-490c-92cb-11ba5ea5167c");
	/**
	 * doe sending data to the remote device
	 */
	public final static UUID VSP_CHAR_RX = UUID
			.fromString("569a2001-b87f-490c-92cb-11ba5ea5167c");
	public final static UUID VSP_CHAR_MODEM_OUT = UUID
			.fromString("569a2002-b87f-490c-92cb-11ba5ea5167c");
	public final static UUID VSP_CHAR_MODEM_IN = UUID
			.fromString("569a2003-b87f-490c-92cb-11ba5ea5167c");

	/*
	 * module VSP responses when in command mode
	 */
	public final static String SUCCESS_CODE = "00";
	public final static String ERROR_CODE_NO_FILE_TO_CLOSE = "E037";
	public final static String ERROR_CODE_MEMORY_FULL = "5002";
	public final static String ERROR_CODE_FSA_FAIL_OPENFILE = "1809";
	public final static String ERROR_CODE_FSA_FILENAME_TOO_LONG = "1803";
	public final static String ERROR_CODE_FILE_NOT_OPEN = "E038";
	public final static String ERROR_CODE_INCORRECT_MODE = "E00E";
	public final static String ERROR_CODE_UNKNOWN_COMMAND = "E007";
	public final static String ERROR_CODE_UNKNOWN_SUBCOMMAND = "E00D";
	public final static String ERROR_CODE_UNEXPECTED_PARM = "E002";

	private boolean mIsValidVspDevice = false;
	protected FifoAndVspManagerState mFifoAndVspManagerState;

	/**
	 * module char that retrieves data. This means we use it to send data from
	 * the android device to the remote module. We write the char to the remote
	 * device to send data
	 */
	private BluetoothGattCharacteristic mCharRx;
	/**
	 * module char that sends data. This means we use it to receive data through
	 * notifications from the remote module
	 */
	private BluetoothGattCharacteristic mCharTx;
	/**
	 * module: i can send some data to the other device now Note: This char is
	 * found if the BLE device is in bridge mode, otherwise if the module is not
	 * in bridge mode it will need to be enabled. This characteristic is
	 * currently not used as android has a big buffer limit
	 */
	private BluetoothGattCharacteristic mCharModemIn;
	/**
	 * module: am ready to retrieve some data - when value is 1 it means that
	 * the module buffer is not full and it can retrieve more data - when the
	 * value is 0 it means that the module buffer is full and therefore if we
	 * send it any more data they will get lost Note: This char is found if the
	 * BLE device is in bridge mode, otherwise if the module is not in bridge
	 * mode it will need to be enabled
	 */
	private BluetoothGattCharacteristic mCharModemOut;
	/**
	 * BLE module buffer full or not?
	 */
	private boolean mIsBufferSpaceAvailableNewState = true;

	/*
	 * counters for the total data send and received
	 */
	private int mRxCounter;
	private int mTxCounter;

	/**
	 * this should be no more than 20 as the Laird module can only receive a
	 * total of 20 bytes on every sent
	 */
	protected static int MAX_DATA_TO_READ_FROM_BUFFER = 15;
	/**
	 * Sending data to module
	 */
	protected FifoByteQueue mTxBuffer;
	/**
	 * Receiving data from module
	 */
	protected FifoByteQueue mRxBuffer;
	protected Handler sendDataHandler = new Handler();
	protected int SEND_DATA_TO_REMOTE_DEVICE_DELAY = 10;
	/**
	 * this is used to get the previously read data from the TX buffer and store
	 * it temporary into this variable
	 */
	protected ByteArrayBuilder mTxDest = new ByteArrayBuilder();
	/**
	 * this is used to get the previously read data from the RX buffer and store
	 * it temporary into this variable
	 */
	protected ByteArrayBuilder mRxDest = new ByteArrayBuilder();

	private Activity mActivity;
	private BluetoothManager mBluetoothManager;
	protected Queue<BluetoothGattCharacteristic> mCharQueue = new LinkedList<BluetoothGattCharacteristic>();
	private CallbackContext callback;

	public VSPBytePeripheral(BluetoothDevice device, Activity activity){
		super(device);
		mActivity = activity;
		mRxBuffer = new FifoByteQueue();
		mTxBuffer = new FifoByteQueue();
		mFifoAndVspManagerState = FifoAndVspManagerState.WAITING;
	}

	public VSPBytePeripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, Activity activity) {
		super(device, advertisingRSSI, scanRecord);
		mActivity = activity;
		mRxBuffer = new FifoByteQueue();
		mTxBuffer = new FifoByteQueue();
		mFifoAndVspManagerState = FifoAndVspManagerState.WAITING;
	}

	public void connectSilent(Activity activity, boolean auto) {
		currentActivity = activity;
		autoconnect = auto;

		gattConnect();

		PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
		result.setKeepCallback(true);
	}

//	public VirtualSerialPortDevice(Activity activity,
//								   IBleUiCallback iBleUiCallback)
//	{
//		super(activity, iBleUiCallback);
//
//		mRxBuffer = new FifoByteQueue();
//		mTxBuffer = new FifoByteQueue();
//
//		mFifoAndVspManagerState = FifoAndVspManagerState.WAITING;
//	}

	public FifoAndVspManagerState getFifoAndVspManagerState()
	{
		return mFifoAndVspManagerState;
	}

	public BluetoothGattCharacteristic getCharRx()
	{
		return mCharRx;
	}

	public BluetoothGattCharacteristic getCharTx()
	{
		return mCharTx;
	}

	public BluetoothGattCharacteristic getCharModemIn()
	{
		return mCharModemIn;
	}

	public BluetoothGattCharacteristic getCharModemOut()
	{
		return mCharModemOut;
	}

	public boolean isBufferSpaceAvailable()
	{
		return mIsBufferSpaceAvailableNewState;
	}

	public int getRxCounter()
	{
		return mRxCounter;
	}

	public int getTxCounter()
	{
		return mTxCounter;
	}

	public FifoByteQueue getRxBuffer()
	{
		return mRxBuffer;
	}

	public FifoByteQueue getTxBuffer()
	{
		return mTxBuffer;
	}

	public boolean isValidVspDevice()
	{
		return mIsValidVspDevice;
	}

	public void clearRxCounter()
	{
		mRxCounter = 0;
	}

	public void clearTxCounter()
	{
		mTxCounter = 0;
	}

	public void clearRxAndTxCounter()
	{
		mRxCounter = 0;
		mTxCounter = 0;
	}

	public void setCallback(CallbackContext callback) {
		this.callback = callback;
	}

	public CallbackContext getCallback() {
		return callback;
	}

	public enum FifoAndVspManagerState
	{
		WAITING, READY_TO_SEND_DATA, UPLOADING, UPLOADED, STOPPED, FAILED
	}

	/**
	 * Send data to remote device
	 *
	 * @param dataToBeSend
	 *			the data to send to the remote device
	 */
	public void startDataTransfer(CallbackContext callbackContext, byte[] dataToBeSend)
	{
		Log.i(TAG, "Preparing to send data: "+dataToBeSend.toString());

		mFifoAndVspManagerState = FifoAndVspManagerState.UPLOADING;
		boolean success = writeToFifoAndUploadDataToRemoteDevice(dataToBeSend);
		if(success){
			callbackContext.success();
		} else {
			callbackContext.error("something went wrong ¯\\_(ツ)_/¯");
		}
	}

	/**
	 * Get the current connection state of the profile to the remote device.
	 *
	 * <p>
	 * Requires {@link android.Manifest.permission#BLUETOOTH} permission.
	 *
	 * @return State of the profile connection. One of
	 *		 {@link BluetoothProfile#STATE_CONNECTED},
	 *		 {@link BluetoothProfile#STATE_CONNECTING},
	 *		 {@link BluetoothProfile#STATE_DISCONNECTED},
	 *		 {@link BluetoothProfile#STATE_DISCONNECTING}
	 */
	public int getConnectionState()
	{
		if(mBluetoothManager == null){
			mBluetoothManager = (BluetoothManager) mActivity
					.getSystemService(Context.BLUETOOTH_SERVICE);
		}
		return mBluetoothManager.getConnectionState(device,
				BluetoothProfile.GATT);
	}

	/**
	 * reads from the RX buffer content based on the
	 * MAX_DATA_TO_READ_FROM_BUFFER and sends it to the remote device
	 */
	protected boolean uploadNextDataFromFifoToRemoteDevice()
	{
		if (gatt == null
				|| getConnectionState() == BluetoothProfile.STATE_DISCONNECTED)
		{
			return false;
		}

		if (mTxBuffer.read(mTxDest, MAX_DATA_TO_READ_FROM_BUFFER) != 0)
		{
			byte[] dataToWriteToRemoteBleDevice = mTxDest.toArray();
			mTxDest.delete(0, MAX_DATA_TO_READ_FROM_BUFFER);

			return sendToModule(dataToWriteToRemoteBleDevice);
		}
		else
		{
			onUploaded();
		}
		return true;
	}

	/**
	 * writes the string data passed to the TX buffer and then sends the data to
	 * the remote device
	 *
	 * @param data
	 *			the data to write to the TX buffer and to send to the remote
	 *			device
	 */
	protected boolean writeToFifoAndUploadDataToRemoteDevice(byte[] data)
	{
		mTxBuffer.write(data);
		return uploadNextDataFromFifoToRemoteDevice();
	};

	/**
	 *
	 * @param dataToBeSend string of data to send
	 * @return true, if the write operation was initiated successfully
	 */
	protected boolean sendToModule(byte[] dataToBeSend)
	{
		if (gatt != null && mCharRx != null && dataToBeSend != null)
		{

			Log.i(TAG, "char data Just send 1: "+ Arrays.toString(dataToBeSend));
			mCharRx.setValue(dataToBeSend);

			System.out.println("char data Just send 2:"
					+ Arrays.toString(mCharRx.getValue()));

			return gatt.writeCharacteristic(mCharRx);
		}

		return false;
	}

	@Override
	public void onConnectionStateChange(BluetoothGatt gatt, int status,
										int newState)
	{
		super.onConnectionStateChange(gatt, status, newState);

		if (status == BluetoothGatt.GATT_SUCCESS)
		{
			switch (newState)
			{
				case BluetoothProfile.STATE_CONNECTING:

					break;

				case BluetoothProfile.STATE_CONNECTED:

					mFifoAndVspManagerState = FifoAndVspManagerState.WAITING;
					break;

				case BluetoothProfile.STATE_DISCONNECTING:

					break;

				case BluetoothProfile.STATE_DISCONNECTED:
					mFifoAndVspManagerState = FifoAndVspManagerState.WAITING;
					setToDefault();
					flushBuffers();
					break;
			}
		}
		else
		{
			mFifoAndVspManagerState = FifoAndVspManagerState.WAITING;
			setToDefault();
			flushBuffers();
		}

	}

	@Override
	public void onCharacteristicWrite(BluetoothGatt gatt,
									  BluetoothGattCharacteristic characteristic, int status)
	{
		super.onCharacteristicWrite(gatt, characteristic, status);

		UUID serviceUUID = characteristic.getService().getUuid();
		UUID charUUID = characteristic.getUuid();

		switch (status)
		{
			case BluetoothGatt.GATT_SUCCESS:
				if (VSP_SERVICE.equals(serviceUUID))
				{
					if (VSP_CHAR_RX.equals(charUUID))
					{
						Log.i(TAG, "Data was sent successfully");

						// keep count of total bytes send to the remote BLE device
						mTxCounter = mTxCounter + characteristic.getValue().length;
						onVspSendDataSuccess(gatt, characteristic);
					}
				}
				break;
		}
	}

	@Override
	public void onCharacteristicChanged(BluetoothGatt gatt,
										BluetoothGattCharacteristic ch)
	{
		UUID serviceUUID = ch.getService().getUuid();
		UUID charUUID = ch.getUuid();

		if (VSP_SERVICE.equals(serviceUUID))
		{
			if (VSP_CHAR_TX.equals(charUUID))
			{
				Log.i(TAG, "Data was received successfully");

				// keep count of total bytes received from the remote BLE device
				mRxCounter = mRxCounter + ch.getValue().length;
				onVspReceiveData(gatt, ch);
			}
			else if (VSP_CHAR_MODEM_OUT.equals(charUUID))
			{
				/*
				 * getting the buffer space state and then we use it to identify
				 * if there is space in the remote device or not.
				 *
				 * when the buffer old state is 0 and the buffer new state is 1
				 * then it means that there was a transition from the remote
				 * device not able to receive data to able to receive data.
				 */
				boolean isBufferSpaceAvailableOldState = mIsBufferSpaceAvailableNewState;

				int isBufferSpaceAvailableNewState = ch.getIntValue(
						BluetoothGattCharacteristic.FORMAT_UINT8, 0);

				if (isBufferSpaceAvailableNewState == 1)
				{
					mIsBufferSpaceAvailableNewState = true;
				}
				else
				{
					mIsBufferSpaceAvailableNewState = false;
				}

				Log.i(TAG, "Was the buffer full previously: "
						+ isBufferSpaceAvailableOldState);
				Log.i(TAG, "Is the buffer full now: "
						+ isBufferSpaceAvailableNewState);

				onVspIsBufferSpaceAvailable(isBufferSpaceAvailableOldState,
						mIsBufferSpaceAvailableNewState);
			}
		}
	}

	@Override
	public void onServicesDiscovered(BluetoothGatt gatt, int status) {
		super.onServicesDiscovered(gatt, status);
		findServicesAndCharacteristics();
	}

	/**
	 * Finds all the services and characteristics that the remote BLE device
	 * has. The
	 * {@link VSPBytePeripheral#onServiceFound(BluetoothGattService)} method
	 * is called whenever a service is found and the
	 * {@link VSPBytePeripheral#onCharFound(BluetoothGattCharacteristic)}
	 * method is called whenever a characteristic is found. Finally the
	 * {@link VSPBytePeripheral#onCharsFoundCompleted()}
	 */
	protected void findServicesAndCharacteristics()
	{
		if (gatt == null)
		{
			Log.w(TAG, "gatt is null");
			return;
		}

		List<BluetoothGattService> services = gatt.getServices();

		for (int i = 0; i < services.size(); i++)
		{
			onServiceFound(services.get(i));

			List<BluetoothGattCharacteristic> characteristics = services.get(i)
					.getCharacteristics();

			for (int j = 0; j < characteristics.size(); j++)
			{
				onCharFound(characteristics.get(j));
			}
		}
		onCharsFoundCompleted();
	}

	protected void onCharsFoundCompleted()
	{

		if (mIsValidVspDevice == false)
		{
			mActivity.runOnUiThread(new Runnable()
			{
				@Override
				public void run()
				{
					Toast.makeText(mActivity,
							"Device does not have the VSP service",
							Toast.LENGTH_SHORT).show();
				}
			});

			disconnect();
		}
		else
		{
			executeCharacteristicsQueue();
		}
	}

	/**
	 * executes the queued characteristics and automatically identifies if they
	 * need to be read or notified/indicated and executes that operation.
	 *
	 * The {@link VSPBytePeripheral#onCharacteristicsQueueCompleted()} method
	 * is called once all characteristics have been executed
	 */
	protected void executeCharacteristicsQueue()
	{
		// based on char properties @
		// https://msdn.microsoft.com/en-us/library/windows.devices.bluetooth.genericattributeprofile.gattcharacteristicproperties.aspx

		if (mCharQueue.size() >= 1)
		{
			Log.i(TAG, "executeCharacteristicsQueue: "
					+ mCharQueue.element().getUuid());

			// if char is readable, read char
			if ((mCharQueue.element().getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == BluetoothGattCharacteristic.PROPERTY_READ)
			{
				gatt.readCharacteristic(mCharQueue.element());
			}
			// if char is notify || indicate, enable
			else if ((mCharQueue.element().getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY
					|| (mCharQueue.element().getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE)
			{
				setCharacteristicNotificationOrIndication(mCharQueue.element(),
						true);
			}
		}
		else
		{
			onCharacteristicsQueueCompleted();
		}
	}

	/*
	 * <p> <b>Note:</b> if the characteristic supports Notifications and
	 * Indications then automatically only Notifications will be enabled and
	 * Indications will be ignored. <br>If we need to define what to enable then
	 * one way would be to create separate methods called
	 * "setCharacteristicNotification" and "setCharacteristicIndication"
	 */
	protected boolean setCharacteristicNotificationOrIndication(
			BluetoothGattCharacteristic characteristic, boolean enable)
	{
		if (gatt == null)
			throw new NullPointerException("gatt object is null!");

		boolean isSettingNotificationLocalySuccess = gatt
				.setCharacteristicNotification(characteristic, enable);
		if (isSettingNotificationLocalySuccess)
		{
			return writeDescriptorForNotificationOrIndication(characteristic,
					enable);
		}
		else
		{
			return false;
		}
	}

	/**
	 * write the Client Characteristic Configuration Descriptor (CCCD) for
	 * enabling/disabling notifications/indications to a specific
	 * characteristic.
	 *
	 * @param ch
	 *			the characteristic to write the CCCD descriptor to
	 * @param enabled
	 *			false to disable notifications/indications, true to enable
	 *			them
	 * @return boolean false if CCCD descriptor was not found or if the
	 *		 operation was not initiated successful, otherwise returns true
	 */
	private boolean writeDescriptorForNotificationOrIndication(
			BluetoothGattCharacteristic ch, boolean enabled)
	{
		// see:
		// https://developer.bluetooth.org/gatt/descriptors/Pages/DescriptorViewer.aspx?u=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml
		BluetoothGattDescriptor descriptor = ch
				.getDescriptor(DefinedBleUUIDs.Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR);
		if (descriptor == null)
			return false;

		int properties = ch.getProperties();

		if ((BluetoothGattCharacteristic.PROPERTY_NOTIFY & properties) != 0)
		{
			// set notifications, heart rate measurement etc
			byte[] val = enabled ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
					: BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
			boolean descrSuccess = descriptor.setValue(val);
			Log.i(TAG, "NOTIFY: " + descrSuccess);
		}
		else if ((BluetoothGattCharacteristic.PROPERTY_INDICATE & properties) != 0)
		{
			// set notifications, temperature measurement etc
			byte[] val = enabled ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
					: BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
			boolean descrSuccess = descriptor.setValue(val);
			Log.i(TAG, "INDICATE: " + descrSuccess);
		}

		boolean success = gatt.writeDescriptor(descriptor);

		Log.i(TAG, "writeDescriptor success: " + success);
		return success;
	}

	protected void onCharacteristicsQueueCompleted()
	{
		Log.i(TAG, "QUEUE COMPLETED");
		enableModemIn();
	}

	/**
	 * Callback that gets called whenever a service is found
	 *
	 * @param service
	 */
	protected void onServiceFound(final BluetoothGattService service)
	{
		if (VSP_SERVICE.equals(service.getUuid()))
		{
			mIsValidVspDevice = true;
		}
	}

	/**
	 * adds the given characteristic to the characteristics queue. Call the
	 * {@link VSPBytePeripheral#executeCharacteristicsQueue()} method to
	 * execute the characteristics operation. Currently only read and
	 * notification/indication operations are supported.
	 *
	 * @param characteristic
	 *			the characteristic to add to the queue
	 */
	protected void addCharToQueue(BluetoothGattCharacteristic characteristic)
	{
		mCharQueue.add(characteristic);
	}

	protected void onCharFound(final BluetoothGattCharacteristic characteristic)
	{
		if (VSP_CHAR_RX.equals(characteristic.getUuid()))
		{
			mCharRx = characteristic;
		}
		else if (VSP_CHAR_TX.equals(characteristic.getUuid()))
		{
			// add to queue as we want to enable notifications
			addCharToQueue(characteristic);
		}
		else if (VSP_CHAR_MODEM_IN.equals(characteristic.getUuid()))
		{
			mCharModemIn = characteristic;
		}
		else if (VSP_CHAR_MODEM_OUT.equals(characteristic.getUuid()))
		{
			// add to queue as we want to enable notifications
			addCharToQueue(characteristic);
		}
	}

	/**
	 * for notifying the remote device that we can't send anymore data
	 */
	public void enableModemIn()
	{
		if (mCharModemIn != null)
		{
			byte[] enable =
					{
							(byte) 1
					};
			mCharModemIn.setValue(enable);
			gatt.writeCharacteristic(mCharModemIn);
		}
	}

	/**
	 * Clears all values
	 */
	private void setToDefault()
	{
		mIsValidVspDevice = false;
		mCharTx = null;
		mCharRx = null;
		mCharModemOut = null;
		mCharModemIn = null;
	}

	/**
	 * clears the RX buffer and the TX buffer
	 */
	protected void flushBuffers()
	{
		mRxBuffer.flush();
		mTxBuffer.flush();
	}

	/**
	 * override this method to define what data to sent to the remote BLE device
	 */
	protected void uploadNextData()
	{}

	protected void onUploaded()
	{
		mFifoAndVspManagerState = FifoAndVspManagerState.UPLOADED;
		flushBuffers();
	}

	/**
	 * used when sending data fails because of a response error from the remote
	 * device and not because of a disconnection issue. For example if the
	 * memory of the module has become full it will give a response error that
	 * it cannot store any more data
	 */
	protected void onUploadFailed(final String errorCode)
	{
		mFifoAndVspManagerState = FifoAndVspManagerState.FAILED;
		flushBuffers();
	}

	/**
	 * callback for notifying if the VSP service was found
	 *
	 * @param found
	 *			true if VSP service was found, otherwise false
	 */
	protected void onVspServiceFound(final boolean found)
	{}

	/**
	 * Callback for whenever data was send to the remote device successful
	 *
	 * @param gatt
	 *			GATT client
	 * @param ch
	 *			the RX characteristic with the updated value
	 */
	public void onVspSendDataSuccess(final BluetoothGatt gatt,
									 final BluetoothGattCharacteristic ch)
	{
		sendDataHandler.postDelayed(new Runnable()
		{
			@Override
			public void run()
			{
				switch (mFifoAndVspManagerState)
				{
					case UPLOADING:
						/*
						 * what to do after the data was send successfully
						 */
						if (isBufferSpaceAvailable() == true)
						{
							uploadNextData();
						}
						break;

					default:
						break;

				}
			}
		}, SEND_DATA_TO_REMOTE_DEVICE_DELAY);
	}

	/**
	 * Callback for whenever data failed to be send to the remote device
	 *
	 * @param gatt GATT client
	 * @param ch the RX characteristic with the updated value
	 * @param status error of the failure
	 */
	public void onVspSendDataFailure(final BluetoothGatt gatt,
									 final BluetoothGattCharacteristic ch, final int status)
	{}

	/**
	 * Callback for when data is received from the remote device
	 *
	 * @param gatt GATT client
	 * @param ch the TX characteristic with the updated value
	 */
	public void onVspReceiveData(final BluetoothGatt gatt,
								 final BluetoothGattCharacteristic ch)
	{
		mRxBuffer.write(ch.getValue());

		while (mRxBuffer.read(mRxDest) != 0)
		{
			/*
			 * found data
			 */
			byte[] rxBufferDataRead = mRxDest.toArray();
			mRxDest.delete(0, mRxDest.length());
//			mSerialManagerUiCallback.onUiReceiveData(rxBufferDataRead);

			Log.i(TAG, "Message Recieved: "+ Arrays.toString(rxBufferDataRead));
			PluginResult result = new PluginResult(PluginResult.Status.OK, rxBufferDataRead);
			result.setKeepCallback(true);
			callback.sendPluginResult(result);
		}
	}

	/**
	 * Callback that notifies the android device if the remote device can
	 * currently accept data or not
	 *
	 * if the new state is false it means it cannot receive any more data as its
	 * buffer is full, when true it can receive data
	 *
	 * @param isBufferSpaceAvailableOldState
	 *			the old state of the buffer
	 * @param isBufferSpaceAvailableNewState
	 *			the new state of the buffer
	 */
	public void onVspIsBufferSpaceAvailable(
			final boolean isBufferSpaceAvailableOldState,
			final boolean isBufferSpaceAvailableNewState)
	{

		switch (mFifoAndVspManagerState)
		{
			case UPLOADING:
				/*
				 * callback for what to do when data was send successfully from the
				 * android device and when the module buffer was full and now it has
				 * been cleared, which means it now has available space
				 */
				if (isBufferSpaceAvailableOldState == false
						&& isBufferSpaceAvailableNewState == true)
				{
					uploadNextData();
				}
				break;

			default:
				break;
		}
	}

}