// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/toolbar/legacy/ui_bundled/primary_toolbar_view_controller.h"

#import "base/check.h"
#import "base/feature_list.h"
#import "base/metrics/field_trial_params.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "ios/chrome/browser/banner_promo/model/default_browser_banner_promo_app_agent.h"
#import "ios/chrome/browser/content_suggestions/ui/content_suggestions_collection_utils.h"
#import "ios/chrome/browser/fullscreen/model/fullscreen_browser_agent.h"
#import "ios/chrome/browser/fullscreen/ui_bundled/fullscreen_animator.h"
#import "ios/chrome/browser/keyboard/ui_bundled/UIKeyCommand+Chrome.h"
#import "ios/chrome/browser/omnibox/public/omnibox_ui_features.h"
#import "ios/chrome/browser/shared/public/commands/browser_coordinator_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/layout_guide_names.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/shared/ui/util/util_swift.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/adaptive_toolbar_view_controller+subclassing.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/banner_promo_view.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/buttons/legacy_toolbar_button.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/buttons/legacy_toolbar_button_factory.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/buttons/toolbar_configuration.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/primary_toolbar_view.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/primary_toolbar_view_controller_delegate.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/public/toolbar_constants.h"
#import "ios/chrome/browser/toolbar/legacy/ui_bundled/public/toolbar_utils.h"
#import "ios/chrome/browser/toolbar/tab_group/ui/tab_group_indicator_view.h"
#import "ios/chrome/browser/toolbar/ui/toolbar_height_delegate.h"
#import "ios/chrome/common/ui/util/ui_util.h"

namespace {
// Duration for the banner promo appearance/disappearance animation
const base::TimeDelta kBannerPromoAnimationDuration = base::Seconds(0.5);
}  // namespace

// TODO(crbug.com/374808149): Clean up the killswitch.
BASE_FEATURE(kPrimaryToolbarViewDidLoadUpdateViews,
             base::FEATURE_ENABLED_BY_DEFAULT);

@interface PrimaryToolbarViewController () <TabGroupIndicatorViewDelegate>

// Redefined to be a PrimaryToolbarView.
@property(nonatomic, strong) PrimaryToolbarView* view;
@property(nonatomic, assign) BOOL isNTP;
// The last fullscreen progress registered.
@property(nonatomic, assign) CGFloat previousFullscreenProgress;
// Pan Gesture Recognizer for the view revealing pan gesture handler.
@property(nonatomic, weak) UIPanGestureRecognizer* panGestureRecognizer;

@end

@implementation PrimaryToolbarViewController

@dynamic view;

#pragma mark - AdaptiveToolbarViewController

- (void)updateForSideSwipeSnapshot:(BOOL)onNonIncognitoNTP {
  [super updateForSideSwipeSnapshot:onNonIncognitoNTP];
  if (!onNonIncognitoNTP) {
    return;
  }

  // An opaque image is expected during a snapshot. Make sure the view is not
  // hidden and display a blank view by using the NTP background and by hidding
  // the location bar.
  self.view.hidden = NO;
  self.view.backgroundColor =
      self.buttonFactory.toolbarConfiguration.NTPBackgroundColor;
  self.view.locationBarContainer.hidden = YES;
}

- (void)resetAfterSideSwipeSnapshot {
  [super resetAfterSideSwipeSnapshot];
  // Note: the view is made visible or not by an `updateToolbar` call when the
  // snapshot animation ends.
  self.view.backgroundColor =
      self.buttonFactory.toolbarConfiguration.backgroundColor;
  if (self.hasOmnibox) {
    self.view.locationBarContainer.hidden = NO;
  }
}

#pragma mark - AdaptiveToolbarViewController (Subclassing)

- (void)setLocationBarViewController:
    (UIViewController*)locationBarViewController {
  [super setLocationBarViewController:locationBarViewController];

  self.view.separator.hidden = !self.hasOmnibox;
  [self updateBackgroundColor];
}

- (void)updateBackgroundColor {
  UIColor* backgroundColor =
      self.buttonFactory.toolbarConfiguration.backgroundColor;
  self.view.backgroundColor = backgroundColor;
}

#pragma mark - NewTabPageControllerDelegate

