// Copyright 2019-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 "MDCBaseTextField.h"

#import <Foundation/Foundation.h>

#import "MaterialTextControls+Enums.h"
#import "MDCTextControlAssistiveLabelDrawPriority.h"
#import <MDFInternationalization/MDFInternationalization.h>

#import "MDCBaseTextFieldDelegate.h"
#import "MaterialTextControlsPrivate+BaseStyle.h"
#import "MaterialTextControlsPrivate+Shared.h"
#import "MaterialTextControlsPrivate+TextFields.h"

static char *const kKVOContextMDCBaseTextField = "kKVOContextMDCBaseTextField";

@interface MDCBaseTextField () <MDCTextControlTextField>

@property(strong, nonatomic) UILabel *label;
@property(nonatomic, strong) MDCTextControlAssistiveLabelView *assistiveLabelView;
@property(strong, nonatomic) MDCBaseTextFieldLayout *layout;
@property(nonatomic, assign) MDCTextControlState textControlState;
@property(nonatomic, assign) MDCTextControlLabelPosition labelPosition;
@property(nonatomic, assign) CGRect labelFrame;
@property(nonatomic, assign) NSTimeInterval animationDuration;
@property(nonatomic, assign) CGFloat lastRecordedWidth;
@property(nonatomic, assign) CGFloat lastCalculatedHeight;

/**
 This property maps MDCTextControlStates as NSNumbers to
 MDCTextControlColorViewModels.
 */
@property(nonatomic, strong)
    NSMutableDictionary<NSNumber *, MDCTextControlColorViewModel *> *colorViewModels;

@end

@implementation MDCBaseTextField
@synthesize containerStyle = _containerStyle;
@synthesize assistiveLabelDrawPriority = _assistiveLabelDrawPriority;
@synthesize customAssistiveLabelDrawPriority = _customAssistiveLabelDrawPriority;

#pragma mark Object Lifecycle

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

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

- (void)commonMDCBaseTextFieldInit {
  [self initializeProperties];
  [self setUpColorViewModels];
  [self setUpLabel];
  [self setUpAssistiveLabels];
  [self observeContentSizeCategoryNotifications];
  [self observeAssistiveLabelKeyPaths];
}

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

#pragma mark View 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];
}

- (void)setUpColorViewModels {
  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];
  CGFloat assistiveFontSize = round([UIFont systemFontSize] * (CGFloat)0.75);
  UIFont *assistiveFont = [UIFont systemFontOfSize:assistiveFontSize];
  self.assistiveLabelView.leadingAssistiveLabel.font = assistiveFont;
  self.assistiveLabelView.trailingAssistiveLabel.font = assistiveFont;
  [self addSubview:self.assistiveLabelView];
}

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

#pragma mark UIResponder Overrides

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
  BOOL canPerformAction = [super canPerformAction:action withSender:sender];
  if ([self.baseTextFieldDelegate
          respondsToSelector:@selector(baseTextField:
                                 shouldPerformAction:withSender:canPerformAction:)]) {
    return [self.baseTextFieldDelegate baseTextField:self
                                 shouldPerformAction:action
                                          withSender:sender
                                    canPerformAction:canPerformAction];
  }
  return canPerformAction;
}

#pragma mark UIView Overrides

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

// UITextField's sizeToFit calls this method and then also calls setNeedsLayout.
// When the system calls this method the size parameter is the view's current size.
- (CGSize)sizeThatFits:(CGSize)size {
  return [self preferredSizeWithWidth:size.width];
}

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

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

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

- (void)setBounds:(CGRect)bounds {
  [super setBounds:bounds];

  // I wouldn't think something like the following is necessary, but in light of b/173646979 I think
  // it is.
  CGFloat newWidth = CGRectGetWidth(bounds);
  BOOL widthHasChanged = newWidth != self.lastRecordedWidth;
  if (widthHasChanged) {
    [self setNeedsLayout];
  }
  self.lastRecordedWidth = CGRectGetWidth(bounds);
}

#pragma mark Layout

/**
 UITextField layout methods such as @c -textRectForBounds: and @c -editingRectForBounds: are called
 within @c -layoutSubviews. The exact values of the CGRects MDCBaseTextField returns from these
 methods depend on many factors, and are calculated alongside all the other frames of
 MDCBaseTextField's subviews. To ensure that these values are known before UITextField's layout
 methods expect them, they are determined by this method, which is called before the superclass's @c
 -layoutSubviews in the layout cycle.
 */
