// Copyright 2020-present the Material Components for iOS authors. All Rights Reserved.
//
// 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 "MDCBaseTextArea.h"

#import <CoreGraphics/CoreGraphics.h>
#import <MDFInternationalization/MDFInternationalization.h>
#import <QuartzCore/QuartzCore.h>

#import "private/MDCBaseTextAreaLayout.h"
#import "private/MDCBaseTextAreaTextView.h"
#import "MDCBaseTextAreaDelegate.h"
#import "MDCTextControlLabelBehavior.h"
#import "MDCTextControlState.h"
#import "MaterialTextControlsPrivate+BaseStyle.h"
#import "MDCTextControlAssistiveLabelDrawPriority.h"
#import "MaterialTextControlsPrivate+Shared.h"

static char *const kKVOContextMDCBaseTextArea = "kKVOContextMDCBaseTextArea";

static const CGFloat kMDCBaseTextAreaDefaultMinimumNumberOfVisibleLines = (CGFloat)2.0;
static const CGFloat kMDCBaseTextAreaDefaultMaximumNumberOfVisibleLines = (CGFloat)4.0;

@interface MDCBaseTextArea () <MDCTextControl,
                               MDCBaseTextAreaTextViewDelegate,
                               UIGestureRecognizerDelegate>

#pragma mark MDCTextControl properties
@property(strong, nonatomic) UILabel *label;
@property(nonatomic, strong) UILabel *placeholderLabel;
@property(nonatomic, strong) MDCTextControlAssistiveLabelView *assistiveLabelView;
@property(strong, nonatomic) MDCBaseTextAreaLayout *layout;
@property(nonatomic, assign) MDCTextControlState textControlState;
@property(nonatomic, assign) MDCTextControlLabelPosition labelPosition;
@property(nonatomic, assign) CGRect labelFrame;
@property(nonatomic, assign) NSTimeInterval animationDuration;

@property(nonatomic, strong)
    NSMutableDictionary<NSNumber *, MDCTextControlColorViewModel *> *colorViewModels;

@property(strong, nonatomic) UIView *maskedTextViewContainerView;
@property(strong, nonatomic) MDCBaseTextAreaTextView *baseTextAreaTextView;

@property(nonatomic, strong) MDCTextControlGradientManager *gradientManager;

@property(strong, nonatomic) UITapGestureRecognizer *tapGesture;
@property(nonatomic, assign) CGSize cachedIntrinsicContentSize;
@property(nonatomic, assign) CGFloat cachedNumberOfLinesOfText;

@end

@implementation MDCBaseTextArea
@synthesize containerStyle = _containerStyle;
@synthesize assistiveLabelDrawPriority = _assistiveLabelDrawPriority;
@synthesize customAssistiveLabelDrawPriority = _customAssistiveLabelDrawPriority;
@synthesize adjustsFontForContentSizeCategory = _adjustsFontForContentSizeCategory;

#pragma mark Object Lifecycle

- (instancetype)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    [self commonMDCBaseTextAreaInit];
  }
  return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
  self = [super initWithCoder:aDecoder];
  if (self) {
    [self commonMDCBaseTextAreaInit];
  }
  return self;
}

- (void)commonMDCBaseTextAreaInit {
  [self initializeProperties];
  [self setUpTapGesture];
  [self setUpColorViewModels];
  [self setUpLabel];
  [self setUpAssistiveLabels];
  [self setUpTextView];
  [self setUpPlaceholderLabel];
  [self observeTextViewNotifications];
  [self observeAssistiveLabelKeyPaths];
}

- (void)dealloc {
  [self removeObservers];
}

#pragma mark Setup

- (void)initializeProperties {
  self.animationDuration = kMDCTextControlDefaultAnimationDuration;
  self.labelBehavior = MDCTextControlLabelBehaviorFloats;
  self.labelPosition = [self determineCurrentLabelPosition];
  self.textControlState = [self determineCurrentTextControlState];
  self.containerStyle = [[MDCTextControlStyleBase alloc] init];
  self.colorViewModels = [[NSMutableDictionary alloc] init];
  self.minimumNumberOfVisibleRows = kMDCBaseTextAreaDefaultMinimumNumberOfVisibleLines;
  self.maximumNumberOfVisibleRows = kMDCBaseTextAreaDefaultMaximumNumberOfVisibleLines;
  self.gradientManager = [[MDCTextControlGradientManager alloc] init];
  self.placeholderColor = [self defaultPlaceholderColor];
}

