package ran.quzitech.rnble.ble;

import android.app.Activity;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.support.annotation.Nullable;
import android.util.Base64;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.RCTNativeAppEventEmitter;


import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;


import ran.quzitech.rnble.MsgCode;
import ran.quzitech.rnble.handler.IProtocalHandler;
import ran.quzitech.rnble.handler.ProtocolMsg;
import ran.quzitech.rnble.utils.ByteUtil;


/**
 * Peripheral wraps the BluetoothDevice and provides methods to convert to JSON.
 */
public class Peripheral extends BluetoothGattCallback {

    private static final String CHARACTERISTIC_NOTIFICATION_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
    public static final String LOG_TAG = "rocks_peripheral";

    private BluetoothDevice device;
    private byte[] advertisingData;
    private String user_id;
    private int advertisingRSSI;
    private boolean connected = false;
    private ReactContext reactContext;

    private BluetoothGatt gatt;

    private Callback connectCallback;
    private Callback readCallback;
    private Callback writeCallback;

    private List<byte[]> writeQueue = new ArrayList<>();
    private Map<Integer, Callback> handlerMap = new HashMap<Integer, Callback>();
    //BabyBathProtoV03.BabyBathMessage babyBathMessage = null;
    private String moreThan20String = "";
    private final String USER_ID = "user_id";
    private IProtocalHandler handler = null;