- (void)preLayoutSubviews {
  self.textControlState = [self determineCurrentTextControlState];
  self.labelPosition = [self determineCurrentLabelPosition];
  MDCTextControlColorViewModel *colorViewModel =
      [self textControlColorViewModelForState:self.textControlState];
  [self applyColorViewModel:colorViewModel withLabelPosition:self.labelPosition];
  CGSize fittingSize = CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX);
  self.layout = [self calculateLayoutWithTextFieldSize:fittingSize];
  self.labelFrame = [self.layout labelFrameWithLabelPosition:self.labelPosition];
}

- (void)postLayoutSubviews {
  self.label.hidden = self.labelPosition == MDCTextControlLabelPositionNone;
  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 updateCalculatedHeight];
}

- (void)updateCalculatedHeight {
  if (self.layout.calculatedHeight != self.lastCalculatedHeight) {
    [self invalidateIntrinsicContentSize];
    if ([self.baseTextFieldDelegate respondsToSelector:@selector(baseTextField:
                                                           didUpdateIntrinsicHeight:)]) {
      [self.baseTextFieldDelegate baseTextField:self
                       didUpdateIntrinsicHeight:self.layout.calculatedHeight];
    }
  }
  self.lastCalculatedHeight = self.layout.calculatedHeight;
}

- (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];
  }
}

- (CGRect)textRectFromLayout:(MDCBaseTextFieldLayout *)layout
               labelPosition:(MDCTextControlLabelPosition)labelPosition {
  CGRect textRect = layout.textRectNormal;
  if (labelPosition == MDCTextControlLabelPositionFloating) {
    textRect = layout.textRectFloating;
  }
  return textRect;
}

/**
 To understand this method one must understand that the CGRect UITextField returns from @c
 -textRectForBounds: does not actually represent the CGRect of visible text in UITextField. It
 represents the CGRect of an internal "field editing" class, which has a height that is
 significantly taller than the text (@c font.lineHeight) itself. Providing a height in @c
 -textRectForBounds: that differs from the height determined by the superclass results in a text
 field with poor text rendering, sometimes to the point of the text not being visible. By taking the
 desired CGRect of the visible text from the layout object, giving it the height preferred by the
 superclass's implementation of @c -textRectForBounds:, and then ensuring that this new CGRect has
 the same midY as the original CGRect, we are able to take control of the text's positioning.
 */
- (CGRect)adjustTextAreaFrame:(CGRect)textRect
    withParentClassTextAreaFrame:(CGRect)parentClassTextAreaFrame {
  CGFloat systemDefinedHeight = CGRectGetHeight(parentClassTextAreaFrame);
  CGFloat minY = CGRectGetMidY(textRect) - (systemDefinedHeight * (CGFloat)0.5);
  return CGRectMake(CGRectGetMinX(textRect), minY, CGRectGetWidth(textRect), systemDefinedHeight);
}

- (MDCBaseTextFieldLayout *)calculateLayoutWithTextFieldSize:(CGSize)textFieldSize {
  CGFloat clampedCustomAssistiveLabelDrawPriority =
      [self clampedCustomAssistiveLabelDrawPriority:self.customAssistiveLabelDrawPriority];
  CGFloat clearButtonSideLength = [self clearButtonSideLengthWithTextFieldSize:textFieldSize];
  id<MDCTextControlVerticalPositioningReference> verticalPositioningReference =
      [self createVerticalPositioningReference];
  id<MDCTextControlHorizontalPositioning> horizontalPositioningReference =
      [self createHorizontalPositioningReference];
  return [[MDCBaseTextFieldLayout alloc]
                 initWithTextFieldSize:textFieldSize
                  positioningReference:verticalPositioningReference
        horizontalPositioningReference:horizontalPositioningReference
                                  text:self.text
                                  font:self.normalFont
                          floatingFont:self.floatingFont
                                 label:self.label
                         labelPosition:self.labelPosition
                         labelBehavior:self.labelBehavior
                     sideViewAlignment:self.sideViewAlignment
                           leadingView:self.leadingView
                       leadingViewMode:self.leadingViewMode
                          trailingView:self.trailingView
                      trailingViewMode:self.trailingViewMode
                 clearButtonSideLength:clearButtonSideLength
                       clearButtonMode:self.clearButtonMode
                 leadingAssistiveLabel:self.assistiveLabelView.leadingAssistiveLabel
                trailingAssistiveLabel:self.assistiveLabelView.trailingAssistiveLabel
            assistiveLabelDrawPriority:self.assistiveLabelDrawPriority
      customAssistiveLabelDrawPriority:clampedCustomAssistiveLabelDrawPriority
                                 isRTL:self.shouldLayoutForRTL
                             isEditing:self.isEditing];
}

