Chapter 11 - Chat App/ChatAppFinished/Pods/FirebaseAuth/Firebase/Auth/Source/Utilities/FIRAuthURLPresenter.m
/*
 * Copyright 2017 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 "FIRAuthURLPresenter.h"

#import <SafariServices/SafariServices.h>

#import "FIRAuthDefaultUIDelegate.h"
#import "FIRAuthErrorUtils.h"
#import "FIRAuthGlobalWorkQueue.h"
#import "FIRAuthUIDelegate.h"
#import "FIRAuthWebViewController.h"

NS_ASSUME_NONNULL_BEGIN

@interface FIRAuthURLPresenter () <SFSafariViewControllerDelegate,
                                   FIRAuthWebViewControllerDelegate>
@end

// Disable unguarded availability warnings because SFSafariViewController is been used throughout
// the code, including as an iVar, which cannot be simply excluded by @available check.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"

@implementation FIRAuthURLPresenter {
  /** @var _isPresenting
      @brief Whether or not some web-based content is being presented.
   */
  BOOL _isPresenting;

  /** @var _callbackMatcher
      @brief The callback URL matcher for the current presentation, if one is active.
   */
  FIRAuthURLCallbackMatcher _Nullable _callbackMatcher;

  /** @var _safariViewController
      @brief The SFSafariViewController used for the current presentation, if any.
   */
  SFSafariViewController *_Nullable _safariViewController;

  /** @var _webViewController
      @brief The FIRAuthWebViewController used for the current presentation, if any.
   */
  FIRAuthWebViewController *_Nullable _webViewController;

  /** @var _UIDelegate
      @brief The UIDelegate used to present the SFSafariViewController.
   */
  id<FIRAuthUIDelegate> _UIDelegate;

  /** @var _completion
      @brief The completion handler for the current presentaion, if one is active.
      @remarks This variable is also used as a flag to indicate a presentation is active.
   */
  FIRAuthURLPresentationCompletion _Nullable _completion;
}

- (void)presentURL:(NSURL *)URL
        UIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
   callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher
        completion:(FIRAuthURLPresentationCompletion)completion {
  if (_isPresenting) {
    // Unable to start a new presentation on top of another.
    _completion(nil, [FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:nil]);
    return;
  }
  _isPresenting = YES;
  _callbackMatcher = callbackMatcher;
  _completion = completion;
  dispatch_async(dispatch_get_main_queue(), ^() {
    self->_UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate];
    if ([SFSafariViewController class]) {
      self->_safariViewController = [[SFSafariViewController alloc] initWithURL:URL];
      self->_safariViewController.delegate = self;
      [self->_UIDelegate presentViewController:self->_safariViewController
                                      animated:YES
                                    completion:nil];
      return;
    } else {
      self->_webViewController = [[FIRAuthWebViewController alloc] initWithURL:URL delegate:self];
      UINavigationController *navController =
          [[UINavigationController alloc] initWithRootViewController:self->_webViewController];
      [self->_UIDelegate presentViewController:navController animated:YES completion:nil];
    }
  });
}

- (BOOL)canHandleURL:(NSURL *)URL {
  if (_isPresenting && _callbackMatcher && _callbackMatcher(URL)) {
    [self finishPresentationWithURL:URL error:nil];
    return YES;
  }
  return NO;
}

#pragma mark - SFSafariViewControllerDelegate

- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller {
  dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
    if (controller == self->_safariViewController) {
      self->_safariViewController = nil;
      //TODO:Ensure that the SFSafariViewController is actually removed from the screen before
      //invoking finishPresentationWithURL:error:
      [self finishPresentationWithURL:nil
                                error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
    }
  });
}

#pragma mark - FIRAuthwebViewControllerDelegate

- (BOOL)webViewController:(FIRAuthWebViewController *)webViewController canHandleURL:(NSURL *)URL {
  __block BOOL result = NO;
  dispatch_sync(FIRAuthGlobalWorkQueue(), ^() {
    if (webViewController == self->_webViewController) {
      result = [self canHandleURL:URL];
    }
  });
  return result;
}

- (void)webViewControllerDidCancel:(FIRAuthWebViewController *)webViewController {
  dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
    if (webViewController == self->_webViewController) {
      [self finishPresentationWithURL:nil
                                error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]];
    }
  });
}

- (void)webViewController:(FIRAuthWebViewController *)webViewController
         didFailWithError:(NSError *)error {
  dispatch_async(FIRAuthGlobalWorkQueue(), ^() {
    if (webViewController == self->_webViewController) {
      [self finishPresentationWithURL:nil error:error];
    }
  });
}

#pragma mark - Private methods

/** @fn finishPresentationWithURL:error:
    @brief Finishes the presentation for a given URL, if any.
    @param URL The URL to finish presenting.
    @param error The error with which to finish presenting, if any.
 */
- (void)finishPresentationWithURL:(nullable NSURL *)URL
                            error:(nullable NSError *)error {
  _callbackMatcher = nil;
  id<FIRAuthUIDelegate> UIDelegate = _UIDelegate;
  _UIDelegate = nil;
  FIRAuthURLPresentationCompletion completion = _completion;
  _completion = nil;
  void (^finishBlock)(void) = ^() {
    self->_isPresenting = NO;
    completion(URL, error);
  };
  SFSafariViewController *safariViewController = _safariViewController;
  _safariViewController = nil;
  FIRAuthWebViewController *webViewController = _webViewController;
  _webViewController = nil;
  if (safariViewController || webViewController) {
    dispatch_async(dispatch_get_main_queue(), ^() {
      [UIDelegate dismissViewControllerAnimated:YES completion:^() {
        dispatch_async(FIRAuthGlobalWorkQueue(), finishBlock);
      }];
    });
  } else {
    finishBlock();
  }
}

#pragma clang diagnostic pop  // ignored "-Wunguarded-availability"

@end

NS_ASSUME_NONNULL_END