- (void)setUpTapGesture {
  self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                            action:@selector(handleTap:)];
  [self addGestureRecognizer:self.tapGesture];
}

- (void)setUpColorViewModels {
  self.colorViewModels = [[NSMutableDictionary alloc] init];
  self.colorViewModels[@(MDCTextControlStateNormal)] =
      [[MDCTextControlColorViewModel alloc] initWithState:MDCTextControlStateNormal];
  self.colorViewModels[@(MDCTextControlStateEditing)] =
      [[MDCTextControlColorViewModel alloc] initWithState:MDCTextControlStateEditing];
  self.colorViewModels[@(MDCTextControlStateDisabled)] =
      [[MDCTextControlColorViewModel alloc] initWithState:MDCTextControlStateDisabled];
}

- (void)setUpAssistiveLabels {
  self.assistiveLabelDrawPriority = MDCTextControlAssistiveLabelDrawPriorityTrailing;
  self.assistiveLabelView = [[MDCTextControlAssistiveLabelView alloc] init];
  [self addSubview:self.assistiveLabelView];
}

- (void)setUpLabel {
  self.label = [[UILabel alloc] init];
  [self addSubview:self.label];
}

- (void)setUpPlaceholderLabel {
  self.placeholderLabel = [[UILabel alloc] init];
  [self.textView addSubview:self.placeholderLabel];
}

- (void)setUpTextView {
  self.maskedTextViewContainerView = [[UIView alloc] init];
  [self addSubview:self.maskedTextViewContainerView];

  self.baseTextAreaTextView = [[MDCBaseTextAreaTextView alloc] init];
  self.baseTextAreaTextView.textAreaTextViewDelegate = self;
  [self.maskedTextViewContainerView addSubview:self.baseTextAreaTextView];
}

#pragma mark UIView Overrides

- (void)layoutSubviews {
  [self preLayoutSubviews];
  [super layoutSubviews];
  [self postLayoutSubviews];
}

- (CGSize)sizeThatFits:(CGSize)size {
  return [self preferredSizeWithWidth:size.width];
}

- (CGSize)intrinsicContentSize {
  self.cachedIntrinsicContentSize = [self preferredSizeWithWidth:CGRectGetWidth(self.bounds)];
  return self.cachedIntrinsicContentSize;
}

- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
  [super traitCollectionDidChange:previousTraitCollection];
  [self setNeedsLayout];
}