- (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;
}

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

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

- (CGFloat)clearButtonSideLengthWithTextFieldSize:(CGSize)textFieldSize {
  CGRect bounds = CGRectMake(0, 0, textFieldSize.width, textFieldSize.height);
  CGRect systemPlaceholderRect = [super clearButtonRectForBounds:bounds];
  return systemPlaceholderRect.size.height;
}

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

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

#pragma mark UITextField Accessor Overrides

- (void)setEnabled:(BOOL)enabled {
  [super setEnabled:enabled];

  [self setNeedsLayout];
}

- (void)setLeftViewMode:(UITextFieldViewMode)leftViewMode {
  self.leadingViewMode = leftViewMode;
}

- (UITextFieldViewMode)leftViewMode {
  return self.leadingViewMode;
}

- (void)setRightViewMode:(UITextFieldViewMode)rightViewMode {
  self.trailingViewMode = rightViewMode;
}

- (UITextFieldViewMode)rightViewMode {
  return self.trailingViewMode;
}

/**
 RTL behavior appears to have been added to UITextField some time after the leftView and rightView
 properties. As a result, things don't always behave how one might expect. The values returned from
 -leftViewRectForBounds: and -rightViewRectForBounds: can sometimes be ignored in RTL locales, or
 when semanticContentAttribute is set. To get around this strange behavior from the superclass we
 manage our own leading and trailing views. The setters for the original UITextField leftView and
 rightView properties get forwarded to the setters for leadingView and trailingView, respectively.
 In RTL, -setRightView: will call -setTrailingView:, which will result in a view that gets rendered
 on the left edge of the view, which is consistent with how UITextField behaves.
*/
- (void)setRightView:(UIView *)rightView {
  self.trailingView = rightView;
}

- (UIView *)rightView {
  return self.trailingView;
}

- (void)setLeftView:(UIView *)leftView {
  self.leadingView = leftView;
}

- (UIView *)leftView {
  return self.leadingView;
}

#pragma mark Custom Accessors

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

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

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

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

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

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

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

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

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

#pragma mark MDCTextControl accessors

- (void)setLabelBehavior:(MDCTextControlLabelBehavior)labelBehavior {
  if (_labelBehavior == labelBehavior) {
    return;
  }
  _labelBehavior = labelBehavior;
  [self setNeedsLayout];
}

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

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

- (CGFloat)numberOfLinesOfVisibleText {
  return 1;
}

#pragma mark UITextField Layout Overrides

- (CGRect)textRectForBounds:(CGRect)bounds {
  CGRect textRect = [self textRectFromLayout:self.layout labelPosition:self.labelPosition];
  return [self adjustTextAreaFrame:textRect
      withParentClassTextAreaFrame:[super textRectForBounds:bounds]];
}

- (CGRect)editingRectForBounds:(CGRect)bounds {
  CGRect textRect = [self textRectFromLayout:self.layout labelPosition:self.labelPosition];
  return [self adjustTextAreaFrame:textRect
      withParentClassTextAreaFrame:[super editingRectForBounds:bounds]];
}

- (CGRect)borderRectForBounds:(CGRect)bounds {
  if (!self.containerStyle) {
    return [super borderRectForBounds:bounds];
  }
  return CGRectZero;
}

- (CGRect)clearButtonRectForBounds:(CGRect)bounds {
  return self.layout.clearButtonFrame;
}

- (CGRect)placeholderRectForBounds:(CGRect)bounds {
  if (self.shouldPlaceholderBeVisible) {
    return [super placeholderRectForBounds:bounds];
  }
  return CGRectZero;
}

#pragma mark UITextField Drawing Overrides

- (void)drawPlaceholderInRect:(CGRect)rect {
  if (self.shouldPlaceholderBeVisible) {
    [super drawPlaceholderInRect:rect];
  }
}

#pragma mark Fonts

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

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

#pragma mark Dynamic Type

