/*
 *  Diagnostic.m
 *  Diagnostic Plugin - Core Module
 *
 *  Copyright (c) 2015 Working Edge Ltd.
 *  Copyright (c) 2012 AVANTIC ESTUDIO DE INGENIEROS
 */

#import "Diagnostic.h"
// Only import the following header if we're not on Mac Catalyst
#if !TARGET_OS_MACCATALYST
#import <CoreTelephony/CTCellularData.h>
#endif

@implementation Diagnostic

// Public constants
NSString*const UNKNOWN = @"unknown";

NSString*const AUTHORIZATION_NOT_DETERMINED = @"not_determined";
NSString*const AUTHORIZATION_DENIED = @"denied_always";
NSString*const AUTHORIZATION_GRANTED = @"authorized";
NSString*const AUTHORIZATION_PROVISIONAL = @"provisional"; // Remote Notifications
NSString*const AUTHORIZATION_EPHEMERAL = @"ephemeral"; // Remote Notifications
NSString*const AUTHORIZATION_LIMITED = @"limited"; // Photo Library

// Internal constants
static NSString*const LOG_TAG = @"Diagnostic[native]";

static NSString*const CPU_ARCH_ARMv6 = @"ARMv6";
static NSString*const CPU_ARCH_ARMv7 = @"ARMv7";
static NSString*const CPU_ARCH_ARMv8 = @"ARMv8";
static NSString*const CPU_ARCH_X86 = @"X86";
static NSString*const CPU_ARCH_X86_64 = @"X86_64";

// Internal properties
static Diagnostic* diagnostic = nil;

// Only if not on Mac Catalyst
#if !TARGET_OS_MACCATALYST
static CTCellularData* cellularData;
#endif

/********************************/
#pragma mark - Public static functions
/********************************/
+ (id) getInstance{
    return diagnostic;
}


/********************************/
#pragma mark - Plugin API
/********************************/

-(void)enableDebug:(CDVInvokedUrlCommand*)command{
    self.debugEnabled = true;
    [self logDebug:@"Debug enabled"];
}

#pragma mark -  Settings
- (void) switchToSettings: (CDVInvokedUrlCommand*)command
{
    @try {
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString: UIApplicationOpenSettingsURLString] options:@{} completionHandler:^(BOOL success) {
            if (success) {
                [self sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] :command];
            }else{
                [self sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR] :command];
            }
        }];
    }
    @catch (NSException *exception) {
        [self handlePluginException:exception :command];
    }
}

#pragma mark - Background refresh
- (void) getBackgroundRefreshStatus: (CDVInvokedUrlCommand*)command
{
    UIBackgroundRefreshStatus _status;
    @try {
        // Must run on UI thread
        _status = [[UIApplication sharedApplication] backgroundRefreshStatus];
    }@catch (NSException *exception) {
        [self handlePluginException:exception :command];
        return;
    }
    [self.commandDelegate runInBackground:^{
        @try {
            NSString* status;
            
            if (_status == UIBackgroundRefreshStatusAvailable) {
                status = AUTHORIZATION_GRANTED;
                [self logDebug:@"Background updates are available for the app."];
            }else if(_status == UIBackgroundRefreshStatusDenied){
                status = AUTHORIZATION_DENIED;
                [self logDebug:@"The user explicitly disabled background behavior for this app or for the whole system."];
            }else if(_status == UIBackgroundRefreshStatusRestricted){
                status = @"restricted";
                [self logDebug:@"Background updates are unavailable and the user cannot enable them again. For example, this status can occur when parental controls are in effect for the current user."];
            }
            [self sendPluginResultString:status:command];
        }
        @catch (NSException *exception) {
            [self handlePluginException:exception :command];
        }
    }];
}


/********************************/
#pragma mark - Internal functions
/********************************/

- (void)pluginInitialize {

    [super pluginInitialize];

    diagnostic = self;

    self.debugEnabled = false;
    self.osVersion = [[[UIDevice currentDevice] systemVersion] floatValue];

    // Only if not on Mac Catalyst
    #if !TARGET_OS_MACCATALYST
        cellularData = [[CTCellularData alloc] init];
    #endif
}