- (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute {
  [super setSemanticContentAttribute:semanticContentAttribute];

  self.textView.semanticContentAttribute = semanticContentAttribute;
  self.placeholderLabel.semanticContentAttribute = semanticContentAttribute;
  [self setNeedsLayout];
}

- (void)setEnabled:(BOOL)enabled {
  [super setEnabled:enabled];
  self.textView.editable = enabled;
  [self setNeedsLayout];
}

#pragma mark Private Layout

- (void)preLayoutSubviews {
  self.textControlState = [self determineCurrentTextControlState];
  self.labelPosition = [self determineCurrentLabelPosition];
  self.placeholderLabel.attributedText = [self determineAttributedPlaceholder];
  self.placeholderLabel.numberOfLines = (NSInteger)self.numberOfLinesOfVisibleText;
  MDCTextControlColorViewModel *colorViewModel =
      [self textControlColorViewModelForState:self.textControlState];
  [self applyColorViewModel:colorViewModel withLabelPosition:self.labelPosition];
  CGSize fittingSize = CGSizeMake(CGRectGetWidth(self.frame), CGFLOAT_MAX);
  self.layout = [self calculateLayoutWithSize:fittingSize];
  self.labelFrame = [self.layout labelFrameWithLabelPosition:self.labelPosition];
}

- (void)postLayoutSubviews {
  self.maskedTextViewContainerView.frame = self.containerFrame;
  self.textView.frame = self.layout.textViewFrame;
  self.placeholderLabel.hidden = self.layout.placeholderLabelHidden;
  self.placeholderLabel.frame = self.layout.placeholderLabelFrame;
  self.assistiveLabelView.frame = self.layout.assistiveLabelViewFrame;
  self.assistiveLabelView.layout = self.layout.assistiveLabelViewLayout;
  [self.assistiveLabelView setNeedsLayout];
  [self animateLabel];
  [self updateSideViews];
  [self.containerStyle applyStyleToTextControl:self animationDuration:self.animationDuration];
  [self layoutGradientLayers];
  [self scrollToSelectedText];
  if ([self widthHasChanged] || [self calculatedHeightHasChanged]) {
    [self handleIntrinsicContentSizeChange];
  }
}

- (void)updateSideViews {
  if (self.layout.displaysLeadingView) {
    if (self.leadingView) {
      if (self.leadingView.superview != self) {
        [self addSubview:self.leadingView];
      }
      self.leadingView.frame = self.layout.leadingViewFrame;
    }
  } else {
    [self.leadingView removeFromSuperview];
  }

  if (self.layout.displaysTrailingView) {
    if (self.trailingView) {
      if (self.trailingView.superview != self) {
        [self addSubview:self.trailingView];
      }
      self.trailingView.frame = self.layout.trailingViewFrame;
    }
  } else {
    [self.trailingView removeFromSuperview];
  }
}

- (void)scrollToSelectedText {
  //  Undesirable things happen to the text view's contentOffset when adding new lines in a growing
  //  MDCBaseTextArea in iOS versions 11 and 12. Specifically, hitting return will appear to result
  //  in two newlines, instead of one, but it's really just contentOffset being set. This line seems
  //  to prevent this issue without creating any others.
  [self.textView scrollRangeToVisible:self.textView.selectedRange];
}

- (MDCBaseTextAreaLayout *)calculateLayoutWithSize:(CGSize)size {
  CGFloat clampedCustomAssistiveLabelDrawPriority =
      [self clampedCustomAssistiveLabelDrawPriority:self.customAssistiveLabelDrawPriority];
  id<MDCTextControlVerticalPositioningReference> verticalPositioningReference =
      [self createVerticalPositioningReference];
  id<MDCTextControlHorizontalPositioning> horizontalPositioningReference =
      [self createHorizontalPositioningReference];
  return [[MDCBaseTextAreaLayout alloc] initWithSize:size
                        verticalPositioningReference:verticalPositioningReference
                      horizontalPositioningReference:horizontalPositioningReference
                                                text:self.baseTextAreaTextView.text
                                                font:self.normalFont
                                        floatingFont:self.floatingFont
                                               label:self.label
                                       labelPosition:self.labelPosition
                                       labelBehavior:self.labelBehavior
                                    placeholderLabel:self.placeholderLabel
                                         leadingView:self.leadingView
                                     leadingViewMode:self.leadingViewMode
                                        trailingView:self.trailingView
                                    trailingViewMode:self.trailingViewMode
                               leadingAssistiveLabel:self.assistiveLabelView.leadingAssistiveLabel
                              trailingAssistiveLabel:self.assistiveLabelView.trailingAssistiveLabel
                          assistiveLabelDrawPriority:self.assistiveLabelDrawPriority
                    customAssistiveLabelDrawPriority:clampedCustomAssistiveLabelDrawPriority
                                               isRTL:self.shouldLayoutForRTL
                                           isEditing:self.textView.isFirstResponder];
}

- (id<MDCTextControlVerticalPositioningReference>)createVerticalPositioningReference {
  return [self.containerStyle
      positioningReferenceWithFloatingFontLineHeight:self.floatingFont.lineHeight
                                normalFontLineHeight:self.normalFont.lineHeight
                                       textRowHeight:(self.normalFont.lineHeight +
                                                      self.normalFont.leading)
                                    numberOfTextRows:self.numberOfLinesOfVisibleText
                                             density:self.verticalDensity
                            preferredContainerHeight:self.preferredContainerHeight
                              isMultilineTextControl:YES];
}

- (id<MDCTextControlHorizontalPositioning>)createHorizontalPositioningReference {
  id<MDCTextControlHorizontalPositioning> horizontalPositioningReference =
      self.containerStyle.horizontalPositioningReference;
  if (self.leadingEdgePaddingOverride) {
    horizontalPositioningReference.leadingEdgePadding =
        (CGFloat)[self.leadingEdgePaddingOverride doubleValue];
  }
  if (self.trailingEdgePaddingOverride) {
    horizontalPositioningReference.trailingEdgePadding =
        (CGFloat)[self.trailingEdgePaddingOverride doubleValue];
  }
  if (self.horizontalInterItemSpacingOverride) {
    horizontalPositioningReference.horizontalInterItemSpacing =
        (CGFloat)[self.horizontalInterItemSpacingOverride doubleValue];
  }
  return horizontalPositioningReference;
}

- (CGFloat)clampedCustomAssistiveLabelDrawPriority:(CGFloat)customPriority {
  CGFloat value = customPriority;
  if (value < 0) {
    value = 0;
  } else if (value > 1) {
    value = 1;
  }
  return value;
}

- (CGSize)preferredSizeWithWidth:(CGFloat)width {
  CGSize fittingSize = CGSizeMake(width, CGFLOAT_MAX);
  MDCBaseTextAreaLayout *layout = [self calculateLayoutWithSize:fittingSize];
  return CGSizeMake(width, layout.calculatedHeight);
}

- (BOOL)widthHasChanged {
  return CGRectGetWidth(self.bounds) != self.cachedIntrinsicContentSize.width;
}

- (BOOL)calculatedHeightHasChanged {
  return self.layout.calculatedHeight != self.cachedIntrinsicContentSize.height;
}

- (void)handleIntrinsicContentSizeChange {
  [self invalidateIntrinsicContentSize];
  if ([self.baseTextAreaDelegate respondsToSelector:@selector(baseTextArea:shouldChangeSize:)]) {
    CGSize preferredSize = CGSizeMake(CGRectGetWidth(self.bounds), self.layout.calculatedHeight);
    [self.baseTextAreaDelegate baseTextArea:self shouldChangeSize:preferredSize];
  }
}

- (void)layoutGradientLayers {
  CGRect gradientLayerFrame = self.containerFrame;
  self.gradientManager.horizontalGradient.frame = gradientLayerFrame;
  self.gradientManager.verticalGradient.frame = gradientLayerFrame;
  self.gradientManager.horizontalGradient.locations = self.layout.horizontalGradientLocations;
  self.gradientManager.verticalGradient.locations = self.layout.verticalGradientLocations;
  self.maskedTextViewContainerView.layer.mask = [self.gradientManager combinedGradientMaskLayer];
}

- (CGFloat)numberOfLinesOfText {
  // For more context on measuring the lines in a UITextView see here:
  // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextLayout/Tasks/CountLines.html
  NSLayoutManager *layoutManager = self.textView.layoutManager;
  NSUInteger numberOfGlyphs = layoutManager.numberOfGlyphs;
  NSRange lineRange = NSMakeRange(0, 1);
  NSUInteger index = 0;
  NSUInteger numberOfLines = 0;
  while (index < numberOfGlyphs) {
    [layoutManager lineFragmentRectForGlyphAtIndex:index effectiveRange:&lineRange];
    index = NSMaxRange(lineRange);
    numberOfLines += 1;
  }

  BOOL textEndsInNewLine =
      self.textView.text.length > 0 &&
      [self.textView.text characterAtIndex:self.textView.text.length - 1] == '\n';
  if (textEndsInNewLine) {
    numberOfLines += 1;
  }
  BOOL numberOfLinesChanged = self.cachedNumberOfLinesOfText != (CGFloat)numberOfLines;
  if (numberOfLinesChanged) {
    [self setNeedsLayout];
  }
  self.cachedNumberOfLinesOfText = (CGFloat)numberOfLines;
  return (CGFloat)numberOfLines;
}

- (CGFloat)numberOfLinesOfVisibleText {
  CGFloat numberOfLinesOfText = [self numberOfLinesOfText];
  if (numberOfLinesOfText < self.minimumNumberOfVisibleRows) {
    return self.minimumNumberOfVisibleRows;
  } else if (numberOfLinesOfText > self.maximumNumberOfVisibleRows) {
    return self.maximumNumberOfVisibleRows;
  }
  return numberOfLinesOfText;
}

#pragma mark Dynamic Type

- (void)setAdjustsFontForContentSizeCategory:(BOOL)adjustsFontForContentSizeCategory {
  _adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
  self.textView.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
  self.leadingAssistiveLabel.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
  self.trailingAssistiveLabel.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
}

#pragma mark MDCTextControlState

- (MDCTextControlState)determineCurrentTextControlState {
  BOOL isEnabled = self.enabled && self.baseTextAreaTextView.isEditable;
  BOOL isEditing = self.textView.isFirstResponder;
  return MDCTextControlStateWith(isEnabled, isEditing);
}

#pragma mark Placeholder

- (NSAttributedString *)determineAttributedPlaceholder {
  if ([self shouldPlaceholderBeVisible]) {
    NSDictionary<NSAttributedStringKey, id> *attributes = @{
      NSParagraphStyleAttributeName : [self defaultPlaceholderParagraphStyle],
      NSForegroundColorAttributeName : self.placeholderColor,
      NSFontAttributeName : self.normalFont
    };
    return [[NSAttributedString alloc] initWithString:self.placeholder attributes:attributes];
  } else {
    return nil;
  }
}

- (BOOL)shouldPlaceholderBeVisible {
  return MDCTextControlShouldPlaceholderBeVisibleWithPlaceholder(
      self.placeholder, self.textView.text, self.labelPosition);
}

/**
 This method provides the default paragraph style object for the placeholder label. Without this
 it's much harder to get the placeholder to be perfectly vertically aligned with the text in the
 text view.
 */
- (NSParagraphStyle *)defaultPlaceholderParagraphStyle {
  static NSParagraphStyle *paragraphStyle = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    id attribute =
        [self defaultUiTextFieldPlaceholderAttributeWithKey:NSParagraphStyleAttributeName];
    if ([attribute isKindOfClass:[NSParagraphStyle class]]) {
      paragraphStyle = (NSParagraphStyle *)attribute;
    }
  });
  return paragraphStyle;
}

