package com.common.bluetooth.service;

import static com.common.bluetooth.BtConstants.CONNECT_SUCCESS;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.common.bluetooth.BtConstants;
import com.common.bluetooth.BtUtils;
import com.common.bluetooth.adapter.BluetoothClientBLEAdapter;
import com.common.bluetooth.bean.CommonMsg;
import com.common.bluetooth.callback.BLEClientListener;
import com.common.bluetooth.callback.BaseResultCallback;
import com.common.bluetooth.callback.BtConnectListener;
import com.common.bluetooth.interfaces.IBluetoothClient;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

/**
 * BLE客户端服务
 *
 * @author wangym
 * @since 2021-10-29
 */
public class BLEClientService extends Service {
    private static final String TAG = "BLEClientService";

    /**
     * 空闲
     */
    private static final int WRITE_STATUS_IDLE = 1;
    /**
     * 传输中
     */
    private static final int WRITE_STATUS_WRITING = 2;

    /**
     * BLE客户端
     */
    private IBluetoothClient mBtClient;

    /**
     * 连接的MAC
     */
    private String connectMac;

    /**
     * 客户端事件监听器
     */
    private BLEClientListener clientListener;

    /**
     * 消息队列
     */
    private final LinkedBlockingQueue<byte[]> msgQueue = new LinkedBlockingQueue<>();

    /**
     * 是否开启通知
     */
    private final AtomicBoolean isEnableNotify = new AtomicBoolean(true);

    /**
     * 传输状态
     */
    private final AtomicInteger writeStatus = new AtomicInteger(WRITE_STATUS_IDLE);

    @Override
    public void onCreate() {
        super.onCreate();

        mBtClient = new BluetoothClientBLEAdapter(BluetoothLeClient.getInstance(this));
        mBtClient.checkBluetoothDevice(BtConstants.BLUETOOTH_TYPE.BLE);
        mBtClient.openBluetooth();
    }

    /**
     * 连接服务端设备
     *
     * @param mac          MAC地址
     * @param enableNotify 是否注册通知
     */
    public void connect(String mac, boolean enableNotify) {
        connect(mac, enableNotify, null);
    }

    /**
     * 通知回调
     */
    private final BaseResultCallback<byte[]> notifyCallback = new BaseResultCallback<byte[]>() {
        @Override
        public void onSuccess(byte[] data) {
            Log.d(TAG, "got notify onSuccess = " + new String(data));
            if (clientListener != null) {
                clientListener.onNotifyMsgReceive(data);
            }
        }

        @Override
        public void onFail(String msg) {
            Log.d(TAG, "got notify onFail = " + msg);
        }
    };

