//
// © 2019-2020 Qualcomm Technologies, Inc. and/or its subsidiaries. All rights reserved.
//

#import "IoTBluetoothState.h"
#import "IoTGlobals.h"
#import "IoTWrappedRepresentation.h"
#import "IoTResourceAttributes.h"
#import "IoTProperties.h"

@interface IoTBluetoothState()

@property (nonatomic, strong, readonly) dispatch_queue_t syncQueue;
@property (nonatomic, copy, readwrite, nullable) IoTBluetoothAttr *btAttribute;
@property (nonatomic, assign, readwrite) IoTBluetoothAdapterState adapterStateInternal;
@property (nonatomic, copy, readwrite, nullable) NSString *connectedBTDeviceAddressInternal;
@property (nonatomic, assign, readwrite) BOOL scanningInternal;
@property (nonatomic, copy, readwrite) NSArray<IoTBtDeviceStatusAttr *> *pairedDevicesInternal;

@end

@implementation IoTBluetoothState

- (instancetype)init
{
    self = [super init];
    if (self) {
        // We use a separate queue to prevent deadlock.
        self->_syncQueue = dispatch_queue_create("com.qualcomm.qti.IoTBluetoothState.syncQueue", DISPATCH_QUEUE_CONCURRENT);
        self->_adapterStateInternal = IoTBluetoothAdapterStateUnknown;
        self->_connectedBTDeviceAddressInternal = nil;
        self->_scanningInternal = NO;
        self->_pairedDevicesInternal = @[];
    }
    return self;
}

- (void)updateWithAttribute:(IoTBluetoothAttr *)btAttr
{
    makeWeak(self)
    dispatch_barrier_async(self.syncQueue, ^{
        makeStrongAndReturnIfNil(self)
        self.btAttribute = [btAttr copy];
        if (btAttr) {
            self.adapterStateInternal = self.btAttribute.adapterState;
            self.scanningInternal = NO;
            self.pairedDevicesInternal = self.btAttribute.pairedDevices;
        }
    });
}

- (NSArray<NSString *> *)updateWithWrapper:(IoTWrappedRepresentation *)wrapper
{
    NSArray *keys = [wrapper keys];
    NSMutableArray<NSString *> *moddedKeys = [keys mutableCopy];

    dispatch_assert_queue_not(self.syncQueue);
    dispatch_sync(self.syncQueue, ^{
        [self.btAttribute unpack:wrapper];

        // We need to infer some state from other responses.
		if ([keys containsObject:[NSString stringWithUTF8String:kAdapterState]]) {
            if (self.btAttribute.adapterState != IoTBluetoothAdapterStateUnknown) {
                self.adapterStateInternal = self.btAttribute.adapterState;
                if (self.btAttribute.adapterState == IoTBluetoothAdapterStateDisabled) {
                    self.scanningInternal = NO;
                }
            }
        }
        if ([keys containsObject:[NSString stringWithUTF8String:kConnectedState]]) {
            if (self.btAttribute.connectedState.status) {
            	self.connectedBTDeviceAddressInternal = self.btAttribute.connectedState.address;
            } else {
                self.connectedBTDeviceAddressInternal = nil;
            }
        }
        if ([keys containsObject:[NSString stringWithUTF8String:kPairedDevices]]) {
            self.pairedDevicesInternal = [self.btAttribute.pairedDevices copy];
            if (self.adapterStateInternal == IoTBluetoothAdapterStateUnknown) {
                self.adapterStateInternal = IoTBluetoothAdapterStateEnabled;
                [moddedKeys addObject:[NSString stringWithUTF8String:kAdapterState]];
            }
        }
        if ([keys containsObject:[NSString stringWithUTF8String:kScanResult]]) {
            self.scanningInternal = YES;
        }
        if ([keys containsObject:[NSString stringWithUTF8String:kSignal]]) {
            if (self.btAttribute.signal.name == IoTNameScanStopped) {
            	self.scanningInternal = NO;
            } else if (self.btAttribute.signal.name == IoTNamePairedListCleared) {
                self.pairedDevicesInternal = @[];
                [moddedKeys addObject:[NSString stringWithUTF8String:kPairedDevices]];
            }
        }
        if ([keys containsObject:[NSString stringWithUTF8String:kError]]) {
            [self handleBTError: moddedKeys];
        }
    });

    return moddedKeys;
}