- (UIColor *)defaultPlaceholderColor {
  static UIColor *placeholderColor = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    id attribute =
        [self defaultUiTextFieldPlaceholderAttributeWithKey:NSForegroundColorAttributeName];
    if ([attribute isKindOfClass:[UIColor class]]) {
      placeholderColor = (UIColor *)attribute;
    }
  });
  return placeholderColor;
}

- (id)defaultUiTextFieldPlaceholderAttributeWithKey:(NSAttributedStringKey)attributedStringKey {
  UITextField *textField = [[UITextField alloc] init];
  textField.placeholder = @"placeholder";
  NSAttributedString *attributedPlaceholder = textField.attributedPlaceholder;
  NSDictionary *attributeKeysToAttributes =
      [attributedPlaceholder attributesAtIndex:0
                         longestEffectiveRange:nil
                                       inRange:NSMakeRange(0, attributedPlaceholder.length)];
  for (NSAttributedStringKey key in attributeKeysToAttributes) {
    if (key == attributedStringKey) {
      return attributeKeysToAttributes[attributedStringKey];
    }
  }
  return nil;
}

#pragma mark Label

- (void)animateLabel {
  __weak MDCBaseTextArea *weakSelf = self;
  [MDCTextControlLabelAnimation animateLabel:self.label
                                       state:self.labelPosition
                            normalLabelFrame:self.layout.labelFrameNormal
                          floatingLabelFrame:self.layout.labelFrameFloating
                                  normalFont:self.normalFont
                                floatingFont:self.floatingFont
                    labelTruncationIsPresent:self.layout.labelTruncationIsPresent
                           animationDuration:self.animationDuration
                                  completion:^(BOOL finished) {
                                    if (finished) {
                                      // Ensure that the label position is correct in case of
                                      // competing animations.
                                      weakSelf.label.frame = weakSelf.labelFrame;
                                    }
                                  }];
}

