// Copyright 2019 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"

#import "Crashlytics/Crashlytics/Components/FIRCLSHost.h"
#import "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h"

#import <GoogleUtilities/GULAppEnvironmentUtil.h>

#if CLS_TARGET_OS_OSX
#import <AppKit/AppKit.h>
#endif

#if CLS_TARGET_OS_HAS_UIKIT
#import <UIKit/UIKit.h>
#endif

NSString* FIRCLSApplicationGetBundleIdentifier(void) {
  return [[[NSBundle mainBundle] bundleIdentifier] stringByReplacingOccurrencesOfString:@"/"
                                                                             withString:@"_"];
}

NSString* FIRCLSApplicationGetSDKBundleID(void) {
  return
      [@"com.google.firebase.crashlytics." stringByAppendingString:FIRCLSApplicationGetPlatform()];
}

NSString* FIRCLSApplicationGetPlatform(void) {
#if defined(TARGET_OS_MACCATALYST) && TARGET_OS_MACCATALYST
  return @"mac";
#elif TARGET_OS_IOS
  return @"ios";
#elif TARGET_OS_OSX
  return @"mac";
#elif TARGET_OS_TV
  return @"tvos";
#elif TARGET_OS_WATCH
  return @"ios";  // TODO: temporarily use iOS until Firebase can add watchos to the backend
#endif
}

NSString* FIRCLSApplicationGetFirebasePlatform(void) {
  NSString* firebasePlatform = [GULAppEnvironmentUtil applePlatform];
#if TARGET_OS_IOS
  // This check is necessary because iOS-only apps running on iPad
  // will report UIUserInterfaceIdiomPhone via UI_USER_INTERFACE_IDIOM().
  if ([firebasePlatform isEqualToString:@"ios"] &&
      ([[UIDevice currentDevice].model.lowercaseString containsString:@"ipad"] ||
       [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)) {
    return @"ipados";
  }
#endif

  return firebasePlatform;
}

// these defaults match the FIRCLSInfoPlist helper in FIRCLSIDEFoundation
NSString* FIRCLSApplicationGetBundleVersion(void) {
  return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}

NSString* FIRCLSApplicationGetShortBundleVersion(void) {
  return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}

NSString* FIRCLSApplicationGetName(void) {
  NSString* name;
  NSBundle* mainBundle;

  mainBundle = [NSBundle mainBundle];

  name = [mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  if (name) {
    return name;
  }

  name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"];
  if (name) {
    return name;
  }

  return FIRCLSApplicationGetBundleVersion();
}

BOOL FIRCLSApplicationHasAppStoreReceipt(void) {
  NSURL* url = NSBundle.mainBundle.appStoreReceiptURL;
  return [NSFileManager.defaultManager fileExistsAtPath:[url path]];
}

FIRCLSApplicationInstallationSourceType FIRCLSApplicationInstallationSource(void) {
  if (FIRCLSApplicationHasAppStoreReceipt()) {
    return FIRCLSApplicationInstallationSourceTypeAppStore;
  }

  return FIRCLSApplicationInstallationSourceTypeDeveloperInstall;
}

BOOL FIRCLSApplicationIsExtension(void) {
  return FIRCLSApplicationExtensionPointIdentifier() != nil;
}

NSString* FIRCLSApplicationExtensionPointIdentifier(void) {
  id extensionDict = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"NSExtension"];

  if (!extensionDict) {
    return nil;
  }

  if (![extensionDict isKindOfClass:[NSDictionary class]]) {
    FIRCLSSDKLog("Error: NSExtension Info.plist entry is mal-formed\n");
    return nil;
  }

  id typeValue = [(NSDictionary*)extensionDict objectForKey:@"NSExtensionPointIdentifier"];

  if (![typeValue isKindOfClass:[NSString class]]) {
    FIRCLSSDKLog("Error: NSExtensionPointIdentifier Info.plist entry is mal-formed\n");
    return nil;
  }

  return typeValue;
}

#if CLS_TARGET_OS_HAS_UIKIT
UIApplication* FIRCLSApplicationSharedInstance(void) {
  if (FIRCLSApplicationIsExtension()) {
    return nil;
  }

  return [[UIApplication class] performSelector:@selector(sharedApplication)];
}
#elif CLS_TARGET_OS_OSX
id FIRCLSApplicationSharedInstance(void) {
  return [NSClassFromString(@"NSApplication") sharedApplication];
}
#else
id FIRCLSApplicationSharedInstance(void) {
  return nil;  // FIXME: what do we actually return for watch?
}
#endif

void FIRCLSApplicationOpenURL(NSURL* url,
                              NSExtensionContext* extensionContext,
                              void (^completionBlock)(BOOL success)) {
  if (extensionContext) {
    [extensionContext openURL:url completionHandler:completionBlock];
    return;
  }

  BOOL result = NO;

#if TARGET_OS_IOS
  // What's going on here is the value returned is a scalar, but we really need an object to
  // call this dynamically. Hoops must be jumped.
  NSInvocationOperation* op =
      [[NSInvocationOperation alloc] initWithTarget:FIRCLSApplicationSharedInstance()
                                           selector:@selector(openURL:)
                                             object:url];
  [op start];
  [op.result getValue:&result];
#elif CLS_TARGET_OS_OSX
  result = [[NSClassFromString(@"NSWorkspace") sharedWorkspace] openURL:url];
#endif

  completionBlock(result);
}

id<NSObject> FIRCLSApplicationBeginActivity(NSActivityOptions options, NSString* reason) {
  if ([[NSProcessInfo processInfo] respondsToSelector:@selector(beginActivityWithOptions:
                                                                                  reason:)]) {
    return [[NSProcessInfo processInfo] beginActivityWithOptions:options reason:reason];
  }

#if CLS_TARGET_OS_OSX
  if (options & NSActivitySuddenTerminationDisabled) {
    [[NSProcessInfo processInfo] disableSuddenTermination];
  }

  if (options & NSActivityAutomaticTerminationDisabled) {
    [[NSProcessInfo processInfo] disableAutomaticTermination:reason];
  }
#endif

  // encode the options, so we can undo our work later
  return @{@"options" : @(options), @"reason" : reason};
}

void FIRCLSApplicationEndActivity(id<NSObject> activity) {
  if (!activity) {
    return;
  }

  if ([[NSProcessInfo processInfo] respondsToSelector:@selector(endActivity:)]) {
    [[NSProcessInfo processInfo] endActivity:activity];
    return;
  }

#if CLS_TARGET_OS_OSX
  NSInteger options = [[(NSDictionary*)activity objectForKey:@"options"] integerValue];

  if (options & NSActivitySuddenTerminationDisabled) {
    [[NSProcessInfo processInfo] enableSuddenTermination];
  }

  if (options & NSActivityAutomaticTerminationDisabled) {
    [[NSProcessInfo processInfo]
        enableAutomaticTermination:[(NSDictionary*)activity objectForKey:@"reason"]];
  }
#endif
}

void FIRCLSApplicationActivity(NSActivityOptions options, NSString* reason, void (^block)(void)) {
  id<NSObject> activity = FIRCLSApplicationBeginActivity(options, reason);

  block();

  FIRCLSApplicationEndActivity(activity);
}