- (void)setScrollProgressForTabletOmnibox:(CGFloat)progress {
  [super setScrollProgressForTabletOmnibox:progress];

  // Sometimes an NTP may make a delegate call when it's no longer visible.
  if (!self.isNTP ||
      (!IsComposeboxIpadEnabled() &&
       (!self.shouldHideOmniboxOnNTP || self.locationBarFocused))) {
    progress = 1;
  }

  if (progress == 1) {
    self.view.locationBarContainer.transform = CGAffineTransformIdentity;
  } else {
    self.view.locationBarContainer.transform = CGAffineTransformMakeTranslation(
        0, [self verticalMarginForLocationBarForFullscreenProgress:1] *
               (progress - 1));
  }
  self.view.locationBarContainer.alpha = progress;
  self.view.separator.alpha = progress;

  // When the locationBarContainer is hidden, show the `fakeOmniboxTarget`.
  if (progress == 0 && !self.view.fakeOmniboxTarget) {
    [self.view addFakeOmniboxTarget];
    UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc]
        initWithTarget:self.browserCoordinatorHandler
                action:@selector(showComposebox)];
    [self.view.fakeOmniboxTarget addGestureRecognizer:tapRecognizer];
  } else if (progress > 0 && self.view.fakeOmniboxTarget) {
    [self.view removeFakeOmniboxTarget];
  }
}

#pragma mark - UIViewController

- (void)loadView {
  DCHECK(self.buttonFactory);

  // The first time, the toolbar is fully displayed.
  self.previousFullscreenProgress = 1;

  self.view =
      [[PrimaryToolbarView alloc] initWithButtonFactory:self.buttonFactory];
  [self.layoutGuideCenter referenceView:self.view
                              underName:kPrimaryToolbarGuide];

  // This method cannot be called from the init as the topSafeAnchor can only be
  // set to topLayoutGuide after the view creation on iOS 10.
  [self.view setUp];

  self.view.bannerPromo.delegate = self.bannerPromoDelegate;

  // Reference the location bar container as the top omnibox layout guide.
  // Force the synchronous layout update, as this fixes the screen rotation
  // animation in this case.
  [self.layoutGuideCenter referenceView:self.view.locationBarContainer
                              underName:kTopOmniboxGuide
         forcesSynchronousLayoutUpdates:YES];
  self.view.locationBarBottomConstraint.constant =
      [self verticalMarginForLocationBarForFullscreenProgress:1];
}

- (void)viewDidLoad {
  [super viewDidLoad];

  // We register for specific trait changes (vertical and horizontal size
  // classes) and provide a handler method
  // `updateViews:previousTraitCollection:` to be called when those traits
  // change.
  [self
      registerForTraitChanges:
          @[ UITraitVerticalSizeClass.class, UITraitHorizontalSizeClass.class ]
                   withAction:@selector(updateViews:previousTraitCollection:)];
  // TODO(crbug.com/374808149): Clean up the killswitch.
  if (base::FeatureList::IsEnabled(kPrimaryToolbarViewDidLoadUpdateViews)) {
    [self updateViews:self.view previousTraitCollection:nil];
  }
}

#pragma mark - UIResponder

// To always be able to register key commands via -keyCommands, the VC must be
// able to become first responder.
- (BOOL)canBecomeFirstResponder {
  return YES;
}

- (NSArray<UIKeyCommand*>*)keyCommands {
  return @[ UIKeyCommand.cr_close ];
}

- (void)keyCommand_close {
  base::RecordAction(base::UserMetricsAction(kMobileKeyCommandClose));
  [self.delegate close];
}

#pragma mark - Public

- (void)setTabGroupIndicatorView:(TabGroupIndicatorView*)view {
  view.delegate = self;
  self.view.tabGroupIndicatorView = view;
}

- (UIView*)shareButton {
  return self.view.shareButton;
}

#pragma mark - Property accessors

- (void)setIsNTP:(BOOL)isNTP {
  if (isNTP == _isNTP) {
    return;
  }
  [super setIsNTP:isNTP];
  _isNTP = isNTP;

  // The omnibox is always visible when having two toolbars and no TabStrip.
  BOOL omniboxAlwaysVisible =
      IsSplitToolbarMode(self) && !CanShowTabStrip(self);
  if (omniboxAlwaysVisible || !self.shouldHideOmniboxOnNTP) {
    return;
  }

  // This is hiding/showing and positionning the omnibox. This is only needed
  // if the omnibox should be hidden when there is only one toolbar.
  [self setScrollProgressForTabletOmnibox:(isNTP ? 0 : 1)];
}

