package com.wandersnail.bledemo;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;

import com.wang.avi.AVLoadingIndicatorView;

import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import cn.wandersnail.adapter.tree.Node;
import cn.wandersnail.adapter.tree.TreeListAdapter;
import cn.wandersnail.ble.Connection;
import cn.wandersnail.ble.ConnectionConfiguration;
import cn.wandersnail.ble.Device;
import cn.wandersnail.ble.EasyBLE;
import cn.wandersnail.ble.EasyBLEBuilder;
import cn.wandersnail.ble.Request;
import cn.wandersnail.ble.RequestBuilder;
import cn.wandersnail.ble.RequestBuilderFactory;
import cn.wandersnail.ble.RequestType;
import cn.wandersnail.ble.WriteCharacteristicBuilder;
import cn.wandersnail.ble.WriteOptions;
import cn.wandersnail.ble.callback.MtuChangeCallback;
import cn.wandersnail.ble.callback.NotificationChangeCallback;
import cn.wandersnail.ble.callback.ReadCharacteristicCallback;
import cn.wandersnail.commons.observer.Observe;
import cn.wandersnail.commons.poster.RunOn;
import cn.wandersnail.commons.poster.Tag;
import cn.wandersnail.commons.poster.ThreadMode;
import cn.wandersnail.commons.util.StringUtils;
import cn.wandersnail.commons.util.ToastUtils;

/**
 * date: 2019/8/2 23:33
 * author: zengfansheng
 */
public class MainActivity extends BaseActivity {
    private Device device;
    private ListView lv;
    private FrameLayout layoutConnecting;
    private AVLoadingIndicatorView loadingIndicator;
    private ImageView ivDisconnected;
    private List<Item> itemList = new ArrayList<>();
    private ListViewAdapter adapter;
    private Connection connection;