// https://stackoverflow.com/a/38441011/777265
- (void) getArchitecture: (CDVInvokedUrlCommand*)command {
    [self.commandDelegate runInBackground:^{
        @try {
            NSString* cpuArch = UNKNOWN;
            
            size_t size;
            cpu_type_t type;
            cpu_subtype_t subtype;
            size = sizeof(type);
            sysctlbyname("hw.cputype", &type, &size, NULL, 0);
            
            size = sizeof(subtype);
            sysctlbyname("hw.cpusubtype", &subtype, &size, NULL, 0);
            
            // values for cputype and cpusubtype defined in mach/machine.h
            if (type == CPU_TYPE_X86_64) {
                cpuArch = CPU_ARCH_X86_64;
            } else if (type == CPU_TYPE_X86) {
                cpuArch = CPU_ARCH_X86;
            } else if (type == CPU_TYPE_ARM64) {
                cpuArch = CPU_ARCH_ARMv8;
            } else if (type == CPU_TYPE_ARM) {
                switch(subtype){
                    case CPU_SUBTYPE_ARM_V6:
                        cpuArch = CPU_ARCH_ARMv6;
                        break;
                    case CPU_SUBTYPE_ARM_V7:
                        cpuArch = CPU_ARCH_ARMv7;
                        break;
                    case CPU_SUBTYPE_ARM_V8:
                        cpuArch = CPU_ARCH_ARMv8;
                        break;
                }
            }
            [self logDebug:[NSString stringWithFormat:@"Current CPU architecture: %@", cpuArch]];
            [self sendPluginResultString:cpuArch:command];
        }@catch (NSException *exception) {
            [self handlePluginException:exception :command];
        }
    }];
}

- (void) getCurrentBatteryLevel: (CDVInvokedUrlCommand*)command {
    [self.commandDelegate runInBackground:^{
        @try {
            UIDevice* currentDevice = [UIDevice currentDevice];
            [currentDevice setBatteryMonitoringEnabled:true];
            int batteryLevel = (int)([currentDevice batteryLevel]*100);
            [self logDebug:[NSString stringWithFormat:@"Battery level: %d", batteryLevel]];
            [self sendPluginResultInt:batteryLevel:command];
            [currentDevice setBatteryMonitoringEnabled:false];
        }@catch (NSException *exception) {
            [self handlePluginException:exception :command];
        }
    }];
}

- (void) getDeviceOSVersion: (CDVInvokedUrlCommand*)command {
    [self.commandDelegate runInBackground:^{
        @try {
            NSString* s_version = [UIDevice currentDevice].systemVersion;
            float f_version = [s_version floatValue];
            
            NSDictionary* details = @{
                @"version": s_version,
                @"apiLevel" : [NSNumber numberWithFloat:f_version*10000],
                @"apiName": s_version
            };
              
            [self sendPluginResultObject:details:command];
        }@catch (NSException *exception) {
            [self handlePluginException:exception :command];
        }
    }];
}

- (void) getBuildOSVersion: (CDVInvokedUrlCommand*)command {
    [self.commandDelegate runInBackground:^{
        @try {
            int i_min_version = __IPHONE_OS_VERSION_MIN_REQUIRED;
            NSString* s_min_version = [NSString stringWithFormat:@"%.01f", (float) i_min_version/10000];
            int i_target_version = __IPHONE_OS_VERSION_MAX_ALLOWED;
            NSString* s_target_version = [NSString stringWithFormat:@"%.01f", (float) i_target_version/10000];
            
            NSDictionary* details = @{
                @"targetApiLevel": [NSNumber numberWithInt:i_target_version],
                @"targetApiName": s_target_version,
                @"minApiLevel": [NSNumber numberWithInt:i_min_version],
                @"minApiName": s_min_version
            };
              
            [self sendPluginResultObject:details:command];
        }@catch (NSException *exception) {
            [self handlePluginException:exception :command];
        }
    }];
}

- (void) isMobileDataAuthorized: (CDVInvokedUrlCommand*)command
{
    [self.commandDelegate runInBackground:^{
        @try {
            #if !TARGET_OS_MACCATALYST
                bool isEnabled = cellularData.restrictedState == kCTCellularDataNotRestricted;;
                [diagnostic sendPluginResultBool:isEnabled :command];
            // If we're on Mac Catalyst, set this always to false
            #else
                [diagnostic sendPluginResultBool:false :command];
                return;
            #endif
        }
        @catch (NSException *exception) {
            [diagnostic handlePluginException:exception :command];
        }
    }];
}