- (void)setLocationBarFocused:(BOOL)locationBarFocused {
  if (self.locationBarFocused == locationBarFocused) {
    return;
  }
  [super setLocationBarFocused:locationBarFocused];

  if (!IsComposeboxIpadEnabled()) {
    [self setScrollProgressForTabletOmnibox:(self.isNTP &&
                                             self.shouldHideOmniboxOnNTP)
                                                ? 0
                                                : 1];
  }
}

- (BOOL)locationBarIsExpanded {
  return self.view.expanded;
}

#pragma mark - FullscreenBrowserAgentObserving

- (void)fullscreenWillUpdateObscuredInsetRange:(FullscreenBrowserAgent*)agent {
  agent->AddObscuredInsetRange(UIRectEdgeTop, [self minHeight],
                               [self maxHeight]);
}

- (void)fullscreenWillUpdateState:(FullscreenBrowserAgent*)agent {
  [self updateForFullscreenProgress:agent->top_progress()];
  [self.view layoutIfNeeded];
  CGFloat minHeight = [self minHeight];
  CGFloat maxHeight = [self maxHeight];
  CGFloat currentHeight =
      minHeight + (maxHeight - minHeight) * agent->top_progress();
  agent->AddObscuredInset(UIRectEdgeTop, currentHeight);
}

#pragma mark - FullscreenUIElement

- (void)updateForFullscreenProgress:(CGFloat)progress {
  [super updateForFullscreenProgress:progress];

  self.previousFullscreenProgress = progress;

  CGFloat alphaValue = fmax(progress * 2 - 1, 0);
  self.view.leadingStackView.alpha = alphaValue;
  self.view.trailingStackView.alpha = alphaValue;
  self.view.locationBarBottomConstraint.constant =
      [self verticalMarginForLocationBarForFullscreenProgress:progress];

  [self.view updateForFullscreenProgress:progress];
}

#pragma mark - ToolbarAnimatee

- (void)expandLocationBar {
  self.view.expanded = YES;
  [self.delegate locationBarExpandedInViewController:self];
  [self.view layoutIfNeeded];
}

- (void)contractLocationBar {
  self.view.splitToolbarMode = IsSplitToolbarMode(self);
  self.view.expanded = NO;
  [self.delegate locationBarContractedInViewController:self];
  [self.view layoutIfNeeded];
}

- (void)showCancelButton {
  self.view.cancelButton.hidden = NO;
}

- (void)hideCancelButton {
  self.view.cancelButton.hidden = YES;
}

- (void)showControlButtons {
  for (LegacyToolbarButton* button in self.view.allButtons) {
    button.alpha = 1;
  }
}

- (void)hideControlButtons {
  for (LegacyToolbarButton* button in self.view.allButtons) {
    button.alpha = 0;
  }
}

- (void)setToolbarFaded:(BOOL)faded {
  self.view.alpha = faded ? 0 : 1;
}

- (void)setLocationBarHeightToMatchFakeOmnibox {
  if (!IsSplitToolbarMode(self)) {
    return;
  }
  [self setLocationBarContainerHeight:content_suggestions::
                                          PinnedFakeOmniboxHeight()];
  self.view.matchNTPHeight = YES;
}

- (void)setLocationBarHeightExpanded {
  [self setLocationBarContainerHeight:LocationBarHeight(
                                          self.traitCollection
                                              .preferredContentSizeCategory)];
  self.view.matchNTPHeight = NO;
}

#pragma mark - PrimaryToolbarConsumer

- (void)showBannerPromo {
  [self.view prepareToShowBannerPromo];
  [self.view.superview layoutIfNeeded];

  __weak __typeof(self) weakSelf = self;
  [UIView animateWithDuration:kBannerPromoAnimationDuration.InSecondsF()
      animations:^{
        [weakSelf showBannerPromoAnimationBlock];
      }
      completion:^(BOOL success) {
        if (success) {
          [weakSelf showBannerPromoCompletionBlock];
        }
      }];
}