- (void)setAdjustsFontForContentSizeCategory:(BOOL)adjustsFontForContentSizeCategory {
  [super setAdjustsFontForContentSizeCategory:adjustsFontForContentSizeCategory];
  self.leadingAssistiveLabel.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
  self.trailingAssistiveLabel.adjustsFontForContentSizeCategory = adjustsFontForContentSizeCategory;
}

- (void)observeContentSizeCategoryNotifications {
  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(contentSizeCategoryDidChange:)
                                               name:UIContentSizeCategoryDidChangeNotification
                                             object:nil];
}

- (void)contentSizeCategoryDidChange:(NSNotification *)notification {
  [self setNeedsLayout];
}

#pragma mark MDCTextControlState

- (MDCTextControlState)determineCurrentTextControlState {
  return MDCTextControlStateWith(self.isEnabled, self.isEditing);
}

#pragma mark Placeholder

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

#pragma mark Label

- (void)animateLabel {
  __weak MDCBaseTextField *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.text.length > 0,
                                         self.canLabelFloat, self.isEditing);
}

#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.textColor isEqual:colorViewModel.textColor]) {
    self.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 *)colorViewModel
                            forState:(MDCTextControlState)textControlState {
  if (colorViewModel) {
    self.colorViewModels[@(textControlState)] = colorViewModel;
  }
}

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

#pragma mark MDCTextControlTextField

- (MDCTextControlTextFieldSideViewAlignment)sideViewAlignment {
  return MDCTextControlTextFieldSideViewAlignmentCenteredInContainer;
}

#pragma mark Accessibility Overrides

- (NSString *)accessibilityLabel {
  NSString *superAccessibilityLabel = [super accessibilityLabel];
  if (superAccessibilityLabel.length > 0) {
    return superAccessibilityLabel;
  }

  NSMutableArray *accessibilityLabelComponents = [NSMutableArray new];
  if (self.label.text.length > 0) {
    [accessibilityLabelComponents addObject:self.label.text];
  }
  if (self.leadingAssistiveLabel.text.length > 0) {
    [accessibilityLabelComponents addObject:self.leadingAssistiveLabel.text];
  }
  if (self.trailingAssistiveLabel.text.length > 0) {
    [accessibilityLabelComponents addObject:self.trailingAssistiveLabel.text];
  }

  if (accessibilityLabelComponents.count > 0) {
    return [accessibilityLabelComponents componentsJoinedByString:@", "];
  }

  return nil;
}

- (UIBezierPath *)accessibilityPath {
  return [UIBezierPath bezierPathWithRect:self.accessibilityFrame];
}

- (CGRect)accessibilityFrame {
  if (self.window) {
    CGRect bounds = self.bounds;
    CGFloat boundsMinY = CGRectGetMinY(bounds);
    CGFloat floatingLabelMinY = CGRectGetMinY(self.layout.labelFrameFloating);
    if (floatingLabelMinY < boundsMinY) {
      CGFloat difference = boundsMinY - floatingLabelMinY;
      bounds.origin.y = boundsMinY - difference;
      bounds.size.height = bounds.size.height + difference;
    }
    return [self convertRect:bounds toCoordinateSpace:self.window.screen.coordinateSpace];
  } else {
    return [super accessibilityFrame];
  }
}

#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 *)leadingAssistiveLabelColor
                             forState:(MDCTextControlState)state {
  MDCTextControlColorViewModel *colorViewModel = [self textControlColorViewModelForState:state];
  colorViewModel.leadingAssistiveLabelColor = leadingAssistiveLabelColor;
  [self setNeedsLayout];
}

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

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

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

#pragma mark UIKeyInput

- (void)deleteBackward {
  BOOL shouldDeleteBackward = YES;
  if ([self.baseTextFieldDelegate
          respondsToSelector:@selector(baseTextFieldShouldDeleteBackward:)]) {
    shouldDeleteBackward = [self.baseTextFieldDelegate baseTextFieldShouldDeleteBackward:self];
  }

  if (shouldDeleteBackward) {
    [super deleteBackward];
    if ([self.baseTextFieldDelegate
            respondsToSelector:@selector(baseTextFieldDidDeleteBackward:)]) {
      [self.baseTextFieldDelegate baseTextFieldDidDeleteBackward:self];
    }
  }
}

#pragma mark - Key-value observing

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

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

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

    for (NSString *assistiveLabelKeyPath in [MDCBaseTextField 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
