package nodomain.freeyourgadget.gadgetbridge.btle;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.util.Log;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import nodomain.freeyourgadget.gadgetbridge.AbstractDeviceSupport;

/**
 * @see TransactionBuilder
 * @see BtLEQueue
 */
public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport implements GattCallback {
    private static final String TAG = "AbstractBTLEDeviceSupport";

    private BtLEQueue mQueue;
    private HashMap<UUID, BluetoothGattCharacteristic> mAvailableCharacteristics;
    private Set<UUID> mSupportedServices = new HashSet<>(4);

    @Override
    public boolean connect() {
        if (mQueue == null) {
            mQueue = new BtLEQueue(getBluetoothAdapter(), getDevice(), this, getContext());
        }
        return mQueue.connect();
    }

    /**
     * Subclasses should populate the given builder to initialize the device (if necessary).
     *
     * @param builder
     * @return the same builder as passed as the argument
     */
    protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
        return builder;
    }

    @Override
    public void dispose() {
        if (mQueue != null) {
            mQueue.dispose();
        }
    }

    /**
     * Send commands like this to the device:
     * <p>
     * <code>perform("sms notification").write(someCharacteristic, someByteArray).queue(getQueue());</code>
     * </p>
     * TODO: support orchestration of multiple reads and writes depending on returned values
     *
     * @see #performConnected(Transaction)
     * @see #initializeDevice(TransactionBuilder)
     */
    protected TransactionBuilder performInitialized(String taskName) throws IOException {
        if (!isConnected()) {
            if (!connect()) {
                throw new IOException("1: Unable to connect to device: " + getDevice());
            }
        }
        if (!isInitialized()) {
            // first, add a transaction that performs device initialization
            TransactionBuilder builder = new TransactionBuilder("Initialize device");
            initializeDevice(builder).queue(getQueue());
        }
        return new TransactionBuilder(taskName);
    }

    /**
     * @param transaction
     * @throws IOException
     * @see {@link #performInitialized(String)}
     */
    protected void performConnected(Transaction transaction) throws IOException {
        if (!isConnected()) {
            if (!connect()) {
                throw new IOException("2: Unable to connect to device: " + getDevice());
            }
        }
        getQueue().add(transaction);
    }

    public BtLEQueue getQueue() {
        return mQueue;
    }

    /**
     * Subclasses should call this method to add services they support.
     * Only supported services will be queried for characteristics.
     *
     * @param aSupportedService
     * @see #getCharacteristic(UUID)
     */
    protected void addSupportedService(UUID aSupportedService) {
        mSupportedServices.add(aSupportedService);
    }

    /**
     * Returns the characteristic matching the given UUID. Only characteristics
     * are returned whose service is marked as supported.
     *
     * @param uuid
     * @return the characteristic for the given UUID or <code>null</code>
     * @see #addSupportedService(UUID)
     */
    protected BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
        if (mAvailableCharacteristics == null) {
            return null;
        }
        return mAvailableCharacteristics.get(uuid);
    }

    private void gattServicesDiscovered(List<BluetoothGattService> discoveredGattServices) {
        mAvailableCharacteristics = null;

        if (discoveredGattServices == null) {
            return;
        }
        Set<UUID> supportedServices = getSupportedServices();

        for (BluetoothGattService service : discoveredGattServices) {
            if (supportedServices.contains(service.getUuid())) {
                List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
                if (characteristics == null || characteristics.isEmpty()) {
                    Log.w(TAG, "Supported LE service " + service.getUuid() + "did not return any characteristics");
                    continue;
                }
                mAvailableCharacteristics = new HashMap<>(characteristics.size());
                for (BluetoothGattCharacteristic characteristic : characteristics) {
                    mAvailableCharacteristics.put(characteristic.getUuid(), characteristic);
                }
            }
        }
    }

    protected Set<UUID> getSupportedServices() {
        return mSupportedServices;
    }

    // default implementations of event handler methods (gatt callbacks)
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt) {
        gattServicesDiscovered(getQueue().getSupportedGattServices());
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt,
                                     BluetoothGattCharacteristic characteristic, int status) {
    }

    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt,
                                      BluetoothGattCharacteristic characteristic, int status) {
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,
                                        BluetoothGattCharacteristic characteristic) {
    }

    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
    }
}

@@ -49,175 +49,8 @@
 e;%0A%0A
-import android.bluetooth.BluetoothGatt;%0Aimport android.bluetooth.BluetoothGattCharacteristic;%0Aimport android.bluetooth.BluetoothGattService;%0Aimport android.util.Log;%0A%0A
 impo
@@ -260,16 +260,225 @@
 Support;
+%0Aimport android.bluetooth.BluetoothGatt;%0Aimport android.bluetooth.BluetoothGattCharacteristic;%0Aimport android.bluetooth.BluetoothGattService;%0Aimport android.bluetooth.BluetoothProfile;%0Aimport android.util.Log;
 %0A%0A/**%0A *
@@ -5294,24 +5294,115 @@
 ervices());%0A
+        initializeDevice(new TransactionBuilder(%22Initializing device%22)).queue(getQueue());%0A
     %7D%0A%0A    @
@@ -5903,32 +5903,46 @@
 istic) %7B%0A    %7D%0A%0A
+    @Override%0A
     public void 

Initialize the BTLE device after the services are discovered.