    public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, String user_id, ReactContext reactContext) {
        this.user_id = user_id;
        this.device = device;
        this.advertisingRSSI = advertisingRSSI;
        this.advertisingData = scanRecord;
        this.reactContext = reactContext;

    }

    public Peripheral(BluetoothDevice device, ReactContext reactContext) {
        this.device = device;
        this.reactContext = reactContext;
    }

    private void sendEvent(String eventName, @Nullable WritableMap params) {
        reactContext
                .getJSModule(RCTNativeAppEventEmitter.class)
                .emit(eventName, params);
    }

    private void sendConnectionEvent(BluetoothDevice device, String eventName) {
        WritableMap map = Arguments.createMap();
        map.putString("peripheral", device.getAddress());
        sendEvent(eventName, map);
        Log.d(LOG_TAG, "Peripheral event (eventName):" + device.getAddress());
    }

    public void connect(Callback callback, Activity activity) {
        if (!connected) {
            BluetoothDevice device = getDevice();
            this.connectCallback = callback;
            gatt = device.connectGatt(activity, false, this);
        } else {
            if (gatt != null) {
                WritableMap map = this.asWritableMap(gatt);
                callback.invoke(null, map);
            } else
                callback.invoke(MsgCode.getErrorMap(MsgCode.BluetoothGatt_Is_Null));
        }
    }

    public void disconnect() {
        connectCallback = null;
        connected = false;
        if (gatt != null) {
            try {
                gatt.disconnect();
                gatt.close();
                gatt = null;
                Log.d(LOG_TAG, "Disconnect");
                sendConnectionEvent(device, "BleManagerDisconnectPeripheral");
            } catch (Exception e) {
                sendConnectionEvent(device, "BleManagerDisconnectPeripheral");
                Log.d(LOG_TAG, "Error on disconnect", e);
            }
        } else
            Log.d(LOG_TAG, "GATT is null");
    }

    public JSONObject asJSONObject() {

        JSONObject json = new JSONObject();

        try {
            json.put("name", device.getName());
            json.put("id", device.getAddress()); // mac address
            json.put("advertising", byteArrayToJSON(advertisingData));
            // TODO real RSSI if we have it, else
            json.put("rssi", advertisingRSSI);
            json.put(USER_ID, this.user_id);
        } catch (JSONException e) { // this shouldn't happen
            e.printStackTrace();
        }

        return json;
    }


    public WritableMap asWritableMap() {

        WritableMap map = Arguments.createMap();

        try {
            map.putString("name", device.getName());
            map.putString("id", device.getAddress()); // mac address
            map.putMap("advertising", byteArrayToWritableMap(advertisingData));
            map.putInt("rssi", advertisingRSSI);
            map.putString(USER_ID, this.user_id);
        } catch (Exception e) { // this shouldn't happen
            e.printStackTrace();
        }

        return map;
    }

    public WritableMap asWritableMap(BluetoothGatt gatt) {

        WritableMap map = asWritableMap();

        WritableArray servicesArray = Arguments.createArray();
        WritableArray characteristicsArray = Arguments.createArray();

        if (connected && gatt != null) {
            for (BluetoothGattService service : gatt.getServices()) {
                WritableMap serviceMap = Arguments.createMap();
                serviceMap.putString("uuid", UUIDHelper.uuidToString(service.getUuid()));


                for (BluetoothGattCharacteristic characteristic : service.getCharacteristics()) {
                    WritableMap characteristicsMap = Arguments.createMap();

                    characteristicsMap.putString("service", UUIDHelper.uuidToString(service.getUuid()));
                    characteristicsMap.putString("characteristic", UUIDHelper.uuidToString(characteristic.getUuid()));

                    characteristicsMap.putMap("properties", Helper.decodeProperties(characteristic));

                    if (characteristic.getPermissions() > 0) {
                        characteristicsMap.putMap("permissions", Helper.decodePermissions(characteristic));
                    }


                    WritableArray descriptorsArray = Arguments.createArray();

                    for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
                        WritableMap descriptorMap = Arguments.createMap();
                        descriptorMap.putString("uuid", UUIDHelper.uuidToString(descriptor.getUuid()));
                        if (descriptor.getValue() != null)
                            descriptorMap.putString("value", Base64.encodeToString(descriptor.getValue(), Base64.NO_WRAP));
                        else
                            descriptorMap.putString("value", null);

                        if (descriptor.getPermissions() > 0) {
                            descriptorMap.putMap("permissions", Helper.decodePermissions(descriptor));
                        }
                        descriptorsArray.pushMap(descriptorMap);
                    }
                    if (descriptorsArray.size() > 0) {
                        characteristicsMap.putArray("descriptors", descriptorsArray);
                    }
                    characteristicsArray.pushMap(characteristicsMap);
                }
                servicesArray.pushMap(serviceMap);
            }
            map.putArray("services", servicesArray);
            map.putArray("characteristics", characteristicsArray);
        }

        return map;
    }

    static JSONObject byteArrayToJSON(byte[] bytes) throws JSONException {
        JSONObject object = new JSONObject();
        object.put("CDVType", "ArrayBuffer");
        object.put("data", Base64.encodeToString(bytes, Base64.NO_WRAP));
        return object;
    }

    static WritableMap byteArrayToWritableMap(byte[] bytes) throws JSONException {
        WritableMap object = Arguments.createMap();
        object.putString("CDVType", "ArrayBuffer");
        object.putString("data", Base64.encodeToString(bytes, Base64.NO_WRAP));
        return object;
    }

    public boolean isConnected() {
        return connected;
    }

    public BluetoothDevice getDevice() {
        return device;
    }

    public Boolean hasService(UUID uuid) {
        if (gatt == null) {
            return null;
        }
        return gatt.getService(uuid) != null;
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        WritableMap map = this.asWritableMap(gatt);
        connectCallback.invoke(null, map);
        connectCallback = null;
    }

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

        Log.d(LOG_TAG, "onConnectionStateChange from " + status + " to " + newState + " on peripheral:" + device.getAddress());

        this.gatt = gatt;

        if (newState == BluetoothGatt.STATE_CONNECTED) {

            connected = true;
            gatt.discoverServices();

            sendConnectionEvent(device, "BleManagerConnectPeripheral");

        } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {

            if (connected) {
                connected = false;

                if (gatt != null) {
                    gatt.disconnect();
                    gatt.close();
                    this.gatt = null;
                }
            }

            sendConnectionEvent(device, "BleManagerDisconnectPeripheral");

            if (connectCallback != null) {

                connectCallback.invoke(MsgCode.getErrorMap(MsgCode.Connection_Fail));
                connectCallback = null;
            }

        }

    }

    public void updateRssi(int rssi) {
        advertisingRSSI = rssi;
    }

    public int unsignedToBytes(byte b) {
        return b & 0xFF;
    }

    public void setProtocolHandler(IProtocalHandler handler) {
        this.handler = handler;
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
        Log.d(LOG_TAG, "onCharacteristicChanged= " + characteristic);
        byte[] dataValue = characteristic.getValue();
        Log.v(LOG_TAG, "onCharacteristicChanged= " + ByteUtil.byte2hex(dataValue).replace(" ", ""));

        if (moreThan20String.equals("")) {
            moreThan20String += ByteUtil.byte2hex(dataValue).replace(" ", "");
        } else {
            moreThan20String += ByteUtil.byte2hex(dataValue).replace(" ", "").substring(2);
        }

        if (!ByteUtil.byte2hex(dataValue).replace(" ", "").startsWith("8")) {
            //这不是最后一条信息了
            Log.v(LOG_TAG, "i am not start with a= " + ByteUtil.byte2hex(dataValue).replace(" ", ""));
            WritableMap mapMessage1 = Arguments.createMap();
            mapMessage1.putString("peripheral", device.getAddress());
            mapMessage1.putString("characteristic", device.getAddress());
            mapMessage1.putString("rawstr", moreThan20String);
            sendEvent("BleManagerDidUpdateValueForCharacteristic", mapMessage1);
            if (writeCallback != null) {
                //                writeCallback.invoke(null, mapMessage1);
                writeCallback = null;
            }
            moreThan20String = "";
        }


/*

//        moreThan20String += ByteUtil.byte2hex(dataValue);
//        if (!ByteUtil.byte2hex(dataValue).startsWith("01")) {
//            if (getHandlerFromMap(dataValue) != null) {
//                WritableMap map = Arguments.createMap();
//                map.putString("peripheral", device.getAddress());
//                map.putString("characteristic", characteristic.getUuid().toString());
//                map.putString("value", ByteUtil.byte2hex(dataValue));
//
//
//                getHandlerFromMap(dataValue).invoke(null, map);
//            }
//        }
        moreThan20String += ByteUtil.byte2hex(dataValue).replace(" ", "").substring(2);
        //more than 20 bytes
        if (!ByteUtil.byte2hex(dataValue).startsWith("8")) {
            if (handler != null) {
                ProtocolMsg msg = handler.getProtocolParseResult(ByteUtil.hexStringToBytes(moreThan20String));
                Log.d(LOG_TAG, "message: " + msg.toString());

//            BabyBathProtoV03.BabyBathMessage message = DataFactory.getInstance().Parser(ByteUtil.hexStringToBytes(moreThan20String));
//            Log.d(LOG_TAG, "message: " + message.toString());
//            WritableMap valueMap = Arguments.createMap();
//            valueMap.putInt("weight", message.getWeight());


                WritableMap map = Arguments.createMap();
                map.putString("peripheral", device.getAddress());
                map.putString("characteristic", characteristic.getUuid().toString());
                map.putMap("value", msg.getWritableMap());
                map.putString("rawData", moreThan20String);
                //message id=0;
                if (ByteUtil.byte2hex(dataValue).replace(" ", "").substring(1).startsWith("0")) {
                    if (msg.getLoopCount() != 0) {
                        if (getHandlerFromMap(ByteUtil.hexStringToBytes(moreThan20String)) != null) {
                            //sendEvent("BleManagerDidUpdateValueForCharacteristic", map);
                            try {
                                getHandlerFromMap(ByteUtil.hexStringToBytes(moreThan20String)).invoke(null, map);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    } else {
                        sendEvent("BleManagerDidUpdateValueForCharacteristic", map);
                    }
                    //message id=1;
                } else if (ByteUtil.byte2hex(dataValue).replace(" ", "").substring(1).startsWith("1")) {
                    WritableMap mapMessage1 = Arguments.createMap();
                    mapMessage1.putString("peripheral", device.getAddress());
                    mapMessage1.putString("characteristic", device.getAddress());
                    mapMessage1.putString("value", "isReady");
                    sendEvent("BleManagerClubReady", mapMessage1);
                }
                Log.d(LOG_TAG, "Read: " + ByteUtil.bytesToHex(dataValue) + " from peripheral: " + device.getAddress());
                Log.d(LOG_TAG, "moreThan20String: " + moreThan20String);
                moreThan20String = "";
            }
        }

*/

    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
        Log.d(LOG_TAG, "onCharacteristicRead " + characteristic);

        if (readCallback != null) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                byte[] dataValue = characteristic.getValue();
                String value = ByteUtil.bytesToHex(dataValue);

                if (readCallback != null) {
                    readCallback.invoke(null, Integer.parseInt(value, 16));
                }
            } else {
                //Todo add the params for the errror
                //  readCallback.invoke("Error reading " + characteristic.getUuid() + " status=" + status, null);
                readCallback.invoke(MsgCode.getErrorMap(MsgCode.Reading_Fail));
            }
            readCallback = null;
        }
    }

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

        if (writeCallback != null) {

            if (writeQueue.size() > 0) {
                byte[] data = writeQueue.get(0);
                writeQueue.remove(0);
                doWrite(characteristic, data);
            } else {

                if (status == BluetoothGatt.GATT_SUCCESS) {
                    //writeCallback.invoke();
                    Log.v("onCharacteristicWrite", "GATT_SUCCESS");
                } else {
                    Log.e(LOG_TAG, "Error onCharacteristicWrite:" + status);
                    //  writeCallback.invoke("Error writing status: " + status);
                    writeCallback.invoke(MsgCode.getErrorMap(MsgCode.Writing_Fail));
                }
                Log.v("onCharacteristicWrite", "before null");
                writeCallback = null;
            }
        } else {
            Log.v(LOG_TAG, "No callback on write");
        }
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
    }

    private void setNotify(UUID serviceUUID, UUID characteristicUUID, Boolean notify, Callback callback) {
        Log.d(LOG_TAG, "setNotify");

        if (gatt == null) {
            callback.invoke(MsgCode.getErrorMap(MsgCode.BluetoothGatt_Is_Null));
            return;
        }

        BluetoothGattService service = gatt.getService(serviceUUID);
        if (service == null) {
            //    callback.invoke("BluetoothGattService is null");
            callback.invoke(MsgCode.getErrorMap(MsgCode.BluetoothGattService_Not_Found));
            return;
        }
        BluetoothGattCharacteristic characteristic = findNotifyCharacteristic(service, characteristicUUID);

        if (characteristic != null) {
            if (gatt.setCharacteristicNotification(characteristic, notify)) {

                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(CHARACTERISTIC_NOTIFICATION_CONFIG));
                if (descriptor != null) {

                    // Prefer notify over indicate
                    if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
                        Log.d(LOG_TAG, "Characteristic " + characteristicUUID + " set NOTIFY");
                        descriptor.setValue(notify ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                    } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
                        Log.d(LOG_TAG, "Characteristic " + characteristicUUID + " set INDICATE");
                        descriptor.setValue(notify ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                    } else {
                        Log.d(LOG_TAG, "Characteristic " + characteristicUUID + " does not have NOTIFY or INDICATE property set");
                    }

                    try {
                        if (gatt.writeDescriptor(descriptor)) {
                            Log.d(LOG_TAG, "setNotify complete");
                            callback.invoke();
                        } else {
                            callback.invoke(MsgCode.getErrorMap(MsgCode.SetNotify_Fail), "characteristicUUID:" + characteristicUUID);
                            //  callback.invoke("Failed to set client characteristic notification for " + characteristicUUID);
                        }
                    } catch (Exception e) {
                        Log.d(LOG_TAG, "Error on setNotify", e);
                        callback.invoke(MsgCode.getErrorMap(MsgCode.SetNotify_Fail), "characteristicUUID:" + characteristicUUID, "error:" + e.getMessage());
                        //  callback.invoke("Failed to set client characteristic notification for " + characteristicUUID + ", error: " + e.getMessage());
                    }

                } else {
                    Log.d(LOG_TAG, "Set notification failed for " + characteristicUUID);
                    callback.invoke(MsgCode.getErrorMap(MsgCode.SetNotify_Fail));
                    // callback.invoke("Set notification failed for " + characteristicUUID);
                }

            } else {
                Log.d(LOG_TAG, "Failed to register notification for " + characteristicUUID);
                callback.invoke(MsgCode.getErrorMap(MsgCode.SetNotify_Fail));
                //  callback.invoke("Failed to register notification for " + characteristicUUID);
            }

        } else {
            Log.d(LOG_TAG, "Characteristic " + characteristicUUID + " not found");
            callback.invoke(MsgCode.getErrorMap(MsgCode.Characteristic_Not_Found));
            // callback.invoke("Characteristic " + characteristicUUID + " not found");
        }

    }

    public void registerNotify(UUID serviceUUID, UUID characteristicUUID, Callback callback) {
        Log.d(LOG_TAG, "registerNotify");
        this.setNotify(serviceUUID, characteristicUUID, true, callback);
    }

    public void removeNotify(UUID serviceUUID, UUID characteristicUUID, Callback callback) {
        Log.d(LOG_TAG, "removeNotify");
        this.setNotify(serviceUUID, characteristicUUID, false, callback);
    }

    // Some devices reuse UUIDs across characteristics, so we can't use service.getCharacteristic(characteristicUUID)
    // instead check the UUID and properties for each characteristic in the service until we find the best match
    // This function prefers Notify over Indicate
    private BluetoothGattCharacteristic findNotifyCharacteristic(BluetoothGattService service, UUID characteristicUUID) {
        BluetoothGattCharacteristic characteristic = null;

        try {
            // Check for Notify first
            List<BluetoothGattCharacteristic> characteristics = null;
            if (service != null) {
                characteristics = service.getCharacteristics();
            } else {
                Log.e(LOG_TAG, "Errore su service is null");
                return null;
            }

            if (characteristics != null) {
                for (BluetoothGattCharacteristic c : characteristics) {
                    if ((c.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0 && characteristicUUID.equals(c.getUuid())) {
                        characteristic = c;
                        break;
                    }
                }
            }


            if (characteristic != null) return characteristic;

            // If there wasn't Notify Characteristic, check for Indicate
            for (BluetoothGattCharacteristic c : characteristics) {
                if ((c.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0 && characteristicUUID.equals(c.getUuid())) {
                    characteristic = c;
                    break;
                }
            }

            // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
            if (characteristic == null) {
                characteristic = service.getCharacteristic(characteristicUUID);
            }

            return characteristic;
        } catch (Exception e) {
            Log.e(LOG_TAG, "Errore su caratteristica " + characteristicUUID, e);
            return null;
        }
    }

    public void read(UUID serviceUUID, UUID characteristicUUID, Callback callback) {

        if (gatt == null) {
            callback.invoke(MsgCode.getErrorMap(MsgCode.BluetoothGatt_Is_Null));
            //  callback.invoke("BluetoothGatt is null", null);
            return;
        }

        BluetoothGattService service = gatt.getService(serviceUUID);
        BluetoothGattCharacteristic characteristic = findReadableCharacteristic(service, characteristicUUID);

        if (characteristic == null) {
            callback.invoke(MsgCode.getErrorMap(MsgCode.Characteristic_Not_Found));
        } else {
            readCallback = callback;
            if (!gatt.readCharacteristic(characteristic)) {

                callback.invoke(MsgCode.getErrorMap(MsgCode.Reading_Fail));
                readCallback = null;
                //   callback.invoke("Read failed", null);
            }
        }

    }


    // Some peripherals re-use UUIDs for multiple characteristics so we need to check the properties
    // and UUID of all characteristics instead of using service.getCharacteristic(characteristicUUID)
    private BluetoothGattCharacteristic findReadableCharacteristic(BluetoothGattService service, UUID characteristicUUID) {
        BluetoothGattCharacteristic characteristic = null;

        int read = BluetoothGattCharacteristic.PROPERTY_READ;

        List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
        for (BluetoothGattCharacteristic c : characteristics) {
            if ((c.getProperties() & read) != 0 && characteristicUUID.equals(c.getUuid())) {
                characteristic = c;
                break;
            }
        }

        // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
        if (characteristic == null) {
            characteristic = service.getCharacteristic(characteristicUUID);
        }

        return characteristic;
    }


    public void doWrite(BluetoothGattCharacteristic characteristic, byte[] data) {
        characteristic.setValue(data);

        if (!gatt.writeCharacteristic(characteristic)) {
            Log.d(LOG_TAG, "Error on doWrite");
        }
    }

    public void write(UUID serviceUUID, UUID characteristicUUID, byte[] data, Integer maxByteSize, Integer queueSleepTime, Callback callback, int writeType) {
        if (gatt == null) {
            callback.invoke(MsgCode.getErrorMap(MsgCode.BluetoothGatt_Is_Null));
            // callback.invoke("BluetoothGatt is null");
        } else {
            moreThan20String = "";

            //saveToMap(data, callback);
            BluetoothGattService service = gatt.getService(serviceUUID);
            BluetoothGattCharacteristic characteristic = findWritableCharacteristic(service, characteristicUUID, writeType);
            characteristic.setWriteType(writeType);

            if (characteristic == null) {
                //callback.invoke("Characteristic " + characteristicUUID + " not found.");
                callback.invoke(MsgCode.getErrorMap(MsgCode.Characteristic_Not_Found));
            } else {

                if (writeQueue.size() > 0) {
                    callback.invoke(MsgCode.getErrorMap(MsgCode.BLE_Is_Busy));
                }

                if (writeCallback != null) {
                    callback.invoke(MsgCode.getErrorMap(MsgCode.BLE_Is_Busy));
                }

                if (writeQueue.size() == 0 && writeCallback == null) {

                    if (BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT == writeType) {
                        writeCallback = callback;
                    }
                    //writeCallback = callback;
                    if (data.length > maxByteSize) {
                        int dataLength = data.length;
                        int count = 0;
                        byte[] firstMessage = null;
                        List<byte[]> splittedMessage = new ArrayList<>();

                        while (count < dataLength && (dataLength - count > maxByteSize)) {
                            if (count == 0) {
                                firstMessage = Arrays.copyOfRange(data, count, count + maxByteSize);
                            } else {
                                byte[] splitMessage = Arrays.copyOfRange(data, count, count + maxByteSize);
                                splittedMessage.add(splitMessage);
                            }
                            count += maxByteSize;
                        }
                        if (count < dataLength) {
                            // Other bytes in queue
                            byte[] splitMessage = Arrays.copyOfRange(data, count, data.length);
                            splittedMessage.add(splitMessage);
                        }

                        if (BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT == writeType) {
                            writeQueue.addAll(splittedMessage);
                            doWrite(characteristic, firstMessage);
                        } else {
                            try {
                                doWrite(characteristic, firstMessage);
                                Thread.sleep(queueSleepTime);
                                for (byte[] message : splittedMessage) {
                                    doWrite(characteristic, message);
                                    Thread.sleep(queueSleepTime);
                                }
                                //   callback.invoke();
                            } catch (InterruptedException e) {
                                callback.invoke(MsgCode.getErrorMap(MsgCode.Writing_Fail));
                                //   callback.invoke("Error during writing");
                            }
                        }
                    } else {

                        // data = ByteUtil.hexStringToBytes("00" + ByteUtil.byte2hex(data).replace(" ", ""));
                        Log.v(LOG_TAG, "write value=" + ByteUtil.byte2hex(data));
                        characteristic.setValue(data);


                        if (gatt.writeCharacteristic(characteristic)) {
                            Log.d(LOG_TAG, "Write completed");
                            if (BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE == writeType) {
                                //callback.invoke();
                            }
                        } else {
                            callback.invoke(MsgCode.getErrorMap(MsgCode.Writing_Fail));
                            //  callback.invoke("Write failed");
                            writeCallback = null;
                        }
                    }
                }
            }
        }

    }

    // Some peripherals re-use UUIDs for multiple characteristics so we need to check the properties
    // and UUID of all characteristics instead of using service.getCharacteristic(characteristicUUID)
    private BluetoothGattCharacteristic findWritableCharacteristic(BluetoothGattService service, UUID characteristicUUID, int writeType) {
        try {
            BluetoothGattCharacteristic characteristic = null;

            // get write property
            int writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE;
            if (writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
                writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
            }

            List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
            for (BluetoothGattCharacteristic c : characteristics) {
                if ((c.getProperties() & writeProperty) != 0 && characteristicUUID.equals(c.getUuid())) {
                    characteristic = c;
                    break;
                }
            }

            // As a last resort, try and find ANY characteristic with this UUID, even if it doesn't have the correct properties
            if (characteristic == null) {
                characteristic = service.getCharacteristic(characteristicUUID);
            }

            return characteristic;
        } catch (Exception e) {
            Log.e(LOG_TAG, "Error on findWritableCharacteristic", e);
            return null;
        }
    }

    private String generateHashKey(BluetoothGattCharacteristic characteristic) {
        return generateHashKey(characteristic.getService().getUuid(), characteristic);
    }

    private String generateHashKey(UUID serviceUUID, BluetoothGattCharacteristic characteristic) {
        return String.valueOf(serviceUUID) + "|" + characteristic.getUuid() + "|" + characteristic.getInstanceId();
    }

    //-------------------------------------------------
    private void saveToMap(byte[] data, Callback callback) {

//        try {
//            ProtocolMsg msg=handler.getProtocolParseResult(ByteUtil.hexStringToBytes(moreThan20String));
//            babyBathMessage = BabyBathProtoV03.BabyBathMessage.parseFrom(data);
//        } catch (InvalidProtocolBufferException e) {
//            Log.v(LOG_TAG, "InvalidProtocolBufferException=" + e);
//        }
        //handlerMap.put(babyBathMessage.getLoopCount(), callback);
        if (handler != null) {
            ProtocolMsg msg = handler.getProtocolParseResult(data);
            Log.v(LOG_TAG, "saveToMap: " + msg);
            if (msg != null && msg.getLoopCount() != 0) {
                handlerMap.put(msg.getLoopCount(), callback);
            }
        }


    }


    private Callback getHandlerFromMap(byte[] data) {

//        try {
//            babyBathMessage = BabyBathProtoV03.BabyBathMessage.parseFrom(data);
//        } catch (InvalidProtocolBufferException e) {
//            Log.v(LOG_TAG, "InvalidProtocolBufferException=" + e);
//        }
//
//        return handlerMap.get(babyBathMessage.getLoopCount()) != null ? handlerMap.get(babyBathMessage.getLoopCount()) : null;
        if (handler != null) {
            ProtocolMsg msg = handler.getProtocolParseResult(data);
            Log.v(LOG_TAG, "getHandlerFromMap: " + msg);
            if (msg != null && msg.getLoopCount() != 0) {
                return handlerMap.get(msg.getLoopCount()) != null ? handlerMap.get(msg.getLoopCount()) : null;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    public void read(UUID serviceUUID, UUID characteristicUUID) {

        if (gatt == null) {
            //  callback.invoke("BluetoothGatt is null", null);
            return;
        }
        BluetoothGattService service = gatt.getService(serviceUUID);
        BluetoothGattCharacteristic characteristic = findReadableCharacteristic(service, characteristicUUID);

//        if (characteristic == null) {
//            //  callback.invoke("Characteristic " + characteristicUUID + " not found.", null);
//        } else {
//            //   readCallback = callback;
//            if (!gatt.readCharacteristic(characteristic)) {
//                //  readCallback = null;
//                //  callback.invoke("Read failed", null);
//            }
//        }

    }

}
