package app.trigger.nuki;

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 static android.bluetooth.BluetoothGatt.GATT_SUCCESS;

import java.util.UUID;

import app.trigger.DoorReply.ReplyCode;
import app.trigger.Log;
import app.trigger.OnTaskCompleted;


abstract class NukiCallback extends BluetoothGattCallback {
    private static final String TAG = "NukiCallback";
    protected final OnTaskCompleted listener;
    private final UUID service_uuid;
    private final UUID characteristic_uuid;
    protected final int setup_id;

    // Client Characteristic Configuration Descriptor
    static final UUID CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    // Pairing UUIDs
    static final UUID PAIRING_SERVICE_UUID = UUID.fromString("a92ee100-5501-11e4-916c-0800200c9a66");
    static final UUID PAIRING_GDIO_XTERISTIC_UUID = UUID.fromString("a92ee101-5501-11e4-916c-0800200c9a66");

    // Keyturner UUIDs
    static final UUID KEYTURNER_SERVICE_UUID = UUID.fromString("a92ee200-5501-11e4-916c-0800200c9a66");
    static final UUID KEYTURNER_GDIO_XTERISTIC_UUID = UUID.fromString("a92ee201-5501-11e4-916c-0800200c9a66");
    static final UUID KEYTURNER_USDIO_XTERISTIC_UUID = UUID.fromString("a92ee202-5501-11e4-916c-0800200c9a66");

    NukiCallback(int setup_id, OnTaskCompleted listener, UUID service_uuid, UUID characteristic_uuid) {
        this.setup_id = setup_id;
        this.listener = listener;
        this.service_uuid = service_uuid;
        this.characteristic_uuid = characteristic_uuid;
    }

    /*
     * This is mostly called to end the connection quickly instead
     * of waiting for the other side to close the connection
    */
    protected void closeConnection(BluetoothGatt gatt) {
        gatt.close();
        NukiRequestHandler.bluetooth_in_use.set(false);
    }

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        //Log.i(TAG, "status: " + getGattStatus(status) + ", newState: " + newState);

        if (status == GATT_SUCCESS) {
            switch (newState) {
                case BluetoothProfile.STATE_CONNECTED:
                    gatt.discoverServices();
                    break;
                case BluetoothProfile.STATE_DISCONNECTED:
                    closeConnection(gatt);
                    break;
                case BluetoothProfile.STATE_CONNECTING:
                case BluetoothProfile.STATE_DISCONNECTING:
                    break;
            }
        } else {
            closeConnection(gatt);
            this.listener.onTaskResult(
                setup_id, ReplyCode.REMOTE_ERROR, "Connection error: " + NukiRequestHandler.getGattStatus(status)
            );
        }
    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (status == GATT_SUCCESS) {
            BluetoothGattService service = gatt.getService(this.service_uuid);
            if (service == null) {
                closeConnection(gatt);
                this.listener.onTaskResult(
                    setup_id, ReplyCode.REMOTE_ERROR, "Service not found: " + this.service_uuid
                );
                return;
            }

            BluetoothGattCharacteristic characteristic = service.getCharacteristic(this.characteristic_uuid);
            if (characteristic == null) {
                closeConnection(gatt);
                this.listener.onTaskResult(
                    setup_id, ReplyCode.REMOTE_ERROR, "Characteristic not found: " + this.characteristic_uuid
                );
                return;
            }

            gatt.setCharacteristicNotification(characteristic, true);
            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CCC_DESCRIPTOR_UUID);
            if (descriptor == null) {
                closeConnection(gatt);
                this.listener.onTaskResult(
                    setup_id, ReplyCode.REMOTE_ERROR, "Descriptor not found: " + CCC_DESCRIPTOR_UUID
                );
                return;
            }
           
            //Log.i(TAG, "characteristic properties: " + NukiTools.getProperties(characteristic));
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
            boolean ok = gatt.writeDescriptor(descriptor);
            if (!ok) {
                Log.e(TAG, "descriptor write failed");
                closeConnection(gatt);
            }
        } else {
            closeConnection(gatt);
            this.listener.onTaskResult(
                setup_id, ReplyCode.LOCAL_ERROR, "Client not found: " + NukiRequestHandler.getGattStatus(status)
            );
        }
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        //Log.i(TAG, "uiid: " + descriptor.getUuid() + ": " + Utils.byteArrayToHexString(descriptor.getValue()));
        if (status == GATT_SUCCESS) {
            onConnected(gatt, descriptor.getCharacteristic());
        } else {
            closeConnection(gatt);
            Log.e(TAG, "failed to write to client: " + NukiRequestHandler.getGattStatus(status));
        }
    }

    public abstract void onConnected(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic);
}