// Helper method to actually do the animation to show the banner promo.
- (void)showBannerPromoAnimationBlock {
  [self.view showBannerPromo];
  [self.toolbarHeightDelegate toolbarsHeightChanged];
  [self.view.superview layoutIfNeeded];
}

- (void)showBannerPromoCompletionBlock {
  UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification,
                                  self.view.bannerPromo);
}

- (void)hideBannerPromo {
  [self.view.superview layoutIfNeeded];
  __weak __typeof(self) weakSelf = self;
  [UIView animateWithDuration:kBannerPromoAnimationDuration.InSecondsF()
      animations:^{
        [weakSelf hideBannerPromoAnimationBlock];
      }
      completion:^(BOOL completed) {
        [weakSelf hideBannerPromoCompletionBlock];
      }];
}

// Helper method to actually do the animation to hide the banner promo.
- (void)hideBannerPromoAnimationBlock {
  [self.view hideBannerPromo];
  [self.toolbarHeightDelegate toolbarsHeightChanged];
  [self.view.superview layoutIfNeeded];
}

// Helper method for completion.
- (void)hideBannerPromoCompletionBlock {
  [self.view cleanupAfterHideBannerPromo];
  [self.view.superview layoutIfNeeded];
}
#pragma mark - Private

// Adjusts the layout and appearance of views in response to changes in
// available space and trait collections.
- (void)updateViews:(UIView*)updatedView
    previousTraitCollection:(UITraitCollection*)previousTraitCollection {
  self.view.locationBarBottomConstraint.constant =
      [self verticalMarginForLocationBarForFullscreenProgress:
                self.previousFullscreenProgress];
  self.view.topCornersRounded = NO;
  [self.view updateTabGroupIndicatorAvailability];
  [self.delegate
      viewControllerTraitCollectionDidChange:previousTraitCollection];
}

- (CGFloat)clampedFontSizeMultiplier {
  return ToolbarClampedFontSizeMultiplier(
      self.traitCollection.preferredContentSizeCategory);
}

// Returns the vertical margin to the location bar based on fullscreen
// `progress`, aligned to the nearest pixel.
- (CGFloat)verticalMarginForLocationBarForFullscreenProgress:(CGFloat)progress {
  // The vertical bottom margin for the location bar is such that the location
  // bar looks visually centered. However, the constraints are not geometrically
  // centering the location bar. It is moved by 0pt in iPhone landscape and by
  // 3pt in all other configurations.
  CGFloat fullscreenVerticalMargin =
      IsCompactHeight(self) ? 0 : kAdaptiveLocationBarVerticalMarginFullscreen;
  return -AlignValueToLowerPixel(
      (kAdaptiveLocationBarVerticalMargin * progress +
       fullscreenVerticalMargin * (1 - progress)) *
          [self clampedFontSizeMultiplier] +
      ([self clampedFontSizeMultiplier] - 1) *
          kLocationBarVerticalMarginDynamicType);
}

// Sets the height of the location bar container.
- (void)setLocationBarContainerHeight:(CGFloat)height {
  PrimaryToolbarView* view = self.view;
  view.locationBarContainerHeight.constant = height;
  view.locationBarContainer.layer.cornerRadius = height / 2;
}

// The minimum height of this toolbar.
- (CGFloat)minHeight {
  UIContentSizeCategory category =
      self.traitCollection.preferredContentSizeCategory;
  return [self hasOmnibox] ? ToolbarCollapsedHeight(category) : 0;
}

// The maximum height of this toolbar.
- (CGFloat)maxHeight {
  CGFloat maxHeight = self.view.intrinsicContentSize.height;
  if (!IsSplitToolbarMode(self) || CanShowTabStrip(self)) {
    maxHeight += kTopToolbarUnsplitMargin;
  }
  return maxHeight;
}

#pragma mark - TabGroupIndicatorViewDelegate

- (void)tabGroupIndicatorViewVisibilityUpdated:(BOOL)visible {
  [self.view tabGroupIndicatorViewVisibilityUpdated:visible];
  [self.delegate viewController:self
      tabGroupIndicatorVisibilityUpdated:visible];
}

@end