- (BOOL)canLabelFloat {
  return self.labelBehavior == MDCTextControlLabelBehaviorFloats;
}

- (MDCTextControlLabelPosition)determineCurrentLabelPosition {
  return MDCTextControlLabelPositionWith(self.label.text.length > 0, self.textView.text.length > 0,
                                         self.canLabelFloat, self.textView.isFirstResponder);
}

#pragma mark Custom Accessors

- (void)setLeadingEdgePaddingOverride:(NSNumber *)leadingEdgePaddingOverride {
  _leadingEdgePaddingOverride = leadingEdgePaddingOverride;
  [self setNeedsLayout];
}

- (void)setTrailingEdgePaddingOverride:(NSNumber *)trailingEdgePaddingOverride {
  _trailingEdgePaddingOverride = trailingEdgePaddingOverride;
  [self setNeedsLayout];
}

- (UITextView *)textView {
  return self.baseTextAreaTextView;
}

- (void)setPlaceholder:(NSString *)placeholder {
  _placeholder = placeholder;
  [self setNeedsLayout];
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor {
  _placeholderColor = placeholderColor ?: [self defaultPlaceholderColor];
}

- (void)setVerticalDensity:(CGFloat)verticalDensity {
  _verticalDensity = verticalDensity;
  [self setNeedsLayout];
}

#pragma mark MDCTextControl Protocol Accessors

- (void)setLeadingView:(UIView *)leadingView {
  [_leadingView removeFromSuperview];
  _leadingView = leadingView;
  [self setNeedsLayout];
}

- (void)setTrailingview:(UIView *)trailingView {
  [_trailingView removeFromSuperview];
  _trailingView = trailingView;
  [self setNeedsLayout];
}

- (void)setLeadingViewMode:(UITextFieldViewMode)leadingViewMode {
  _leadingViewMode = leadingViewMode;
  [self setNeedsLayout];
}

- (void)setTrailingViewMode:(UITextFieldViewMode)trailingViewMode {
  _trailingViewMode = trailingViewMode;
  [self setNeedsLayout];
}

- (void)setContainerStyle:(id<MDCTextControlStyle>)containerStyle {
  id<MDCTextControlStyle> oldStyle = _containerStyle;
  if (oldStyle) {
    [oldStyle removeStyleFrom:self];
  }
  _containerStyle = containerStyle;
  [_containerStyle applyStyleToTextControl:self animationDuration:self.animationDuration];
}

- (CGRect)containerFrame {
  return CGRectMake(0, 0, CGRectGetWidth(self.frame), self.layout.containerHeight);
}

- (UILabel *)leadingAssistiveLabel {
  return self.assistiveLabelView.leadingAssistiveLabel;
}

- (UILabel *)trailingAssistiveLabel {
  return self.assistiveLabelView.trailingAssistiveLabel;
}

#pragma mark Fonts

- (UIFont *)normalFont {
  return self.baseTextAreaTextView.font ?: MDCTextControlDefaultUITextFieldFont();
}

- (UIFont *)floatingFont {
  return [self.containerStyle floatingFontWithNormalFont:self.normalFont];
}

#pragma mark MDCBaseTextAreaTextViewDelegate

- (void)textAreaTextView:(MDCBaseTextAreaTextView *)textView
    willBecomeFirstResponder:(BOOL)willBecome {
  if (textView == self.baseTextAreaTextView) {
    [self setNeedsLayout];
  }
}

- (void)textAreaTextView:(MDCBaseTextAreaTextView *)textView
    willResignFirstResponder:(BOOL)willResign {
  if (textView == self.baseTextAreaTextView) {
    [self setNeedsLayout];
  }
}

#pragma mark Internationalization

- (BOOL)shouldLayoutForRTL {
  if (self.semanticContentAttribute == UISemanticContentAttributeForceRightToLeft) {
    return YES;
  } else if (self.semanticContentAttribute == UISemanticContentAttributeForceLeftToRight) {
    return NO;
  } else {
    return self.mdf_effectiveUserInterfaceLayoutDirection ==
           UIUserInterfaceLayoutDirectionRightToLeft;
  }
}

#pragma mark Coloring

- (void)applyColorViewModel:(MDCTextControlColorViewModel *)colorViewModel
          withLabelPosition:(MDCTextControlLabelPosition)labelPosition {
  UIColor *labelColor = [UIColor clearColor];
  if (labelPosition == MDCTextControlLabelPositionNormal) {
    labelColor = colorViewModel.normalLabelColor;
  } else if (labelPosition == MDCTextControlLabelPositionFloating) {
    labelColor = colorViewModel.floatingLabelColor;
  }
  if (![self.textView.textColor isEqual:colorViewModel.textColor]) {
    self.textView.textColor = colorViewModel.textColor;
  }
  if (![self.leadingAssistiveLabel.textColor isEqual:colorViewModel.leadingAssistiveLabelColor]) {
    self.leadingAssistiveLabel.textColor = colorViewModel.leadingAssistiveLabelColor;
  }
  if (![self.trailingAssistiveLabel.textColor isEqual:colorViewModel.trailingAssistiveLabelColor]) {
    self.trailingAssistiveLabel.textColor = colorViewModel.trailingAssistiveLabelColor;
  }
  if (![self.label.textColor isEqual:labelColor]) {
    self.label.textColor = labelColor;
  }
}

- (void)setTextControlColorViewModel:(MDCTextControlColorViewModel *)TextControlColorViewModel
                            forState:(MDCTextControlState)textControlState {
  self.colorViewModels[@(textControlState)] = TextControlColorViewModel;
}

- (MDCTextControlColorViewModel *)textControlColorViewModelForState:
    (MDCTextControlState)textControlState {
  MDCTextControlColorViewModel *colorViewModel = self.colorViewModels[@(textControlState)];
  if (!colorViewModel) {
    colorViewModel = [[MDCTextControlColorViewModel alloc] initWithState:textControlState];
  }
  return colorViewModel;
}

#pragma mark Color Accessors

- (void)setNormalLabelColor:(nonnull UIColor *)labelColor forState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  colorViewModel.normalLabelColor = labelColor;
  [self setNeedsLayout];
}

