package cn.wandersnail.ble;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;

import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;

import cn.wandersnail.ble.callback.RequestCallback;
import cn.wandersnail.ble.callback.ScanListener;
import cn.wandersnail.ble.util.Logger;
import cn.wandersnail.commons.observer.Observable;
import cn.wandersnail.commons.poster.MethodInfo;
import cn.wandersnail.commons.poster.PosterDispatcher;
import cn.wandersnail.commons.util.MathUtils;
import cn.wandersnail.commons.util.StringUtils;

/**
 * date: 2019/8/3 19:47
 * author: zengfansheng
 */
class ConnectionImpl implements Connection, ScanListener {
    private static final int MSG_REQUEST_TIMEOUT = 0;
    private static final int MSG_CONNECT = 1;
    private static final int MSG_DISCONNECT = 2;
    private static final int MSG_REFRESH = 3;
    private static final int MSG_TIMER = 4;
    private static final int MSG_DISCOVER_SERVICES = 6;
    private static final int MSG_ON_CONNECTION_STATE_CHANGE = 7;
    private static final int MSG_ON_SERVICES_DISCOVERED = 8;

    private static final int MSG_ARG_NONE = 0;
    private static final int MSG_ARG_RECONNECT = 1;

    private final BluetoothAdapter bluetoothAdapter;
    private final Device device;
    private final ConnectionConfiguration configuration;//连接配置
    private BluetoothGatt bluetoothGatt;
    private final List<GenericRequest> requestQueue = new ArrayList<>();//请求队列
    private GenericRequest currentRequest;//当前的请求
    private EventObserver observer;//伴生观察者
    private boolean isReleased;//连接是否已释放
    private final Handler connHandler;//用于操作连接的Handler,运行在主线程
    private long connStartTime; //用于连接超时计时
    private int refreshCount;//刷新(清缓存)计数,在发现服务后清零
    private int tryReconnectCount;//尝试重连计数
    private ConnectionState lastConnectionState;//上次连接状态
    private int reconnectImmediatelyCount = 0; //不搜索直接重连计数
    private boolean refreshing;//是否正在执行清理缓存
    private boolean isActiveDisconnect;//是否主动断开连接
    private long lastScanStopTime;//上次搜索停止时间
    private final Logger logger;
    private final Observable observable;
    private final PosterDispatcher posterDispatcher;
    private final BluetoothGattCallback gattCallback = new BleGattCallback();
    private final EasyBLE easyBle;
    private int mtu = 23;
    private BluetoothGattCallback originCallback;

    ConnectionImpl(EasyBLE easyBle, BluetoothAdapter bluetoothAdapter, Device device, ConnectionConfiguration configuration,
                   int connectDelay, EventObserver observer) {
        this.easyBle = easyBle;
        this.bluetoothAdapter = bluetoothAdapter;
        this.device = device;
        //如果没有配置
        if (configuration == null) {
            this.configuration = new ConnectionConfiguration();
        } else {
            this.configuration = configuration;
        }
        this.observer = observer;
        logger = easyBle.getLogger();
        observable = easyBle.getObservable();
        posterDispatcher = easyBle.getPosterDispatcher();
        connHandler = new ConnHandler(this);
        connStartTime = System.currentTimeMillis();
        connHandler.sendEmptyMessageDelayed(MSG_CONNECT, connectDelay); //执行连接
        connHandler.sendEmptyMessageDelayed(MSG_TIMER, connectDelay); //启动定时器
        easyBle.addScanListener(this);
    }

    @Override
    public void onScanStart() {
    }

    @Override
    public void onScanStop() {
        synchronized (this) {
            lastScanStopTime = System.currentTimeMillis();
        }
    }

    @Override
    public void onScanResult(@NonNull Device device, boolean isConnectedBySys) {
        synchronized (this) {
            if (!isReleased && this.device.equals(device) && this.device.connectionState == ConnectionState.SCANNING_FOR_RECONNECTION) {
                connHandler.sendEmptyMessage(MSG_CONNECT);
            }
        }
    }

    @Override
    public void onScanError(int errorCode, @NonNull String errorMsg) {

    }

    @Override
    public void setBluetoothGattCallback(BluetoothGattCallback callback) {
        originCallback = callback;
    }

    @Override
    public boolean hasProperty(UUID service, UUID characteristic, int property) {
        BluetoothGattCharacteristic charac = getCharacteristic(service, characteristic);
        if (charac == null) {
            return false;
        }
        return (charac.getProperties() & property) != 0;
    }

