//
//  ButtonScanner.m
//
//  Created by Anton Meier on 2016-04-20.
//

#import "ButtonScanner.h"

NSString * const ButtonScannerErrorDomain = @"com.shortcutlabs.buttonscanner";

@interface ButtonScanner () {
    BOOL _buttonIsPending;
}

@property (nonatomic) NSUInteger numberOfButtonsFoundDuringScan;
@property (weak, nonatomic, nullable) id<SCLFlicManagerDelegate> previousDelegate;
@property (strong, nonatomic, readwrite) NSMutableDictionary<NSNumber *, NSNumber *> *errorMap;

@property (strong, nonatomic, readwrite) NSTimer *shortTimer; // Timer for initial private button check
@property (strong, nonatomic, readwrite) NSTimer *longTimer;  // Timer for longer scan

@end

#pragma clang diagnostic ignored "-Wprotocol"

@implementation ButtonScanner

@synthesize scanning=_scanning;

+ (instancetype) sharedScanner; {
    static dispatch_once_t onceToken;
    static id sharedScanner;
    dispatch_once(&onceToken, ^{
        sharedScanner = [[self alloc] init];
    });
    return sharedScanner;
}

- (instancetype) init {
	self = [super init];
    if (self) {
        _errorMap = [NSMutableDictionary dictionary];
        _errorMap[@(SCLFlicErrorConnectionFailed)] = @(ButtonScannerErrorCodeConnectionFailed);
        _errorMap[@(SCLFlicErrorButtonIsPrivate)] = @(ButtonScannerErrorCodeButtonIsPrivate);
        _errorMap[@(SCLFlicErrorUnknown)] = @(ButtonScannerErrorCodeUnknown);
        _errorMap[@(SCLFlicErrorCryptographicFailure)] = @(ButtonScannerErrorCodeCredentialsNotMatching);
        _errorMap[@(SCLFlicErrorCredentialsNotMatching)] = @(ButtonScannerErrorCodeCredentialsNotMatching);
        _errorMap[@(SCLFlicErrorButtonDisconnectedDuringVerification)] = @(ButtonScannerErrorCodeUnknown);
        _errorMap[@(SCLFlicErrorFlicRefusedConnection)] = @(ButtonScannerErrorCodeUnknown);
        _errorMap[@(SCLFlicErrorBackendUnreachable)] = @(ButtonScannerErrorCodeBackendUnreachable);
        _errorMap[@(SCLFlicErrorNoInternetConnection)] = @(ButtonScannerErrorCodeNoInternetConnection);
        _errorMap[@(SCLFlicErrorBLEPairingFailedPreviousPairingAlreadyExisting)] = @(ButtonScannerErrorCodeBLEPairingFailedPreviousPairingAlreadyExisting);
        _errorMap[@(SCLFlicErrorBLEPairingFailedUserCanceled)] = @(ButtonScannerErrorCodeBLEPairingFailedUserCanceled);
        _errorMap[@(SCLFlicErrorBLEPairingFailedUnknownReason)] = @(ButtonScannerErrorCodeBLEPairingFailedUnknownReason);
    }
	return self;
}

- (void) startScanFlow; {
    
    if ([SCLFlicManager sharedManager].bluetoothState != SCLFlicManagerBluetoothStatePoweredOn) {
        _error = [NSError errorWithDomain:ButtonScannerErrorDomain code:ButtonScannerErrorCodeBluetoothNotActivated userInfo:nil];
        [self done];
        return;
    }
    
    if (self.scanning) {
        [self stopScanFlow];
        return;
    }
    
    _previousDelegate = [SCLFlicManager sharedManager].delegate;
    [SCLFlicManager sharedManager].delegate = self;
    
	self.button = nil;
	self.connecting = NO;
	self.hasFoundPrivateButtons = NO;
	self.scanning = YES;
    _buttonIsPending = NO;
	
	[[SCLFlicManager sharedManager] startScan];
    
    [self.shortTimer invalidate];
    
    self.shortTimer = [NSTimer scheduledTimerWithTimeInterval:4.0f
                                             target:self
                                           selector:@selector(shortTimerFired)
                                           userInfo:nil
                                            repeats:NO];
    
}

- (void) shortTimerFired {
    if (self.scanning) {
        self.numberOfButtonsFoundDuringScan = [[SCLFlicManager sharedManager] stopScan];
        
        if (self.numberOfButtonsFoundDuringScan > 0 && !self.button && !_buttonIsPending) {
            self.hasFoundPrivateButtons = YES;
            self.stateChangeHandler(SCLButtonScannerStatusEventButtonPrivateButtonsAvailable, nil);
            [[SCLFlicManager sharedManager] startScan];
            
            [self.longTimer invalidate];
            
            self.longTimer = [NSTimer scheduledTimerWithTimeInterval:15.0f
                                                               target:self
                                                             selector:@selector(longTimerFired)
                                                             userInfo:nil
                                                              repeats:NO];
            
        } else {
            [self stopScan];
            if (!self.button) {
                _error = [NSError errorWithDomain:ButtonScannerErrorDomain code:ButtonScannerErrorCodeNoPublicButtonDiscovered userInfo:nil];
                [self done];
            }
        }
        
    }
}

- (void) longTimerFired {
    [[SCLFlicManager sharedManager] stopScan];
    
    if (!self.scanning) {
        return;
    }
    if (!self.button) {
        _error = [NSError errorWithDomain:ButtonScannerErrorDomain code:ButtonScannerErrorCodeNoPublicButtonDiscovered userInfo:nil];
        [self done];
    }
}