    /**
     * 连接服务端设备
     *
     * @param mac             MAC地址
     * @param enableNotify    是否注册通知
     * @param connectListener 链接回调
     */
    public void connect(String mac, boolean enableNotify, BtConnectListener connectListener) {
        this.connectMac = mac;
        isEnableNotify.set(enableNotify);
        if (TextUtils.isEmpty(connectMac)) {
            connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this);
        }
        Observable<String>[] observables = new Observable[2];
        observables[0] = mBtClient.connect(connectMac);
        // 判断是否需要启动通知
        if (enableNotify) {
            observables[1] = mBtClient.registerNotify(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(),
                    BtConstants.INSTANCE.getUUID_CHARACTERISTIC_NOTIFY(), notifyCallback);
        }
        Observable.concatArray(observables).subscribe(new Observer<String>() {
            @Override
            public void onSubscribe(@NonNull Disposable d) {
                Log.d(TAG, "connect onSubscribe");
            }

            @Override
            public void onNext(@NonNull String value) {
                Log.d(TAG, String.format("connect onNext: %s", value));
            }

            @Override
            public void onError(@NonNull Throwable e) {
                Log.e(TAG, "connect onError: ", e);
                // 链接过程报错，断开链接
                mBtClient.disconnect(connectMac);
                connectMac = "";

                if (clientListener != null) {
                    clientListener.onResult(new CommonMsg(BtConstants.CONNECT_ERROR, e.getMessage()));
                }
                if (connectListener != null) {
                    connectListener.onFail(e.getMessage());
                }
            }

            @Override
            public void onComplete() {
                Log.d(TAG, "connect onComplete");
                // 将链接成功的MAC地址保存
                BtUtils.INSTANCE.saveConnectMac(BLEClientService.this, mac);
                if (clientListener != null) {
                    clientListener.onResult(new CommonMsg(CONNECT_SUCCESS, mac));
                }
                if (connectListener != null) {
                    connectListener.onSuccess();
                }
            }
        });
    }

    /**
     * 传输内容
     *
     * @param msg 内容
     */
    public void write(@NonNull final byte[] msg) {
        if (mBtClient == null) {
            Log.e(TAG, "write msg mBtClient got null");
            return;
        }
        if (TextUtils.isEmpty(connectMac)) {
            connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this);
            if (TextUtils.isEmpty(connectMac)) {
                Log.e(TAG, "write msg connectMac got null");
                return;
            }
        }
        Log.d(TAG, "add msg to queue : " + new String(msg));

        // 将字符串添加到队列中
        msgQueue.add(msg);
        doTranslate();
    }

    /**
     * 进行数据传输
     */
    private void doTranslate() {
        // 判断状态
        if (writeStatus.get() == WRITE_STATUS_WRITING) {
            Log.d(TAG, "doTranslate is writing, return");
            return;
        }
        synchronized (BLEClientService.class) {
            writeStatus.set(WRITE_STATUS_WRITING);
            // 从队列中获取数据
            final byte[] msg = msgQueue.poll();
            if (msg == null) {
                Log.d(TAG, "doTranslate Queue is null");
                writeStatus.set(WRITE_STATUS_IDLE);
                return;
            }
            Log.d(TAG, "doTranslate start translate : " + new String(msg));
            // 检查需要发送的内容是否超过了限额
            int size = msg.length / BtConstants.MAX_MTU;
            if (msg.length % BtConstants.MAX_MTU != 0) {
                size++;
            }
            Observable<String>[] observables = new Observable[size];
            for (int index = 0; index < size; index++) {
                byte[] temp;
                int arrSize;
                if (index == size - 1) {
                    arrSize = msg.length - (index * BtConstants.MAX_MTU);
                } else {
                    arrSize = BtConstants.MAX_MTU;
                }
                temp = new byte[arrSize];
                System.arraycopy(msg, index * BtConstants.MAX_MTU, temp, 0, arrSize);
                Observable<String> observable = mBtClient.write(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(),
                        BtConstants.INSTANCE.getUUID_CHARACTERISTIC_WRITE(),
                        temp);
                observables[index] = observable;
            }

            Observable.concatArray(observables).subscribe(new Observer<String>() {
                @Override
                public void onSubscribe(@NonNull Disposable d) {
                }

                @Override
                public void onNext(@NonNull String s) {
                    Log.d(TAG, String.format("write onNext %s", s));
                }

                @Override
                public void onError(@NonNull Throwable e) {
                    Log.e(TAG, "write onError: " + e.getMessage());
                    if (BtConstants.EXCEPTION.NOT_CONNECTED.name().equals(e.getMessage())) {
                        // 写时发现，链接断开，则尝试重连
                        connect(connectMac, isEnableNotify.get(), new BtConnectListener() {
                            @Override
                            public void onSuccess() {
                                // 将数据重新放入队列后再次传输
                                msgQueue.add(msg);

                                writeStatus.set(WRITE_STATUS_IDLE);
                                doTranslate();
                            }

                            @Override
                            public void onFail(@NonNull String error) {
                                if (clientListener != null) {
                                    clientListener.onResult(new CommonMsg(BtConstants.WRITE_ERROR, error));
                                }
                            }
                        });
                    } else {
                        if (clientListener != null) {
                            clientListener.onResult(new CommonMsg(BtConstants.WRITE_ERROR, e.getMessage()));
                        }
                    }
                    writeStatus.set(WRITE_STATUS_IDLE);
                }

                @Override
                public void onComplete() {
                    Log.d(TAG, "write onComplete");
                    if (clientListener != null) {
                        clientListener.onResult(new CommonMsg(BtConstants.WRITE_SUCCESS, "write success"));
                    }
                    writeStatus.set(WRITE_STATUS_IDLE);
                    Log.d(TAG, "doTranslate end translate : " + msg);
                    doTranslate();
                }
            });
        }
    }

    public void setClientListener(BLEClientListener clientListener) {
        this.clientListener = clientListener;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new BLEClientBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        if (mBtClient != null && !TextUtils.isEmpty(connectMac)) {
            mBtClient.disconnect(connectMac);
        }
        return super.onUnbind(intent);
    }

    public class BLEClientBinder extends Binder {
        public BLEClientService getService() {
            return BLEClientService.this;
        }
    }
}
