package com.common.bluetooth.adapter;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.le.ScanResult;
import android.os.HandlerThread;
import android.util.Log;

import androidx.annotation.NonNull;

import com.common.bluetooth.BtConstants;
import com.common.bluetooth.callback.BaseResultCallback;
import com.common.bluetooth.callback.BtScanCallBack;
import com.common.bluetooth.exception.BluetoothException;
import com.common.bluetooth.exception.BluetoothNotOpenException;
import com.common.bluetooth.exception.BluetoothSearchConflictException;
import com.common.bluetooth.exception.BluetoothWriteException;
import com.common.bluetooth.interfaces.IBluetoothClient;
import com.common.bluetooth.interfaces.IBluetoothSearch;
import com.common.bluetooth.service.BluetoothLeClient;
import com.common.bluetooth.service.BluetoothLeConnector;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;

/**
 * 蓝牙设备适配器
 *
 * @author wangym
 * @since 2021-10-19
 */
public class BluetoothClientBLEAdapter implements IBluetoothClient {
    private static final String TAG = "BluetoothClient";

    private final BluetoothLeClient mClient;

    /**
     * 监听通知的回调
     * adapter不是单例导致需要共享回调，保证不同线程创建的都能正常使用
     */
    private volatile static BaseResultCallback<byte[]> notifyCallback = null;

    public BluetoothClientBLEAdapter(BluetoothLeClient client) {
        mClient = client;

        HandlerThread workThread = new HandlerThread("bluetooth ble worker");
        workThread.start();
    }

    @Override
    public Observable<BluetoothDevice> search(final int millis, final boolean cancel) {
        return Observable.create(new ObservableOnSubscribe<BluetoothDevice>() {
            @Override
            public void subscribe(@NonNull final ObservableEmitter<BluetoothDevice> emitter) {
                IBluetoothSearch searcher = mClient.getBluetoothSearcher();

                if (searcher.isScanning() && !cancel) {
                    emitter.onError(new BluetoothSearchConflictException("is searching now"));
                    return;
                }

                if (searcher.isScanning()) {
                    stopSearch();
                }

                mClient.getBluetoothSearcher()
                        .startScan(millis, new BtScanCallBack() {
                            private final Set<BluetoothDevice> devices = new HashSet<>();

                            @Override
                            public void onResult(ScanResult result) {
                                BluetoothDevice device = result.getDevice();
                                if (devices.contains(device)) {
                                    return;
                                }

                                devices.add(device);
                                emitter.onNext(device);
                            }

                            @Override
                            public void onComplete() {
                                emitter.onComplete();
                            }

                            @Override
                            public void onError(String msg) {
                                emitter.onError(new BluetoothNotOpenException(msg));
                            }
                        });
            }
        });
    }

    @Override
    public void stopSearch() {
        mClient.getBluetoothSearcher().stopScan();
    }

    @NonNull
    @Override
    public Observable<String> connect(final String mac) {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(@NonNull final ObservableEmitter<String> emitter) {
                BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);

                connector.connect(new BluetoothLeConnector.OnConnectListener() {
                    @Override
                    public void onConnect() {
                    }

                    @Override
                    public void onDisconnect() {
                    }

                    @Override
                    public void onServiceDiscover() {
                        emitter.onNext(mac);
                        emitter.onComplete();
                    }

                    @Override
                    public void onError(String msg) {
                    }
                });
            }
        });
    }

    @Override
    public void disconnect(@NonNull String mac) {
        mClient.getBluetoothLeConnector(mac).disconnect();
    }

    @Override
    public Observable<String> write(@NonNull final String mac, @NonNull final UUID service,
                                    @NonNull final UUID characteristic,
                                    @NonNull final byte[] values) {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(@NonNull final ObservableEmitter<String> emitter) {
                BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);

                connector.setOnDataAvailableListener(new BluetoothLeConnector.OnDataAvailableListener() {
                    @Override
                    public void onCharacteristicRead(byte[] values, int status) {
                    }

                    @Override
                    public void onCharacteristicChange(UUID characteristic, byte[] values) {
                        if (notifyCallback != null) {
                            notifyCallback.onSuccess(values);
                        }
                    }

                    @Override
                    public void onCharacteristicWrite(UUID characteristic, int status) {
                        emitter.onNext(mac);
                        emitter.onComplete();
                    }

                    @Override
                    public void onDescriptorWrite(UUID descriptor, int status) {
                    }

                    @Override
                    public void onError(BtConstants.EXCEPTION msg) {
                        Log.e(TAG, "write got error, msg = " + msg);
                        emitter.onError(new Throwable(msg.name()));
                    }
                });
                connector.writeCharacteristic(service, characteristic, values);
            }
        });
    }

    public void setNotifyCallback(BaseResultCallback<byte[]> notifyCallback) {
        this.notifyCallback = notifyCallback;
    }

    @Override
    public Observable<String> registerNotify(@NonNull final String mac, @NonNull final UUID service,
                                             @NonNull final UUID characteristic,
                                             final BaseResultCallback<byte[]> callback) {
        return Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(@NonNull final ObservableEmitter<String> emitter) {
                notifyCallback = callback;
                BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);
                connector.setOnDataAvailableListener(new BluetoothLeConnector.OnDataAvailableListener() {
                    @Override
                    public void onCharacteristicRead(byte[] values, int status) {
                    }

                    @Override
                    public void onCharacteristicChange(UUID characteristic, byte[] values) {
                        if (notifyCallback != null) {
                            notifyCallback.onSuccess(values);
                        }
                    }

                    @Override
                    public void onCharacteristicWrite(UUID cha, int status) {
                    }

                    @Override
                    public void onDescriptorWrite(UUID descriptor, int status) {
                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            Log.d(TAG, "registerNotify pass");
                            emitter.onNext(mac);
                            emitter.onComplete();
                        } else {
                            String err = "write exception mac " + mac + " with " + status;
                            Log.e(TAG, err);
                            emitter.onError(new BluetoothWriteException(err, mac));
                        }
                    }

                    @Override
                    public void onError(BtConstants.EXCEPTION msg) {
                        emitter.onError(new BluetoothException(msg.name()));
                    }
                });
                connector.setCharacteristicNotification(service, characteristic, true);
            }
        });
    }

    @Override
    public Observable<String> unRegisterNotify(@NonNull String mac, @NonNull UUID service,
                                               @NonNull UUID characteristic) {
        return null;
    }

    @Override
    public void clean(@NonNull String mac) {
        mClient.cleanConnector(mac);
    }

    @Override
    public void cleanAll() {
        mClient.cleanAllConnector();
    }

    @Override
    public boolean checkBluetoothDevice(@NonNull BtConstants.BLUETOOTH_TYPE type) {
        return mClient.checkBtDevice(type);
    }

    @Override
    public void openBluetooth() {
        mClient.openBt();
    }

    @Override
    public boolean closeBluetooth() {
        return mClient.closeBt();
    }

    @Override
    public Set<BluetoothDevice> getBondedDevices() {
        return mClient.getBondedDevices();
    }
}