- (void)handleBTError:(NSMutableArray<NSString *> *)keys;
{
    dispatch_assert_queue(self.syncQueue);

    NSLog (@"BT Error: %@", self.btAttribute.errorReason.status);
    NSString *errorReason = self.btAttribute.errorReason.status;
    if ([errorReason isEqualToString:@"bt_not_enabled"] ||
        [errorReason isEqualToString:@"set_discoverable_bt_not_enabled"] ||
        [errorReason isEqualToString:@"exit_discoverable_bt_not_enabled"]) {
        self.adapterStateInternal = IoTBluetoothAdapterStateDisabled;
        self.pairedDevicesInternal = @[];
        [keys addObject:[NSString stringWithUTF8String:kAdapterState]];
        [keys addObject:[NSString stringWithUTF8String:kPairedDevices]];
    } else if ([errorReason isEqualToString:@"device_already_paired"] ||
               [errorReason isEqualToString:@"device_not_paired"]) {
        // adding pairedState to keys triggers a refetch of pairedDevices.
        [keys addObject:[NSString stringWithUTF8String:kPairedState]];
    } else if ([errorReason isEqualToString:@"device_already_connected"]) {
        self.connectedBTDeviceAddressInternal = self.btAttribute.errorReason.address;
        [keys addObject:[NSString stringWithUTF8String:kConnectedState]];
    } else if ([errorReason isEqualToString:@"device_not_connected"]) {
        if ([self.connectedBTDeviceAddressInternal isEqualToString:self.btAttribute.errorReason.address]) {
            self.connectedBTDeviceAddressInternal = nil;
            [keys addObject:[NSString stringWithUTF8String:kConnectedState]];
        }
    }
    /*    set_discoverable_bt_not_enabled
     make_discoverable_invalid parameter
     exit_discoverable_bt_not_enabled
     bt_not_enabled
     invalid_address
     device_not_in_interface_list
     device_already_paired
     bt_last_connected_peer_device_entry_missing
     bt_last_connected_peer_device_empty
     device_already_connected
     device_object_not_created
     device_not_connected
     device_not_paired
     command_not_supported
     */
}

- (BOOL)supported
{
    __block BOOL supported = NO;
    dispatch_sync(self.syncQueue, ^{
        supported = self.btAttribute != nil;
    });
    return supported;
}

- (IoTBluetoothAdapterState)adapterState
{
    __block IoTBluetoothAdapterState state = IoTBluetoothAdapterStateUnknown;
    dispatch_sync(self.syncQueue, ^{
        state = self.adapterStateInternal;
    });
    return state;
}

- (IoTDiscoverable)discoverable
{
    return self.btAttribute.discoverable;
}

- (NSString *)connectedBTDeviceAddress
{
    __block NSString *connectedDevice = nil;
    dispatch_sync(self.syncQueue, ^{
        connectedDevice = [self.connectedBTDeviceAddressInternal copy];
        if (!connectedDevice) {
            for (IoTBtDeviceStatusAttr* btDevice in self.pairedDevicesInternal) {
                if (btDevice.status) {
                    connectedDevice = btDevice.address;
                }
            }
        }
    });
    return connectedDevice;
}

- (NSArray<IoTBtDeviceStatusAttr *> *)pairedDevices
{
    __block NSArray<IoTBtDeviceStatusAttr *> *paired = @[];
    dispatch_sync(self.syncQueue, ^{
        paired = [self.pairedDevicesInternal copy] ?: @[];
    });
    return paired;
}

- (IoTBtDeviceAttr *)discoveredDevice
{
	return self.btAttribute.scanResult;
}

- (BOOL)scanning
{
    __block BOOL scanning = NO;
    dispatch_sync(self.syncQueue, ^{
        scanning = self.scanningInternal;
    });
    return scanning;
}

@end