- (UIColor *)normalLabelColorForState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  return colorViewModel.normalLabelColor;
}

- (void)setFloatingLabelColor:(nonnull UIColor *)labelColor forState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  colorViewModel.floatingLabelColor = labelColor;
  [self setNeedsLayout];
}

- (UIColor *)floatingLabelColorForState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  return colorViewModel.floatingLabelColor;
}

- (void)setTextColor:(nonnull UIColor *)labelColor forState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  colorViewModel.textColor = labelColor;
  [self setNeedsLayout];
}

- (UIColor *)textColorForState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  return colorViewModel.textColor;
}

- (void)setLeadingAssistiveLabelColor:(nonnull UIColor *)assistiveLabelColor
                             forState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  colorViewModel.leadingAssistiveLabelColor = assistiveLabelColor;
  [self setNeedsLayout];
}

- (UIColor *)leadingAssistiveLabelColorForState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  return colorViewModel.leadingAssistiveLabelColor;
}

- (void)setTrailingAssistiveLabelColor:(nonnull UIColor *)assistiveLabelColor
                              forState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  colorViewModel.trailingAssistiveLabelColor = assistiveLabelColor;
  [self setNeedsLayout];
}

- (UIColor *)trailingAssistiveLabelColorForState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  return colorViewModel.trailingAssistiveLabelColor;
}