- (void) isAccessibilityModeEnabled: (CDVInvokedUrlCommand*)command
{
    [self.commandDelegate runInBackground:^{
        @try {
            bool isEnabled = UIAccessibilityIsVoiceOverRunning();
            [diagnostic sendPluginResultBool:isEnabled :command];
        }
        @catch (NSException *exception) {
            [diagnostic handlePluginException:exception :command];
        }
    }];
}

- (void) isDebugBuild: (CDVInvokedUrlCommand*)command
{
    [self.commandDelegate runInBackground:^{
        @try {
            bool result = false;
            #ifdef DEBUG
                result = true;
            #endif
            [diagnostic sendPluginResultBool:result :command];
        }
        @catch (NSException *exception) {
            [diagnostic handlePluginException:exception :command];
        }
    }];
}

/********************************/
#pragma mark - Send results
/********************************/

- (void) sendPluginResult: (CDVPluginResult*)result :(CDVInvokedUrlCommand*)command
{
    [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}

- (void) sendPluginResultSuccess:(CDVInvokedUrlCommand*)command{
    [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
}

- (void) sendPluginNoResultAndKeepCallback:(CDVInvokedUrlCommand*)command {
    CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_NO_RESULT];
    [pluginResult setKeepCallbackAsBool:YES];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void) sendPluginResultBool: (BOOL)result :(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult;
    if(result) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:1];
    } else {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:0];
    }
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void) sendPluginResultString: (NSString*)result :(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:result];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void) sendPluginResultInt: (int)result :(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsInt:result];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void) sendPluginResultObject: (NSDictionary*)result :(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:result];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void) sendPluginError: (NSString*) errorMessage :(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage];
    [self logError:errorMessage];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void) handlePluginException: (NSException*) exception :(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:exception.reason];
    [self logError:[NSString stringWithFormat:@"EXCEPTION: %@", exception.reason]];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

- (void)executeGlobalJavascript: (NSString*)jsString
{
    [self.commandDelegate evalJs:jsString];
}

- (NSString*) arrayToJsonString:(NSArray*)inputArray
{
    NSError* error;
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:inputArray options:NSJSONWritingPrettyPrinted error:&error];
    NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    jsonString = [[jsonString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]] componentsJoinedByString:@""];
    return jsonString;
}

- (NSString*) objectToJsonString:(NSDictionary*)inputObject
{
    NSError* error;
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:inputObject options:NSJSONWritingPrettyPrinted error:&error];
    NSString* jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    return jsonString;
}

- (NSArray*) jsonStringToArray:(NSString*)jsonStr
{
    NSError* error = nil;
    NSArray* array = [NSJSONSerialization JSONObjectWithData:[jsonStr dataUsingEncoding:NSUTF8StringEncoding] options:kNilOptions error:&error];
    if (error != nil){
        array = nil;
    }
    return array;
}

- (NSDictionary*) jsonStringToDictionary:(NSString*)jsonStr
{
    return (NSDictionary*) [self jsonStringToArray:jsonStr];
}

- (bool)isNull: (NSString*)str
{
    return str == nil || str == (id)[NSNull null] || str.length == 0 || [str isEqual: @"<null>"];
}


/********************************/
#pragma mark - utility functions
/********************************/

- (void)logDebug: (NSString*)msg
{
    if(self.debugEnabled){
        NSLog(@"%@: %@", LOG_TAG, msg);
        NSString* jsString = [NSString stringWithFormat:@"console.log(\"%@: %@\")", LOG_TAG, [self escapeDoubleQuotes:msg]];
        [self executeGlobalJavascript:jsString];
    }
}

- (void)logError: (NSString*)msg
{
    NSLog(@"%@ ERROR: %@", LOG_TAG, msg);
    if(self.debugEnabled){
        NSString* jsString = [NSString stringWithFormat:@"console.error(\"%@: %@\")", LOG_TAG, [self escapeDoubleQuotes:msg]];
        [self executeGlobalJavascript:jsString];
    }
}

- (NSString*)escapeDoubleQuotes: (NSString*)str
{
    NSString *result =[str stringByReplacingOccurrencesOfString: @"\"" withString: @"\\\""];
    return result;
}

- (void) setSetting: (NSString*)key forValue:(id)value
{
    [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (id) getSetting: (NSString*) key
{
    return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}

@end