    private class BleGattCallback extends BluetoothGattCallback {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onConnectionStateChange(gatt, status, newState));
            }
            if (!isReleased) {
                Message.obtain(connHandler, MSG_ON_CONNECTION_STATE_CHANGE, status, newState).sendToTarget();
            } else {
                closeGatt(gatt);
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onServicesDiscovered(gatt, status));
            }
            if (!isReleased) {
                Message.obtain(connHandler, MSG_ON_SERVICES_DISCOVERED, status, 0).sendToTarget();
            } else {
                closeGatt(gatt);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onCharacteristicRead(gatt, characteristic, status));
            }
            if (currentRequest != null) {
                if (currentRequest.type == RequestType.READ_CHARACTERISTIC) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        notifyCharacteristicRead(currentRequest, characteristic.getValue());
                    } else {
                        handleGattStatusFailed();
                    }
                    executeNextRequest();
                }
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onCharacteristicWrite(gatt, characteristic, status));
            }
            if (currentRequest != null && currentRequest.type == RequestType.WRITE_CHARACTERISTIC &&
                    currentRequest.writeOptions.isWaitWriteResult) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    if (logger.isEnabled()) {
                        byte[] data = (byte[]) currentRequest.value;//完整包数据
                        int packageSize = currentRequest.writeOptions.packageSize;
                        int total = data.length / packageSize + (data.length % packageSize == 0 ? 0 : 1);
                        int progress;
                        if (currentRequest.remainQueue == null || currentRequest.remainQueue.isEmpty()) {
                            progress = total;
                        } else {
                            progress = data.length / packageSize - currentRequest.remainQueue.size() + 1;
                        }
                        printWriteLog(currentRequest, progress, total, characteristic.getValue());
                    }
                    if (currentRequest.remainQueue == null || currentRequest.remainQueue.isEmpty()) {
                        notifyCharacteristicWrite(currentRequest, (byte[]) currentRequest.value);
                        executeNextRequest();
                    } else {
                        connHandler.removeMessages(MSG_REQUEST_TIMEOUT);
                        connHandler.sendMessageDelayed(Message.obtain(connHandler, MSG_REQUEST_TIMEOUT, currentRequest),
                                configuration.requestTimeoutMillis);
                        GenericRequest req = currentRequest;
                        int delay = currentRequest.writeOptions.packageWriteDelayMillis;
                        if (delay > 0) {
                            try {
                                Thread.sleep(delay);
                            } catch (InterruptedException ignore) {
                            }
                            if (req != currentRequest) {
                                return;
                            }
                        }
                        req.sendingBytes = req.remainQueue.remove();
                        write(req, characteristic, req.sendingBytes);
                    }
                } else {
                    handleFailedCallback(currentRequest, REQUEST_FAIL_TYPE_GATT_STATUS_FAILED, true);
                }
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onCharacteristicChanged(gatt, characteristic));
            }
            notifyCharacteristicChanged(characteristic);
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onReadRemoteRssi(gatt, rssi, status));
            }
            if (currentRequest != null) {
                if (currentRequest.type == RequestType.READ_RSSI) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        notifyRssiRead(currentRequest, rssi);
                    } else {
                        handleGattStatusFailed();
                    }
                    executeNextRequest();
                }
            }
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onDescriptorRead(gatt, descriptor, status));
            }
            if (currentRequest != null) {
                if (currentRequest.type == RequestType.READ_DESCRIPTOR) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        notifyDescriptorRead(currentRequest, descriptor.getValue());
                    } else {
                        handleGattStatusFailed();
                    }
                    executeNextRequest();
                }
            }
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            if (originCallback != null) {
                easyBle.getExecutorService().execute(() -> originCallback.onDescriptorWrite(gatt, descriptor, status));
            }
            if (currentRequest != null) {
                if (currentRequest.type == RequestType.SET_NOTIFICATION || currentRequest.type == RequestType.SET_INDICATION) {
                    BluetoothGattDescriptor localDescriptor = getDescriptor(descriptor.getCharacteristic().getService().getUuid(),
                            descriptor.getCharacteristic().getUuid(), clientCharacteristicConfig);
                    if (status != BluetoothGatt.GATT_SUCCESS) {
                        handleGattStatusFailed();
                        if (localDescriptor != null) {
                            localDescriptor.setValue(currentRequest.descriptorTemp);
                        }
                    } else {
                        notifyNotificationChanged(currentRequest, ((int) currentRequest.value) == 1);
                    }
                    executeNextRequest();
                }
            }
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            if (originCallback != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    easyBle.getExecutorService().execute(() -> originCallback.onMtuChanged(gatt, mtu, status));
                }
            }
            if (currentRequest != null) {
                if (currentRequest.type == RequestType.CHANGE_MTU) {
                    if (status == BluetoothGatt.GATT_SUCCESS) {
                        ConnectionImpl.this.mtu = mtu;
                        notifyMtuChanged(currentRequest, mtu);
                    } else {
                        handleGattStatusFailed();
                    }
                    executeNextRequest();
                }
            }
        }

        @Override
        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            if (originCallback != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    easyBle.getExecutorService().execute(() -> originCallback.onPhyRead(gatt, txPhy, rxPhy, status));
                }
            }
            handlePhyChange(true, txPhy, rxPhy, status);
        }

        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            if (originCallback != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    easyBle.getExecutorService().execute(() -> originCallback.onPhyRead(gatt, txPhy, rxPhy, status));
                }
            }
            handlePhyChange(false, txPhy, rxPhy, status);
        }
    }

    private void doOnConnectionStateChange(int status, int newState) {
        if (bluetoothGatt != null) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                if (newState == BluetoothProfile.STATE_CONNECTED) {
                    logD(Logger.TYPE_CONNECTION_STATE, "connected! [name: %s, addr: %s]", device.name, device.address);
                    device.connectionState = ConnectionState.CONNECTED;
                    sendConnectionCallback();
                    // 延时一会再去发现服务
                    connHandler.sendEmptyMessageDelayed(MSG_DISCOVER_SERVICES, configuration.discoverServicesDelayMillis);
                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    logD(Logger.TYPE_CONNECTION_STATE, "disconnected! [name: %s, addr: %s, autoReconnEnable: %s]",
                            device.name, device.address, configuration.isAutoReconnect);
                    clearRequestQueueAndNotify();
                    notifyDisconnected();
                }
            } else {
                logE(Logger.TYPE_CONNECTION_STATE, "GATT error! [status: %d, name: %s, addr: %s]",
                        status, device.name, device.address);
                if (status == 133) {
                    doClearTaskAndRefresh();
                } else {
                    clearRequestQueueAndNotify();
                    notifyDisconnected();
                }
            }
        }
    }

    private void doOnServicesDiscovered(int status) {
        if (bluetoothGatt != null) {
            List<BluetoothGattService> services = bluetoothGatt.getServices();
            if (status == BluetoothGatt.GATT_SUCCESS) {
                logD(Logger.TYPE_CONNECTION_STATE, "services discovered! [name: %s, addr: %s, size: %d]", device.name,
                        device.address, services.size());
                if (services.isEmpty()) {
                    doClearTaskAndRefresh();
                } else {
                    refreshCount = 0;
                    tryReconnectCount = 0;
                    reconnectImmediatelyCount = 0;
                    device.connectionState = ConnectionState.SERVICE_DISCOVERED;
                    sendConnectionCallback();
                }
            } else {
                doClearTaskAndRefresh();
                logE(Logger.TYPE_CONNECTION_STATE, "GATT error! [status: %d, name: %s, addr: %s]",
                        status, device.name, device.address);
            }
        }
    }

    private void doDiscoverServices() {
        if (bluetoothGatt != null) {
            bluetoothGatt.discoverServices();
            device.connectionState = ConnectionState.SERVICE_DISCOVERING;
            sendConnectionCallback();
        } else {
            notifyDisconnected();
        }
    }

    private void doTimer() {
        if (!isReleased) {
            //只处理不是已发现服务并且不在刷新也不是主动断开连接的
            if (device.connectionState != ConnectionState.SERVICE_DISCOVERED && !refreshing && !isActiveDisconnect) {
                if (device.connectionState != ConnectionState.DISCONNECTED) {
                    //超时
                    if (System.currentTimeMillis() - connStartTime > configuration.connectTimeoutMillis) {
                        connStartTime = System.currentTimeMillis();
                        logE(Logger.TYPE_CONNECTION_STATE, "connect timeout! [name: %s, addr: %s]", device.name, device.address);
                        int type;
                        switch (device.connectionState) {
                            case SCANNING_FOR_RECONNECTION:
                                type = TIMEOUT_TYPE_CANNOT_DISCOVER_DEVICE;
                                break;
                            case CONNECTING:
                                type = TIMEOUT_TYPE_CANNOT_CONNECT;
                                break;
                            default:
                                type = TIMEOUT_TYPE_CANNOT_DISCOVER_SERVICES;
                                break;
                        }
                        observable.notifyObservers(MethodInfoGenerator.onConnectTimeout(device, type));
                        if (observer != null) {
                            posterDispatcher.post(observer, MethodInfoGenerator.onConnectTimeout(device, type));
                        }
                        boolean infinite = configuration.tryReconnectMaxTimes == ConnectionConfiguration.TRY_RECONNECT_TIMES_INFINITE;
                        if (configuration.isAutoReconnect && (infinite || tryReconnectCount < configuration.connectTimeoutMillis)) {
                            doDisconnect(true);
                        } else {
                            doDisconnect(false);
                            if (observer != null) {
                                posterDispatcher.post(observer, MethodInfoGenerator.onConnectFailed(device, CONNECT_FAIL_TYPE_MAXIMUM_RECONNECTION));
                            }
                            observable.notifyObservers(MethodInfoGenerator.onConnectFailed(device, CONNECT_FAIL_TYPE_MAXIMUM_RECONNECTION));
                            logE(Logger.TYPE_CONNECTION_STATE, "connect failed! [type: maximun reconnection, name: %s, addr: %s]",
                                    device.name, device.address);
                        }
                    }
                } else if (configuration.isAutoReconnect) {
                    doDisconnect(true);
                }
            }
            connHandler.sendEmptyMessageDelayed(MSG_TIMER, 500);
        }
    }

    private Runnable connectRunnable = new Runnable() {
        @Override
        public void run() {
            if (!isReleased) {
                //连接之前必须先停止搜索
                easyBle.stopScan();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    bluetoothGatt = device.getOriginDevice().connectGatt(easyBle.getContext(), false, gattCallback,
                            configuration.transport, configuration.phy);
                } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    bluetoothGatt = device.getOriginDevice().connectGatt(easyBle.getContext(), false, gattCallback,
                            configuration.transport);
                } else {
                    bluetoothGatt = device.getOriginDevice().connectGatt(easyBle.getContext(), false, gattCallback);
                }
            }
        }
    };

    private void doConnect() {
        cancelRefreshState();
        device.connectionState = ConnectionState.CONNECTING;
        sendConnectionCallback();
        logD(Logger.TYPE_CONNECTION_STATE, "connecting [name: %s, addr: %s]", device.name, device.address);
        connHandler.postDelayed(connectRunnable, 500);
    }

    /**
     * 处理断开
     *
     * @param reconnect 断开后是否重连
     */
    private void doDisconnect(boolean reconnect) {
        clearRequestQueueAndNotify();
        connHandler.removeCallbacks(connectRunnable);
        connHandler.removeMessages(MSG_DISCOVER_SERVICES);
        if (bluetoothGatt != null) {
            closeGatt(bluetoothGatt);
            bluetoothGatt = null;
        }
        device.connectionState = ConnectionState.DISCONNECTED;
        if (bluetoothAdapter != null && bluetoothAdapter.isEnabled() && reconnect && !isReleased) {
            if (reconnectImmediatelyCount < configuration.reconnectImmediatelyMaxTimes) {
                tryReconnectCount++;
                reconnectImmediatelyCount++;
                connStartTime = System.currentTimeMillis();
                doConnect();
                return;
            } else if (canScanReconnect()) {
                tryScanReconnect();
            }
        }
        sendConnectionCallback();
    }

    private void doClearTaskAndRefresh() {
        clearRequestQueueAndNotify();
        doRefresh(true);
    }

    //处理刷新
    private void doRefresh(boolean isAuto) {
        logD(Logger.TYPE_CONNECTION_STATE, "refresh GATT! [name: %s, addr: %s]", device.name, device.address);
        connStartTime = System.currentTimeMillis();
        if (bluetoothGatt != null) {
            try {
                bluetoothGatt.disconnect();
            } catch (Exception ignore) {
            }

            if (isAuto) {
                if (refreshCount <= 5) {
                    refreshing = doRefresh();
                }
                refreshCount++;
            } else {
                refreshing = doRefresh();
            }
            if (refreshing) {
                connHandler.postDelayed(this::cancelRefreshState, 2000);
            } else if (bluetoothGatt != null) {
                closeGatt(bluetoothGatt);
                bluetoothGatt = null;
            }
        }
        notifyDisconnected();
    }

    private void cancelRefreshState() {
        if (refreshing) {
            refreshing = false;
            if (bluetoothGatt != null) {
                closeGatt(bluetoothGatt);
                bluetoothGatt = null;
            }
        }
    }

    private void tryScanReconnect() {
        if (!isReleased) {
            connStartTime = System.currentTimeMillis();
            easyBle.stopScan();
            //搜索设备,搜索到才执行连接
            device.connectionState = ConnectionState.SCANNING_FOR_RECONNECTION;
            logD(Logger.TYPE_CONNECTION_STATE, "scanning for reconnection [name: %s, addr: %s]", device.name, device.address);
            easyBle.startScan();
        }
    }

    private boolean canScanReconnect() {
        long duration = System.currentTimeMillis() - lastScanStopTime;
        List<Pair<Integer, Integer>> parameters = configuration.scanIntervalPairsInAutoReconnection;
        Collections.sort(parameters, (o1, o2) -> {
            if (o1 == null || o1.first == null) return 1;
            if (o2 == null || o2.first == null) return -1;
            return o2.first.compareTo(o1.first);
        });
        for (Pair<Integer, Integer> pair : parameters) {
            if (pair.first != null && pair.second != null && tryReconnectCount >= pair.first && duration >= pair.second) {
                return true;
            }
        }
        return false;
    }

    private void closeGatt(BluetoothGatt gatt) {
        try {
            gatt.disconnect();
        } catch (Exception ignore) {
        }
        try {
            gatt.close();
        } catch (Exception ignore) {
        }
    }

    private void notifyDisconnected() {
        device.connectionState = ConnectionState.DISCONNECTED;
        sendConnectionCallback();
    }

    private void sendConnectionCallback() {
        if (lastConnectionState != device.connectionState) {
            lastConnectionState = device.connectionState;
            if (observer != null) {
                posterDispatcher.post(observer, MethodInfoGenerator.onConnectionStateChanged(device));
            }
            observable.notifyObservers(MethodInfoGenerator.onConnectionStateChanged(device));
        }
    }

    private boolean write(GenericRequest request, BluetoothGattCharacteristic characteristic, byte[] value) {
        characteristic.setValue(value);
        int writeType = request.writeOptions.writeType;
        if ((writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE ||
                writeType == BluetoothGattCharacteristic.WRITE_TYPE_SIGNED ||
                writeType == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)) {
            characteristic.setWriteType(writeType);
        }
        if (bluetoothGatt == null) {
            handleFailedCallback(request, REQUEST_FAIL_TYPE_GATT_IS_NULL, true);
            return false;
        }
        if (!bluetoothGatt.writeCharacteristic(characteristic)) {
            handleWriteFailed(request);
            return false;
        }
        return true;
    }

    private void handleWriteFailed(GenericRequest request) {
        connHandler.removeMessages(MSG_REQUEST_TIMEOUT);
        request.remainQueue = null;
        handleFailedCallback(request, REQUEST_FAIL_TYPE_REQUEST_FAILED, true);
    }

    private boolean enableNotificationOrIndicationFail(boolean enable, boolean notification, BluetoothGattCharacteristic characteristic) {
        if (!bluetoothAdapter.isEnabled() || bluetoothGatt == null || !bluetoothGatt
                .setCharacteristicNotification(characteristic, enable)) {
            return true;
        }
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientCharacteristicConfig);
        if (descriptor == null) {
            return true;
        }
        byte[] originValue = descriptor.getValue();
        if (currentRequest != null) {
            if (currentRequest.type == RequestType.SET_NOTIFICATION || currentRequest.type == RequestType.SET_INDICATION) {
                currentRequest.descriptorTemp = originValue;
            }
        }
        if (enable) {
            if (notification) {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            } else {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
            }
        } else {
            descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        }
        // There was a bug in Android up to 6.0 where the descriptor was written using parent
        // characteristic's write type, instead of always Write With Response, as the spec says.
        int writeType = characteristic.getWriteType();
        characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        boolean result = bluetoothGatt.writeDescriptor(descriptor);
        if (!enable) {
            //还原原始值
            descriptor.setValue(originValue);
        }
        characteristic.setWriteType(writeType);
        return !result;
    }

    private static class ConnHandler extends Handler {
        private final WeakReference<ConnectionImpl> weakRef;

        ConnHandler(ConnectionImpl connection) {
            super(Looper.getMainLooper());
            weakRef = new WeakReference<>(connection);
        }

        @Override
        public void handleMessage(Message msg) {
            ConnectionImpl connection = weakRef.get();
            if (connection != null) {
                if (connection.isReleased) {
                    return;
                }
                switch (msg.what) {
                    case MSG_REQUEST_TIMEOUT:
                        GenericRequest request = (GenericRequest) msg.obj;
                        if (connection.currentRequest != null && connection.currentRequest == request) {
                            connection.handleFailedCallback(request, REQUEST_FAIL_TYPE_REQUEST_TIMEOUT, false);
                            connection.executeNextRequest();
                        }
                        break;
                    case MSG_CONNECT://连接   
                        if (connection.bluetoothAdapter.isEnabled()) {
                            connection.doConnect();
                        }
                        break;
                    case MSG_DISCONNECT://断开
                        boolean reconnect = msg.arg1 == MSG_ARG_RECONNECT && connection.bluetoothAdapter.isEnabled();
                        connection.doDisconnect(reconnect);
                        break;
                    case MSG_REFRESH://手动刷新
                        connection.doRefresh(false);
                        break;
                    case MSG_TIMER://定时器
                        connection.doTimer();
                        break;
                    case MSG_DISCOVER_SERVICES://执行发现服务
                    case MSG_ON_CONNECTION_STATE_CHANGE://连接状态变化
                    case MSG_ON_SERVICES_DISCOVERED://服务已发现
                        if (connection.bluetoothAdapter.isEnabled()) {
                            if (msg.what == MSG_DISCOVER_SERVICES) {
                                connection.doDiscoverServices();
                            } else if (msg.what == MSG_ON_SERVICES_DISCOVERED) {
                                connection.doOnServicesDiscovered(msg.arg1);
                            } else {
                                connection.doOnConnectionStateChange(msg.arg1, msg.arg2);
                            }
                        }
                        break;
                }
            }
        }
    }

    private void enqueue(GenericRequest request) {
        if (isReleased) {
            handleFailedCallback(request, REQUEST_FAIL_TYPE_CONNECTION_RELEASED, false);
        } else {
            synchronized (this) {
                if (currentRequest == null) {
                    executeRequest(request);
                } else {
                    //根据优化级将请求插入队列中
                    int index = -1;
                    for (int i = 0; i < requestQueue.size(); i++) {
                        GenericRequest req = requestQueue.get(i);
                        if (req.priority >= request.priority) {
                            if (i < requestQueue.size() - 1) {
                                if (requestQueue.get(i + 1).priority < request.priority) {
                                    index = i + 1;
                                    break;
                                }
                            } else {
                                index = i + 1;
                            }
                        }
                    }
                    if (index == -1) {
                        requestQueue.add(0, request);
                    } else if (index >= requestQueue.size()) {
                        requestQueue.add(request);
                    } else {
                        requestQueue.add(index, request);
                    }
                }
            }
        }
    }

    private void executeNextRequest() {
        synchronized (this) {
            connHandler.removeMessages(MSG_REQUEST_TIMEOUT);
            if (requestQueue.isEmpty()) {
                currentRequest = null;
            } else {
                executeRequest(requestQueue.remove(0));
            }
        }
    }

    private void executeRequest(GenericRequest request) {
        currentRequest = request;
        connHandler.sendMessageDelayed(Message.obtain(connHandler, MSG_REQUEST_TIMEOUT, request), configuration.requestTimeoutMillis);
        if (bluetoothAdapter.isEnabled()) {
            if (bluetoothGatt != null) {
                switch (request.type) {
                    case READ_RSSI:
                        if (!bluetoothGatt.readRemoteRssi()) {
                            handleFailedCallback(request, REQUEST_FAIL_TYPE_REQUEST_FAILED, true);
                        }
                        break;
                    case CHANGE_MTU:
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            if (!bluetoothGatt.requestMtu((int) request.value)) {
                                handleFailedCallback(request, REQUEST_FAIL_TYPE_REQUEST_FAILED, true);
                            }
                        }
                        break;
                    case READ_PHY:
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            bluetoothGatt.readPhy();
                        }
                        break;
                    case SET_PREFERRED_PHY:
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                            int[] options = (int[]) request.value;
                            bluetoothGatt.setPreferredPhy(options[0], options[1], options[2]);
                        }
                        break;
                    default:
                        BluetoothGattService gattService = bluetoothGatt.getService(request.service);
                        if (gattService != null) {
                            BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(request.characteristic);
                            if (characteristic != null) {
                                switch (request.type) {
                                    case SET_NOTIFICATION:
                                    case SET_INDICATION:
                                        executeIndicationOrNotification(request, characteristic);
                                        break;
                                    case READ_CHARACTERISTIC:
                                        executeReadCharacteristic(request, characteristic);
                                        break;
                                    case READ_DESCRIPTOR:
                                        executeReadDescriptor(request, characteristic);
                                        break;
                                    case WRITE_CHARACTERISTIC:
                                        executeWriteCharacteristic(request, characteristic);
                                        break;
                                }
                            } else {
                                handleFailedCallback(request, REQUEST_FAIL_TYPE_CHARACTERISTIC_NOT_EXIST, true);
                            }
                        } else {
                            handleFailedCallback(request, REQUEST_FAIL_TYPE_SERVICE_NOT_EXIST, true);
                        }
                        break;
                }
            } else {
                handleFailedCallback(request, REQUEST_FAIL_TYPE_GATT_IS_NULL, true);
            }
        } else {
            handleFailedCallback(request, REQUEST_FAIL_TYPE_BLUETOOTH_ADAPTER_DISABLED, true);
        }
    }

    private void printWriteLog(GenericRequest request, int progress, int total, byte[] value) {
        if (logger.isEnabled()) {
            String t = String.valueOf(total);
            StringBuilder sb = new StringBuilder(String.valueOf(progress));
            while (sb.length() < t.length()) {
                sb.insert(0, "0");
            }
            logD(Logger.TYPE_CHARACTERISTIC_WRITE, "package [%s/%s] write success! [UUID: %s, addr: %s, value: %s]",
                    sb, t, substringUuid(request.characteristic), device.address, toHex(value));
        }
    }

    private void executeWriteCharacteristic(GenericRequest request, BluetoothGattCharacteristic characteristic) {
        try {
            byte[] value = (byte[]) request.value;
            WriteOptions options = request.writeOptions;
            int reqDelay = options.requestWriteDelayMillis > 0 ? options.requestWriteDelayMillis : options.packageWriteDelayMillis;
            if (reqDelay > 0) {
                try {
                    Thread.sleep(reqDelay);
                } catch (InterruptedException ignore) {
                }
                if (request != currentRequest) {
                    return;
                }
            }
            if (options.useMtuAsPackageSize) {
                options.packageSize = mtu - 3;
            }
            if (value.length > options.packageSize) {
                List<byte[]> list = MathUtils.splitPackage(value, options.packageSize);
                if (!options.isWaitWriteResult) { //不等待写入回调,直接写入下一包数据
                    int delay = options.packageWriteDelayMillis;
                    for (int i = 0; i < list.size(); i++) {
                        byte[] bytes = list.get(i);
                        if (i > 0 && delay > 0) {
                            try {
                                Thread.sleep(delay);
                            } catch (InterruptedException ignore) {
                            }
                            if (request != currentRequest) {
                                return;
                            }
                        }
                        if (!write(request, characteristic, bytes)) {
                            return;
                        } else {
                            printWriteLog(request, i + 1, list.size(), bytes);
                        }
                    }
                    printWriteLog(request, list.size(), list.size(), list.get(list.size() - 1));
                } else { //发送第一包,剩下的加入队列
                    request.remainQueue = new ConcurrentLinkedQueue<>();
                    request.remainQueue.addAll(list);
                    request.sendingBytes = request.remainQueue.remove();
                    write(request, characteristic, request.sendingBytes);
                }
            } else {
                request.sendingBytes = value;
                if (write(request, characteristic, value)) {
                    if (!options.isWaitWriteResult) {
                        notifyCharacteristicWrite(request, value);
                        printWriteLog(request, 1, 1, value);
                        executeNextRequest();
                    }
                }
            }
        } catch (Exception e) {
            handleWriteFailed(request);
        }
    }

    private void executeReadDescriptor(GenericRequest request, BluetoothGattCharacteristic characteristic) {
        BluetoothGattDescriptor gattDescriptor = characteristic.getDescriptor(request.descriptor);
        if (gattDescriptor != null) {
            if (!bluetoothGatt.readDescriptor(gattDescriptor)) {
                handleFailedCallback(request, REQUEST_FAIL_TYPE_REQUEST_FAILED, true);
            }
        } else {
            handleFailedCallback(request, REQUEST_FAIL_TYPE_DESCRIPTOR_NOT_EXIST, true);
        }
    }

    private void executeReadCharacteristic(GenericRequest request, BluetoothGattCharacteristic characteristic) {
        if (!bluetoothGatt.readCharacteristic(characteristic)) {
            handleFailedCallback(request, REQUEST_FAIL_TYPE_REQUEST_FAILED, true);
        }
    }

    private void executeIndicationOrNotification(GenericRequest request, BluetoothGattCharacteristic characteristic) {
        if (enableNotificationOrIndicationFail(((int) request.value) == 1,
                request.type == RequestType.SET_NOTIFICATION, characteristic)) {
            handleGattStatusFailed();
        }
    }

    private void handlePhyChange(boolean read, int txPhy, int rxPhy, int status) {
        if (currentRequest != null) {
            if ((read && currentRequest.type == RequestType.READ_PHY) || ((!read && currentRequest.type == RequestType.SET_PREFERRED_PHY))) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    notifyPhyChange(currentRequest, txPhy, rxPhy);
                } else {
                    handleGattStatusFailed();
                }
                executeNextRequest();
            }
        }
    }

    private void handleGattStatusFailed() {
        if (currentRequest != null) {
            handleFailedCallback(currentRequest, REQUEST_FAIL_TYPE_GATT_STATUS_FAILED, false);
        }
    }

    private void handleFailedCallback(GenericRequest request, int failType, boolean executeNext) {
        notifyRequestFailed(request, failType);
        if (executeNext) {
            executeNextRequest();
        }
    }

    private String toHex(byte[] bytes) {
        return StringUtils.toHex(bytes);
    }

    private String substringUuid(UUID uuid) {
        return uuid == null ? "null" : uuid.toString().substring(0, 8);
    }

    private void handleCallbacks(RequestCallback callback, MethodInfo info) {
        if (observer != null) {
            posterDispatcher.post(observer, info);//通知伴生观察者
        }
        if (callback != null) {//回调方式
            posterDispatcher.post(callback, info);
        } else {//观察者模式
            observable.notifyObservers(info);
        }
    }

    private void log(int priority, int type, String format, Object... args) {
        logger.log(priority, type, String.format(Locale.US, format, args));
    }

    private void logE(int type, String format, Object... args) {
        log(Log.ERROR, type, format, args);
    }

    private void logD(int type, String format, Object... args) {
        log(Log.DEBUG, type, format, args);
    }

    private void notifyRequestFailed(GenericRequest request, int failType) {
        MethodInfo info = MethodInfoGenerator.onRequestFailed(request, failType, request.value);
        handleCallbacks(request.callback, info);
        logE(Logger.TYPE_REQUEST_FAILED, "request failed! [requestType: %s, addr: %s, failType: %d",
                request.type, device.address, failType);
    }

    private void notifyCharacteristicRead(GenericRequest request, byte[] value) {
        MethodInfo info = MethodInfoGenerator.onCharacteristicRead(request, value);
        handleCallbacks(request.callback, info);
        logD(Logger.TYPE_CHARACTERISTIC_READ, "characteristic read! [UUID: %s, addr: %s, value: %s]",
                substringUuid(request.characteristic), device.address, toHex(value));
    }

    private void notifyCharacteristicChanged(BluetoothGattCharacteristic characteristic) {
        MethodInfo info = MethodInfoGenerator.onCharacteristicChanged(device, characteristic.getService().getUuid(),
                characteristic.getUuid(), characteristic.getValue());
        observable.notifyObservers(info);
        if (observer != null) {
            posterDispatcher.post(observer, info);
        }
        logD(Logger.TYPE_CHARACTERISTIC_CHANGED, "characteristic change! [UUID: %s, addr: %s, value: %s]",
                substringUuid(characteristic.getUuid()), device.address, toHex(characteristic.getValue()));
    }

    private void notifyRssiRead(GenericRequest request, int rssi) {
        MethodInfo info = MethodInfoGenerator.onRssiRead(request, rssi);
        handleCallbacks(request.callback, info);
        logD(Logger.TYPE_READ_REMOTE_RSSI, "rssi read! [addr: %s, rssi: %d]", device.address, rssi);
    }

    private void notifyMtuChanged(GenericRequest request, int mtu) {
        MethodInfo info = MethodInfoGenerator.onMtuChanged(request, mtu);
        handleCallbacks(request.callback, info);
        logD(Logger.TYPE_MTU_CHANGED, "mtu change! [addr: %s, mtu: %d]", device.address, mtu);
    }

    private void notifyDescriptorRead(GenericRequest request, byte[] value) {
        MethodInfo info = MethodInfoGenerator.onDescriptorRead(request, value);
        handleCallbacks(request.callback, info);
        logD(Logger.TYPE_DESCRIPTOR_READ, "descriptor read! [UUID: %s, addr: %s, value: %s]",
                substringUuid(request.characteristic), device.address, toHex(value));
    }

    private void notifyNotificationChanged(GenericRequest request, boolean isEnabled) {
        MethodInfo info = MethodInfoGenerator.onNotificationChanged(request, isEnabled);
        handleCallbacks(request.callback, info);
        if (request.type == RequestType.SET_NOTIFICATION) {
            logD(Logger.TYPE_NOTIFICATION_CHANGED, "%s [UUID: %s, addr: %s]", isEnabled ? "notification enabled!" :
                    "notification disabled!", substringUuid(request.characteristic), device.address);
        } else {
            logD(Logger.TYPE_INDICATION_CHANGED, "%s [UUID: %s, addr: %s]", isEnabled ? "indication enabled!" :
                    "indication disabled!", substringUuid(request.characteristic), device.address);
        }
    }

    private void notifyCharacteristicWrite(GenericRequest request, byte[] value) {
        MethodInfo info = MethodInfoGenerator.onCharacteristicWrite(request, value);
        handleCallbacks(request.callback, info);
    }

    private void notifyPhyChange(GenericRequest request, int txPhy, int rxPhy) {
        MethodInfo info = MethodInfoGenerator.onPhyChange(request, txPhy, rxPhy);
        handleCallbacks(request.callback, info);
        String event = request.type == RequestType.READ_PHY ? "phy read!" : "phy update!";
        logD(Logger.TYPE_PHY_CHANGE, "%s [addr: %s, tvPhy: %s, rxPhy: %s]", event, device.address, txPhy, rxPhy);
    }

    @Override
    public int getMtu() {
        return mtu;
    }

    @NonNull
    @Override
    public Device getDevice() {
        return device;
    }

    @Override
    public void reconnect() {
        if (!isReleased) {
            isActiveDisconnect = false;
            tryReconnectCount = 0;
            reconnectImmediatelyCount = 0;
            Message.obtain(connHandler, MSG_DISCONNECT, MSG_ARG_RECONNECT, 0).sendToTarget();
        }
    }

    @Override
    public void disconnect() {
        if (!isReleased) {
            isActiveDisconnect = true;
            Message.obtain(connHandler, MSG_DISCONNECT, MSG_ARG_NONE, 0).sendToTarget();
        }
    }

    //清理内部缓存并强制刷新蓝牙设备的服务
    @SuppressWarnings("all")
    private boolean doRefresh() {
        try {
            Method localMethod = bluetoothGatt.getClass().getMethod("refresh");
            return (boolean) localMethod.invoke(bluetoothGatt);
        } catch (Exception ignore) {
        }
        return false;
    }

    @Override
    public void refresh() {
        connHandler.sendEmptyMessage(MSG_REFRESH);
    }

    private void release(boolean noEvent) {
        if (!isReleased) {
            isReleased = true;
            configuration.setAutoReconnect(false); //停止自动重连
            connHandler.removeCallbacksAndMessages(null);
            easyBle.removeScanListener(this);
            clearRequestQueueAndNotify();
            if (bluetoothGatt != null) {
                closeGatt(bluetoothGatt);
                bluetoothGatt = null;
            }
            device.connectionState = ConnectionState.RELEASED;
            logD(Logger.TYPE_CONNECTION_STATE, "connection released! [name: %s, addr: %s]", device.name, device.address);
            if (!noEvent) {
                sendConnectionCallback();
            }
            easyBle.releaseConnection(device);//从集合中删除
        }
    }

    @Override
    public void release() {
        release(false);
    }

    @Override
    public void releaseNoEvent() {
        release(true);
    }

    @NonNull
    @Override
    public ConnectionState getConnectionState() {
        return device.connectionState;
    }

    @Override
    public boolean isAutoReconnectEnabled() {
        return configuration.isAutoReconnect;
    }

    @Nullable
    @Override
    public BluetoothGatt getGatt() {
        return bluetoothGatt;
    }

    @Override
    public void clearRequestQueue() {
        synchronized (this) {
            requestQueue.clear();
            currentRequest = null;
        }
    }

    @Override
    public void clearRequestQueueByType(@Nullable RequestType type) {
        synchronized (this) {
            Iterator<GenericRequest> it = requestQueue.iterator();
            while (it.hasNext()) {
                GenericRequest request = it.next();
                if (request.type == type) {
                    it.remove();
                }
            }
            if (currentRequest != null && currentRequest.type == type) {
                currentRequest = null;
            }
        }
    }

    /**
     * 清空请求队列并触发通知事件
     */
    private void clearRequestQueueAndNotify() {
        synchronized (this) {
            for (GenericRequest request : requestQueue) {
                handleFailedCallback(request, REQUEST_FAIL_TYPE_CONNECTION_DISCONNECTED, false);
            }
            if (currentRequest != null) {
                handleFailedCallback(currentRequest, REQUEST_FAIL_TYPE_CONNECTION_DISCONNECTED, false);
            }
        }
        clearRequestQueue();
    }

    @NonNull
    @Override
    public ConnectionConfiguration getConnectionConfiguration() {
        return configuration;
    }

    @Nullable
    @Override
    public BluetoothGattService getService(UUID service) {
        if (service != null && bluetoothGatt != null) {
            return bluetoothGatt.getService(service);
        }
        return null;
    }

    @Nullable
    @Override
    public BluetoothGattCharacteristic getCharacteristic(UUID service, UUID characteristic) {
        if (service != null && characteristic != null && bluetoothGatt != null) {
            BluetoothGattService gattService = bluetoothGatt.getService(service);
            if (gattService != null) {
                return gattService.getCharacteristic(characteristic);
            }
        }
        return null;
    }

    @Nullable
    @Override
    public BluetoothGattDescriptor getDescriptor(UUID service, UUID characteristic, UUID descriptor) {
        if (service != null && characteristic != null && descriptor != null && bluetoothGatt != null) {
            BluetoothGattService gattService = bluetoothGatt.getService(service);
            if (gattService != null) {
                BluetoothGattCharacteristic gattCharacteristic = gattService.getCharacteristic(characteristic);
                if (gattCharacteristic != null) {
                    return gattCharacteristic.getDescriptor(descriptor);
                }
            }
        }
        return null;
    }

    //检查uuid是否存在,存在则将请求加入队列,不存在则失败回调或通知观察者
    private void checkUuidExistsAndEnqueue(GenericRequest request, int uuidNum) {
        boolean exists = false;
        if (uuidNum > 2) {
            exists = checkDescriptorExists(request, request.service, request.characteristic, request.descriptor);
        } else if (uuidNum > 1) {
            exists = checkCharacteristicExists(request, request.service, request.characteristic);
        } else if (uuidNum == 1) {
            exists = checkServiceExists(request, request.service);
        }
        if (exists) {
            enqueue(request);
        }
    }

    //检查服务是否存在
    private boolean checkServiceExists(GenericRequest request, UUID uuid) {
        if (getService(uuid) == null) {
            handleFailedCallback(request, REQUEST_FAIL_TYPE_SERVICE_NOT_EXIST, false);
            return false;
        }
        return true;
    }

    //检查特征是否存在
    private boolean checkCharacteristicExists(GenericRequest request, UUID service, UUID characteristic) {
        if (checkServiceExists(request, service)) {
            if (getCharacteristic(service, characteristic) == null) {
                handleFailedCallback(request, REQUEST_FAIL_TYPE_CHARACTERISTIC_NOT_EXIST, false);
                return false;
            }
            return true;
        }
        return false;
    }

    //检查Descriptor是否存在
    private boolean checkDescriptorExists(GenericRequest request, UUID service, UUID characteristic, UUID descriptor) {
        if (checkServiceExists(request, service) && checkCharacteristicExists(request, service, characteristic)) {
            if (getDescriptor(service, characteristic, descriptor) == null) {
                handleFailedCallback(request, REQUEST_FAIL_TYPE_DESCRIPTOR_NOT_EXIST, false);
                return false;
            }
            return true;
        }
        return false;
    }


    @Override
    public void execute(@NonNull Request request) {
        if (request instanceof GenericRequest) {
            GenericRequest req = (GenericRequest) request;
            req.device = device;
            switch (req.type) {
                case SET_NOTIFICATION:
                case SET_INDICATION:
                case READ_CHARACTERISTIC:
                case WRITE_CHARACTERISTIC:
                    if (req.type == RequestType.WRITE_CHARACTERISTIC && req.writeOptions == null) {
                        //从默认配置中取
                        req.writeOptions = configuration.getDefaultWriteOptions(req.service, req.characteristic);
                        if (req.writeOptions == null) {
                            //没有设置默认的,则新建
                            req.writeOptions = new WriteOptions.Builder().build();
                        }
                    }
                    checkUuidExistsAndEnqueue(req, 2);
                    break;
                case READ_DESCRIPTOR:
                    checkUuidExistsAndEnqueue(req, 3);
                    break;
                default:
                    enqueue(req);
                    break;
            }
        }
    }

    @Override
    public boolean isNotificationOrIndicationEnabled(@NonNull BluetoothGattCharacteristic characteristic) {
        Inspector.requireNonNull(characteristic, "characteristic can't be null");
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(clientCharacteristicConfig);
        return descriptor != null && (Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) ||
                Arrays.equals(descriptor.getValue(), BluetoothGattDescriptor.ENABLE_INDICATION_VALUE));
    }

    @Override
    public boolean isNotificationOrIndicationEnabled(UUID service, UUID characteristic) {
        BluetoothGattCharacteristic c = getCharacteristic(service, characteristic);
        if (c != null) {
            return isNotificationOrIndicationEnabled(c);
        }
        return false;
    }
}