#pragma mark User Actions

- (void)handleTap:(UITapGestureRecognizer *)tap {
  if (tap.state == UIGestureRecognizerStateEnded) {
    if (!self.textView.isFirstResponder) {
      [self.textView becomeFirstResponder];
    }
  }
}

- (void)textViewChanged:(NSNotification *)notification {
  if (notification.object == self.baseTextAreaTextView) {
    [self setNeedsLayout];
  }
}

#pragma mark Notifications

- (void)observeTextViewNotifications {
  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(textViewChanged:)
                                               name:UITextViewTextDidChangeNotification
                                             object:nil];
}

#pragma mark - Key-value observing

- (void)observeAssistiveLabelKeyPaths {
  for (NSString *keyPath in [MDCBaseTextArea assistiveLabelKVOKeyPaths]) {
    [self.leadingAssistiveLabel addObserver:self
                                 forKeyPath:keyPath
                                    options:NSKeyValueObservingOptionNew
                                    context:kKVOContextMDCBaseTextArea];
    [self.trailingAssistiveLabel addObserver:self
                                  forKeyPath:keyPath
                                     options:NSKeyValueObservingOptionNew
                                     context:kKVOContextMDCBaseTextArea];
  }
}

- (void)removeObservers {
  for (NSString *keyPath in [MDCBaseTextArea assistiveLabelKVOKeyPaths]) {
    [self.leadingAssistiveLabel removeObserver:self
                                    forKeyPath:keyPath
                                       context:kKVOContextMDCBaseTextArea];
    [self.trailingAssistiveLabel removeObserver:self
                                     forKeyPath:keyPath
                                        context:kKVOContextMDCBaseTextArea];
  }
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary<NSKeyValueChangeKey, id> *)change
                       context:(void *)context {
  if (context == kKVOContextMDCBaseTextArea) {
    if (object != self.leadingAssistiveLabel && object != self.trailingAssistiveLabel) {
      return;
    }

    for (NSString *assistiveLabelKeyPath in [MDCBaseTextArea assistiveLabelKVOKeyPaths]) {
      if ([assistiveLabelKeyPath isEqualToString:keyPath]) {
        [self setNeedsLayout];
        break;
      }
    }
  }
}

+ (NSArray<NSString *> *)assistiveLabelKVOKeyPaths {
  static NSArray<NSString *> *keyPaths = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    keyPaths = @[
      NSStringFromSelector(@selector(text)),
      NSStringFromSelector(@selector(font)),
    ];
  });
  return keyPaths;
}

@end