    private void assignViews() {
        lv = findViewById(R.id.lv);
        layoutConnecting = findViewById(R.id.layoutConnecting);
        loadingIndicator = findViewById(R.id.loadingIndicator);
        ivDisconnected = findViewById(R.id.ivDisconnected);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        device = getIntent().getParcelableExtra("device");
        setContentView(R.layout.activity_main);
        assignViews();
        initViews();
        //连接配置,举个例随意配置两项
        ConnectionConfiguration config = new ConnectionConfiguration();
        config.setConnectTimeoutMillis(10000);
        config.setRequestTimeoutMillis(1000);
        config.setAutoReconnect(false);
//        connection = EasyBLE.getInstance().connect(device, config, observer);//回调监听连接状态,设置此回调不影响观察者接收连接状态消息
        connection = EasyBLE.getInstance().connect(device, config);//观察者监听连接状态  
        connection.setBluetoothGattCallback(new BluetoothGattCallback() {
            @Override
            public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
                Log.d("EasyBLE", "原始写入数据:" + StringUtils.toHex(characteristic.getValue()));
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //释放连接
        EasyBLE.getInstance().releaseConnection(device);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        if (device != null && !device.isDisconnected()) {
            menu.findItem(R.id.menuDisconnect).setVisible(true);
            menu.findItem(R.id.menuConnect).setVisible(false);
        } else {
            menu.findItem(R.id.menuDisconnect).setVisible(false);
            menu.findItem(R.id.menuConnect).setVisible(true);
        }
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()) {
            case R.id.menuDisconnect:
                EasyBLE.getInstance().disconnectConnection(device);
        		break;
            case R.id.menuConnect:
                EasyBLE.getInstance().getConnection(device).reconnect();
        		break;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * 使用{@link Observe}确定要接收消息,{@link RunOn}指定在主线程执行方法,设置{@link Tag}防混淆后找不到方法
     */
    @Tag("onConnectionStateChanged")
    @Observe
    @RunOn(ThreadMode.MAIN)
    @Override
    public void onConnectionStateChanged(@NonNull Device device) {
        Log.d("EasyBLE", "主线程:" + (Looper.getMainLooper() == Looper.myLooper()) + ", 连接状态:" + device.getConnectionState());
        switch (device.getConnectionState()) {
            case SCANNING_FOR_RECONNECTION:
                ivDisconnected.setVisibility(View.INVISIBLE);
                break;
            case CONNECTING:
                layoutConnecting.setVisibility(View.VISIBLE);
                loadingIndicator.setVisibility(View.VISIBLE);
                ivDisconnected.setVisibility(View.INVISIBLE);
                break;
            case DISCONNECTED:
                layoutConnecting.setVisibility(View.VISIBLE);
                loadingIndicator.setVisibility(View.INVISIBLE);
                ivDisconnected.setVisibility(View.VISIBLE);
                break;
            case SERVICE_DISCOVERED:
                layoutConnecting.setVisibility(View.INVISIBLE);
                loadingIndicator.setVisibility(View.INVISIBLE);
                itemList.clear();
                int id = 0;
                List<BluetoothGattService> services = connection.getGatt().getServices();
                for (BluetoothGattService service : services) {
                    int pid = id;
                    Item item = new Item(pid, 0, 0);
                    item.isService = true;
                    item.service = service;
                    itemList.add(item);
                    id++;
                    List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
                    for (BluetoothGattCharacteristic characteristic : characteristics) {
                        Item i = new Item(id++, pid, 1);
                        i.service = service;
                        i.characteristic = characteristic;
                        itemList.add(i);
                    }
                }
                adapter.notifyDataSetChanged();
                //设置MTU
                Connection connection = EasyBLE.getInstance().getConnection(device);
                RequestBuilder<MtuChangeCallback> builder = new RequestBuilderFactory().getChangeMtuBuilder(503);
                Request request = builder.setCallback(new MtuChangeCallback() {
                    @Override
                    public void onMtuChanged(@NonNull Request request, int mtu) {
                        Log.d("EasyBLE", "MTU修改成功,新值:" + mtu);
                    }

                    @Override
                    public void onRequestFailed(@NonNull Request request, int failType, @Nullable Object value) {

                    }
                }).build();
                connection.execute(request);
                break;
        }
        invalidateOptionsMenu();
    }


    /**
     * 使用{@link Observe}确定要接收消息,方法在{@link EasyBLEBuilder#setMethodDefaultThreadMode(ThreadMode)}指定的线程执行
     */
    @Observe
    @Override
    public void onNotificationChanged(@NonNull Request request, boolean isEnabled) {
        Log.d("EasyBLE", "主线程:" + (Looper.getMainLooper() == Looper.myLooper()) + ", 通知/Indication:" + (isEnabled ? "开启" : "关闭"));
        if (request.getType() == RequestType.SET_NOTIFICATION) {
            if (isEnabled) {
                ToastUtils.showShort("通知开启了");
            } else {
                ToastUtils.showShort("通知关闭了");
            }
        } else {
            if (isEnabled) {
                ToastUtils.showShort("Indication开启了");
            } else {
                ToastUtils.showShort("Indication关闭了");
            }
        }
    }

    /**
     * 如果{@link EasyBLEBuilder#setObserveAnnotationRequired(boolean)}设置为false时,无论加不加{@link Observe}注解都会收到消息。
     * 设置为true时,必须加{@link Observe}才会收到消息。
     * 默认为false,方法默认执行线程在{@link EasyBLEBuilder#setMethodDefaultThreadMode(ThreadMode)}指定
     */
    @Override
    public void onCharacteristicWrite(@NonNull Request request, @NonNull byte[] value) {
        Log.d("EasyBLE", "主线程:" + (Looper.getMainLooper() == Looper.myLooper()) + ", 成功写入:" + StringUtils.toHex(value, " "));
        ToastUtils.showShort("成功写入:" + StringUtils.toHex(value, " "));
    }

    private void initViews() {
        adapter = new ListViewAdapter(lv, itemList);
        adapter.setOnInnerItemClickListener((item, adapterView, view, i) -> {
            final List<String> menuItems = new ArrayList<>();
            if (item.hasNotifyProperty) {
                menuItems.add("开关通知");
            }
            if (item.hasReadProperty) {
                menuItems.add("读取特征值");
            }
            if (item.hasWriteProperty) {
                menuItems.add("写入测试数据");
            }
            new AlertDialog.Builder(MainActivity.this)
                    .setItems(menuItems.toArray(new String[0]), (dialog, which) -> {
                        dialog.dismiss();
                        switch (menuItems.get(which)) {
                            case "开关通知":
                                setNotification(item);
                                break;
                            case "读取特征值":
                                readCharacteristic(item);
                                break;
                            default:
                                writeCharacteristic(item);
                                break;
                        }
                    })
                    .show();
        });
    }

    private void writeCharacteristic(@NotNull Item item) {
        Log.d("EasyBLE", "开始写入");
        WriteCharacteristicBuilder builder = new RequestBuilderFactory().getWriteCharacteristicBuilder(item.service.getUuid(), 
                item.characteristic.getUuid(), ("Multi-pass deformation also shows that in high-temperature rolling process, " +
                        "the material will be softened as a result of the recovery and recrystallization, " +
                        "so the rolling force is reduced and the time interval of the passes of rough rolling should be longer." +
                        "Multi-pass deformation also shows that in high-temperature rolling process, " +
                        "the material will be softened as a result of the recovery and recrystallization, " +
                        "so the rolling force is reduced and the time interval of the passes of rough rolling should be longer.").getBytes());
        //根据需要设置写入配置
        int writeType = connection.hasProperty(item.service.getUuid(), item.characteristic.getUuid(), 
                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) ? 
                BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE : BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT;
        builder.setWriteOptions(new WriteOptions.Builder()
                .setPackageSize(connection.getMtu() - 3)
                .setPackageWriteDelayMillis(5)
                .setRequestWriteDelayMillis(10)
                .setWaitWriteResult(true)
                .setWriteType(writeType)
                .build());
        //不设置回调,使用观察者模式接收结果
        builder.build().execute(connection);
    }

    private void readCharacteristic(@NotNull Item item) {
        RequestBuilder<ReadCharacteristicCallback> builder = new RequestBuilderFactory().getReadCharacteristicBuilder(item.service.getUuid(), item.characteristic.getUuid());
        builder.setTag(UUID.randomUUID().toString());
        builder.setPriority(Integer.MAX_VALUE);//设置请求优先级
        //设置了回调则观察者不会收到此次请求的结果消息
        builder.setCallback(new ReadCharacteristicCallback() {
            //注解可以指定回调线程
            @RunOn(ThreadMode.BACKGROUND)
            @Override
            public void onCharacteristicRead(@NonNull Request request, @NonNull byte[] value) {
                Log.d("EasyBLE", "主线程:" + (Looper.getMainLooper() == Looper.myLooper()) + ", 读取到特征值:" + StringUtils.toHex(value, " "));
                ToastUtils.showShort("读取到特征值:" + StringUtils.toHex(value, " "));
            }

            //不使用注解指定线程的话,使用构建器设置的默认线程
            @Override
            public void onRequestFailed(@NonNull Request request, int failType, @Nullable Object value) {

            }
        });
        builder.build().execute(connection);
    }

    private void setNotification(@NotNull Item item) {
        boolean isEnabled = connection.isNotificationOrIndicationEnabled(item.service.getUuid(), item.characteristic.getUuid());
        RequestBuilder<NotificationChangeCallback> builder = new RequestBuilderFactory().getSetNotificationBuilder(item.service.getUuid(), item.characteristic.getUuid(), !isEnabled);
        //不设置回调,使用观察者模式接收结果
        builder.build().execute(connection);
    }

    private class ListViewAdapter extends TreeListAdapter<Item> {
        ListViewAdapter(@NotNull ListView lv, @NotNull List<Item> nodes) {
            super(lv, nodes);
        }

        @NotNull
        @Override
        protected Holder<Item> getHolder(int i) {
            //根据位置返回不同布局
            int type = getItemViewType(i);
            if (type == 1) {//服务
                return new Holder<Item>() {
                    private ImageView iv;
                    private TextView tvUuid;

                    @Override
                    public void onBind(Item item, int position) {
                        iv.setVisibility(item.hasChild() ? View.VISIBLE : View.INVISIBLE);
                        iv.setBackgroundResource(item.isExpand() ? R.drawable.expand : R.drawable.fold);
                        tvUuid.setText(item.service.getUuid().toString());
                    }

                    @NotNull
                    @Override
                    public View createView() {
                        View view = View.inflate(MainActivity.this, R.layout.item_service, null);
                        iv = view.findViewById(R.id.ivIcon);
                        tvUuid = view.findViewById(R.id.tvUuid);
                        return view;
                    }
                };
            } else {
                return new Holder<Item>() {
                    private TextView tvUuid;
                    private TextView tvProperty;

                    @Override
                    public void onBind(Item item, int i) {
                        tvUuid.setText(item.characteristic.getUuid().toString());
                        //获取权限列表
                        tvProperty.setText(getPropertiesString(item));
                    }

                    @NotNull
                    @Override
                    public View createView() {
                        View view = View.inflate(MainActivity.this, R.layout.item_characteristic, null);
                        tvUuid = view.findViewById(R.id.tvUuid);
                        tvProperty = view.findViewById(R.id.tvProperty);
                        return view;
                    }
                };
            }
        }

        private String getPropertiesString(Item node) {
            StringBuilder sb = new StringBuilder();
            int[] properties = new int[]{BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PROPERTY_INDICATE,
                    BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PROPERTY_READ,
                    BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE};
            String[] propertyStrs = new String[]{"WRITE", "INDICATE", "NOTIFY", "READ", "SIGNED_WRITE", "WRITE_NO_RESPONSE"};
            for (int i = 0; i < properties.length; i++) {
                int property = properties[i];
                if ((node.characteristic.getProperties() & property) != 0) {
                    if (sb.length() != 0) {
                        sb.append(", ");
                    }
                    sb.append(propertyStrs[i]);
                    if (property == BluetoothGattCharacteristic.PROPERTY_NOTIFY || property == BluetoothGattCharacteristic.PROPERTY_INDICATE) {
                        node.hasNotifyProperty = true;
                    }
                    if (property == BluetoothGattCharacteristic.PROPERTY_WRITE || property == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) {
                        node.hasWriteProperty = true;
                    }
                    if (property == BluetoothGattCharacteristic.PROPERTY_READ) {
                        node.hasReadProperty = true;
                    }
                }
            }
            return sb.toString();
        }

        @Override
        public int getViewTypeCount() {
            return super.getViewTypeCount() + 1;
        }

        @Override
        public int getItemViewType(int position) {
            return getItem(position).isService ? 1 : 0;
        }
    }

    private class Item extends Node<Item> {
        boolean isService;
        BluetoothGattService service;
        BluetoothGattCharacteristic characteristic;
        boolean hasNotifyProperty;
        boolean hasWriteProperty;
        boolean hasReadProperty;

        Item(int id, int pId, int level) {
            super(id, pId, level);
        }
    }
}