- (void) stopScanFlow; {
    _error = [NSError errorWithDomain:ButtonScannerErrorDomain code:ButtonScannerErrorCodeNoPublicButtonDiscovered userInfo:nil];
    
    if (self.button) {
        _buttonIsPending = NO;
        [[SCLFlicManager sharedManager] forgetButton:self.button];
    }
    [self stopScan];
    [self done];
}

- (void) done; {
    [self.shortTimer invalidate];
    [self.longTimer invalidate];
    
	if (!self.button && !self.error && self.numberOfButtonsFoundDuringScan > 0) {
		_error = [NSError errorWithDomain:ButtonScannerErrorDomain code:ButtonScannerErrorCodeButtonIsPrivate userInfo:nil];
	} else if (!self.button && !self.error) {
		_error = [NSError errorWithDomain:ButtonScannerErrorDomain code:ButtonScannerErrorCodeNoPublicButtonDiscovered userInfo:nil];
	}
	
	if (self.completionHandler) {
		self.completionHandler(self.button, !self.button ? self.error : nil);
	}
	
	[self cleanup];
}

- (void) stopScan; {
	[[SCLFlicManager sharedManager] stopScan];
	self.scanning = NO;
}

- (void) cleanup; {
    [SCLFlicManager sharedManager].delegate = self.previousDelegate;
    self.button = nil;
    _buttonIsPending = NO;
    self.connecting = NO;
    self.hasFoundPrivateButtons = NO;
    self.scanning = NO;
}

- (void) abortButtonVerify {
    if (self.button) {
        [[SCLFlicManager sharedManager] forgetButton:self.button];
        self.button = nil;
    }
    self.stateChangeHandler(SCLButtonScannerStatusEventButtonVerificationFailed, nil);
    [self done];
}

- (ButtonScannerErrorCode) translateErrorCode:(NSInteger) code {
    if ([self.errorMap objectForKey:@(code)]) {
        return (ButtonScannerErrorCode)[[self.errorMap objectForKey:@(code)] integerValue];
    }
    return ButtonScannerErrorCodeUnknown;
}

// -- SCLFlicMagagerDelegate --

- (void) flicManager:(SCLFlicManager *) manager didDiscoverButton:(SCLFlicButton *)button withRSSI:(NSNumber *)RSSI; {
	dispatch_async(dispatch_get_main_queue(), ^{
        [self stopScan];
        
		if(!_buttonIsPending) {
            
            _buttonIsPending = YES;
			self.stateChangeHandler(SCLButtonScannerStatusEventButtonDiscovered, nil);
            self.button = button;
            button.delegate = self;
			_connecting = YES;
            [button connect];
			
        } else {
            [[SCLFlicManager sharedManager] forgetButton:button];
        }
	});
    
    if (self.previousDelegate && [self.previousDelegate respondsToSelector:@selector(flicManager:didDiscoverButton:withRSSI:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{ [self.previousDelegate flicManager:manager didDiscoverButton:button withRSSI:RSSI]; });
    }
}

- (void) flicManager:(SCLFlicManager * _Nonnull) manager didGrabFlicButton:(SCLFlicButton * _Nullable) button withError: (NSError * _Nullable) error; {
    if (self.previousDelegate && [self.previousDelegate respondsToSelector:@selector(flicManager:didGrabFlicButton:withError:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{ [self.previousDelegate flicManager:manager didGrabFlicButton:button withError:error]; });
    }
}

- (void) flicManager:(SCLFlicManager * _Nonnull) manager didChangeBluetoothState: (SCLFlicManagerBluetoothState) state; {
    if (self.previousDelegate && [self.previousDelegate respondsToSelector:@selector(flicManager:didChangeBluetoothState:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{ [self.previousDelegate flicManager:manager didChangeBluetoothState:state]; });
    }
}

- (void) flicManagerDidRestoreState:(SCLFlicManager * _Nonnull) manager; {
    if (self.previousDelegate && [self.previousDelegate respondsToSelector:@selector(flicManagerDidRestoreState:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{ [self.previousDelegate flicManagerDidRestoreState:manager]; });
    }
}

- (void) flicManager:(SCLFlicManager * _Nonnull) manager didForgetButton:(NSUUID * _Nonnull) buttonIdentifier error:(NSError * _Nullable) error; {
    if (self.previousDelegate && [self.previousDelegate respondsToSelector:@selector(flicManager:didForgetButton:error:)]) {
        dispatch_async(dispatch_get_main_queue(), ^{ [self.previousDelegate flicManager:manager didForgetButton:buttonIdentifier error:error]; });
    }
}

// -- SCLFlicButtonDelegate --

- (void) flicButtonDidConnect:(SCLFlicButton *)flicButton; {
    self.stateChangeHandler(SCLButtonScannerStatusEventButtonConnected, nil);
}

- (void) flicButtonIsReady:(SCLFlicButton * _Nonnull) button; {
    button.delegate = [SCLFlicManager sharedManager].defaultButtonDelegate;
    self.stateChangeHandler(SCLButtonScannerStatusEventButtonVerified, nil);
    [self done];
}

- (void) flicButton:(SCLFlicButton * _Nonnull) button didDisconnectWithError:(NSError * _Nullable) error; {
    if (!_buttonIsPending) return;
    if (error) {
        _error = [NSError errorWithDomain:ButtonScannerErrorDomain code:[self translateErrorCode:error.code] userInfo:nil];
    }
    [self abortButtonVerify];
}

- (void) flicButton:(SCLFlicButton * _Nonnull) button didFailToConnectWithError:(NSError * _Nullable) error; {
    if (!_buttonIsPending) return;
    if (error) {
        _error = [NSError errorWithDomain:ButtonScannerErrorDomain code:[self translateErrorCode:error.code] userInfo:nil];
    }
    [self abortButtonVerify];
}

@end
