//
//  NBAsYouTypeFormatter.m
//  libPhoneNumber
//
//  Created by ishtar on 13. 2. 25..
//

#import "NBAsYouTypeFormatter.h"
#import "NBPhoneNumberDefines.h"

#import "NBMetadataHelper.h"

#import "NBNumberFormat.h"
#import "NBPhoneMetaData.h"
#import "NBPhoneNumberUtil.h"
#import "NSArray+NBAdditions.h"

/**
 * The digits that have not been entered yet will be represented by a \u2008,
 * the punctuation space.
 */
static NSString *const NBDigitPlaceHolder = @"\u2008";
/**
 * Character used when appropriate to separate a prefix, such as a long NDD or a
 * country calling code, from the national number.
 */
static NSString *const NBSeparatorBeforeNationalNumber = @" ";

/**
 * This is the minimum length of national number accrued that is required to
 * trigger the formatter. The first element of the leadingDigitsPattern of
 * each numberFormat contains a regular expression that matches up to this
 * number of digits.
 */
static const NSUInteger NBMinLeadingDigitsLength = 3;

@interface NBAsYouTypeFormatter ()

@property(nonatomic, strong, readwrite) NSString *currentOutput_, *currentFormattingPattern_;
@property(nonatomic, strong, readwrite) NSString *defaultCountry_;
@property(nonatomic, strong, readwrite) NSString *nationalPrefixExtracted_;
@property(nonatomic, strong, readwrite) NSMutableString *formattingTemplate_, *accruedInput_,
    *prefixBeforeNationalNumber_, *accruedInputWithoutFormatting_, *nationalNumber_;
@property(nonatomic, strong, readwrite) NSRegularExpression *DIGIT_PATTERN_,
    *NATIONAL_PREFIX_SEPARATORS_PATTERN_, *CHARACTER_CLASS_PATTERN_, *STANDALONE_DIGIT_PATTERN_;
@property(nonatomic, strong, readwrite) NSRegularExpression *ELIGIBLE_FORMAT_PATTERN_;
@property(nonatomic, assign, readwrite) BOOL ableToFormat_, inputHasFormatting_, isCompleteNumber_,
    isExpectingCountryCallingCode_, shouldAddSpaceAfterNationalPrefix_;
@property(nonatomic, strong, readwrite) NBPhoneNumberUtil *phoneUtil_;
@property(nonatomic, assign, readwrite) NSUInteger lastMatchPosition_, originalPosition_,
    positionToRemember_;
@property(nonatomic, strong, readwrite) NSMutableArray *possibleFormats_;
@property(nonatomic, strong, readwrite) NBPhoneMetaData *currentMetaData_, *defaultMetaData_;

@end

@implementation NBAsYouTypeFormatter

- (instancetype)init {
  self = [super init];

  if (self) {
    _isSuccessfulFormatting = NO;

    /**
     * @type {string}
     * @private
     */
    self.currentOutput_ = @"";

    /**
     * @type {!goog.string.StringBuffer}
     * @private
     */
    self.formattingTemplate_ = [[NSMutableString alloc] init];

    NSError *anError = nil;

    /**
     * @type {RegExp}
     * @private
     */
    self.DIGIT_PATTERN_ = [NSRegularExpression regularExpressionWithPattern:NBDigitPlaceHolder
                                                                    options:0
                                                                      error:&anError];

    /**
     * A set of characters that, if found in a national prefix formatting rules, are
     * an indicator to us that we should separate the national prefix from the
     * number when formatting.
     * @const
     * @type {RegExp}
     * @private
     */
    self.NATIONAL_PREFIX_SEPARATORS_PATTERN_ =
        [NSRegularExpression regularExpressionWithPattern:@"[- ]" options:0 error:&anError];

    /**
     * A pattern that is used to match character classes in regular expressions.
     * An example of a character class is [1-4].
     * @const
     * @type {RegExp}
     * @private
     */
    self.CHARACTER_CLASS_PATTERN_ =
        [NSRegularExpression regularExpressionWithPattern:@"\\[([^\\[\\]])*\\]"
                                                  options:0
                                                    error:&anError];

    /**
     * Any digit in a regular expression that actually denotes a digit. For
     * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
     * (8 and 0) are standalone digits, but the rest are not.
     * Two look-aheads are needed because the number following \\d could be a
     * two-digit number, since the phone number can be as long as 15 digits.
     * @const
     * @type {RegExp}
     * @private
     */
    self.STANDALONE_DIGIT_PATTERN_ =
        [NSRegularExpression regularExpressionWithPattern:@"\\d(?=[^,}][^,}])"
                                                  options:0
                                                    error:&anError];

    /**
     * A pattern that is used to determine if a numberFormat under availableFormats
     * is eligible to be used by the AYTF. It is eligible when the format element
     * under numberFormat contains groups of the dollar sign followed by a single
     * digit, separated by valid phone number punctuation. This prevents invalid
     * punctuation (such as the star sign in Israeli star numbers) getting into the
     * output of the AYTF.
     * @const
     * @type {RegExp}
     * @private
     */
    NSString *eligible_format =
        @"^[-x‐-―−ー－-／ ­​⁠　()（）［］.\\[\\]/~⁓∼～]*(\\$\\d[-x‐-―−ー－-／ "
        @"­​⁠　()（）［］.\\[\\]/~⁓∼～]*)+$";
    self.ELIGIBLE_FORMAT_PATTERN_ =
        [NSRegularExpression regularExpressionWithPattern:eligible_format options:0 error:&anError];

    /**
     * The pattern from numberFormat that is currently used to create
     * formattingTemplate.
     * @type {string}
     * @private
     */
    self.currentFormattingPattern_ = @"";

    /**
     * @type {!goog.string.StringBuffer}
     * @private
     */
    self.accruedInput_ = [[NSMutableString alloc] init];

    /**
     * @type {!goog.string.StringBuffer}
     * @private
     */
    self.accruedInputWithoutFormatting_ = [[NSMutableString alloc] init];

    /**
     * This indicates whether AsYouTypeFormatter is currently doing the
     * formatting.
     * @type {BOOL}
     * @private
     */
    self.ableToFormat_ = YES;

    /**
     * Set to YES when users enter their own formatting. AsYouTypeFormatter will
     * do no formatting at all when this is set to YES.
     * @type {BOOL}
     * @private
     */
    self.inputHasFormatting_ = NO;

    /**
     * This is set to YES when we know the user is entering a full national
     * significant number, since we have either detected a national prefix or an
     * international dialing prefix. When this is YES, we will no longer use
     * local number formatting patterns.
     * @type {BOOL}
     * @private
     */
    self.isCompleteNumber_ = NO;

    /**
     * @type {BOOL}
     * @private
     */
    self.isExpectingCountryCallingCode_ = NO;

    /**
     * @type {number}
     * @private
     */
    self.lastMatchPosition_ = 0;

    /**
     * The position of a digit upon which inputDigitAndRememberPosition is most
     * recently invoked, as found in the original sequence of characters the user
     * entered.
     * @type {number}
     * @private
     */
    self.originalPosition_ = 0;

    /**
     * The position of a digit upon which inputDigitAndRememberPosition is most
     * recently invoked, as found in accruedInputWithoutFormatting.
     * entered.
     * @type {number}
     * @private
     */
    self.positionToRemember_ = 0;

    /**
     * This contains anything that has been entered so far preceding the national
     * significant number, and it is formatted (e.g. with space inserted). For
     * example, this can contain IDD, country code, and/or NDD, etc.
     * @type {!goog.string.StringBuffer}
     * @private
     */
    self.prefixBeforeNationalNumber_ = [[NSMutableString alloc] init];

    /**
     * @type {BOOL}
     * @private
     */
    self.shouldAddSpaceAfterNationalPrefix_ = NO;

    /**
     * This contains the national prefix that has been extracted. It contains only
     * digits without formatting.
     * @type {string}
     * @private
     */
    self.nationalPrefixExtracted_ = @"";

    /**
     * @type {!goog.string.StringBuffer}
     * @private
     */
    self.nationalNumber_ = [[NSMutableString alloc] init];

    /**
     * @type {Array.<i18n.phonenumbers.NumberFormat>}
     * @private
     */
    self.possibleFormats_ = [[NSMutableArray alloc] init];
  }

  return self;
}

/**
 * Constructs an AsYouTypeFormatter for the specific region.
 *
 * - param regionCode regionCode the ISO 3166-1 two-letter region code that denotes
 *     the region where the phone number is being entered.
 * @constructor
 */

- (instancetype)initWithRegionCode:(NSString *)regionCode {
  return [self initWithRegionCode:regionCode bundle:[NSBundle mainBundle]];
}

- (instancetype)initWithRegionCode:(NSString *)regionCode bundle:(NSBundle *)bundle {
  self = [self init];
  if (self) {
    /**
     * @private
     * @type {i18n.phonenumbers.PhoneNumberUtil}
     */
    self.phoneUtil_ = [NBPhoneNumberUtil sharedInstance];
    self.defaultCountry_ = regionCode;
    self.currentMetaData_ = [self getMetadataForRegion_:self.defaultCountry_];
    /**
     * @type {i18n.phonenumbers.PhoneMetadata}
     * @private
     */
    self.defaultMetaData_ = self.currentMetaData_;
  }

  return self;
}

/**
 * The metadata needed by this class is the same for all regions sharing the
 * same country calling code. Therefore, we return the metadata for "main"
 * region for this country calling code.
 * - param regionCode regionCode an ISO 3166-1 two-letter region code.
 * @return {i18n.phonenumbers.PhoneMetadata} main metadata for this region.
 * @private
 */
- (NBPhoneMetaData *)getMetadataForRegion_:(NSString *)regionCode {
  NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
  /** @type {number} */
  NSNumber *countryCallingCode = [self.phoneUtil_ getCountryCodeForRegion:regionCode];
  /** @type {string} */
  NSString *mainCountry = [self.phoneUtil_ getRegionCodeForCountryCode:countryCallingCode];
  /** @type {i18n.phonenumbers.PhoneMetadata} */
  NBPhoneMetaData *metadata = [helper getMetadataForRegion:mainCountry];
  if (metadata != nil) {
    return metadata;
  }
  // Set to a default instance of the metadata. This allows us to function with
  // an incorrect region code, even if formatting only works for numbers
  // specified with '+'.
  return [[NBPhoneMetaData alloc] init];
}

/**
 * @return {BOOL} YES if a new template is created as opposed to reusing the
 *     existing template.
 * @private
 */
- (BOOL)maybeCreateNewTemplate_ {
  // When there are multiple available formats, the formatter uses the first
  // format where a formatting template could be created.
  /** @type {number} */
  NSUInteger possibleFormatsLength = [self.possibleFormats_ count];
  for (NSUInteger i = 0; i < possibleFormatsLength; ++i) {
    /** @type {i18n.phonenumbers.NumberFormat} */
    NBNumberFormat *numberFormat =
        [self.possibleFormats_ nb_safeObjectAtIndex:i class:[NBNumberFormat class]];
    /** @type {string} */
    NSString *pattern = numberFormat.pattern;

    if (!pattern.length || [self.currentFormattingPattern_ isEqualToString:pattern]) {
      return NO;
    }

    if ([self createFormattingTemplate_:numberFormat]) {
      self.currentFormattingPattern_ = pattern;
      NSRange nationalPrefixRange =
          NSMakeRange(0, [numberFormat.nationalPrefixFormattingRule length]);
      if (nationalPrefixRange.length > 0) {
        NSTextCheckingResult *matchResult = [self.NATIONAL_PREFIX_SEPARATORS_PATTERN_
            firstMatchInString:numberFormat.nationalPrefixFormattingRule
                       options:0
                         range:nationalPrefixRange];
        self.shouldAddSpaceAfterNationalPrefix_ = (matchResult != nil);
      } else {
        self.shouldAddSpaceAfterNationalPrefix_ = NO;
      }
      // With a new formatting template, the matched position using the old
      // template needs to be reset.
      self.lastMatchPosition_ = 0;
      return YES;
    }
  }
  self.ableToFormat_ = NO;
  return NO;
}

/**
 * - param leadingDigits leadingThreeDigits first three digits of entered number.
 * @private
 */
- (void)getAvailableFormats_:(NSString *)leadingDigits {
  /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
  BOOL isIntlNumberFormats =
      (self.isCompleteNumber_ && self.currentMetaData_.intlNumberFormats.count > 0);
  NSArray *formatList = isIntlNumberFormats ? self.currentMetaData_.intlNumberFormats
                                            : self.currentMetaData_.numberFormats;

  /** @type {number} */
  NSUInteger formatListLength = formatList.count;

  for (NSUInteger i = 0; i < formatListLength; ++i) {
    /** @type {i18n.phonenumbers.NumberFormat} */
    NBNumberFormat *format = [formatList nb_safeObjectAtIndex:i class:[NBNumberFormat class]];
    /** @type {BOOL} */
    BOOL nationalPrefixIsUsedByCountry =
        (self.currentMetaData_.nationalPrefix && self.currentMetaData_.nationalPrefix.length > 0);

    if (!nationalPrefixIsUsedByCountry || self.isCompleteNumber_ ||
        format.nationalPrefixOptionalWhenFormatting ||
        [self.phoneUtil_ formattingRuleHasFirstGroupOnly:format.nationalPrefixFormattingRule]) {
      if ([self isFormatEligible_:format.format]) {
        [self.possibleFormats_ addObject:format];
      }
    }
  }

  [self narrowDownPossibleFormats_:leadingDigits];
}

/**
 * - param format format
 * @return {BOOL}
 * @private
 */
- (BOOL)isFormatEligible_:(NSString *)format {
  if (!format.length) {
    return NO;
  }
  NSTextCheckingResult *matchResult =
      [self.ELIGIBLE_FORMAT_PATTERN_ firstMatchInString:format
                                                options:0
                                                  range:NSMakeRange(0, [format length])];
  return (matchResult != nil);
}

/**
 * - param leadingDigits leadingDigits
 * @private
 */
- (void)narrowDownPossibleFormats_:(NSString *)leadingDigits {
  /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
  NSMutableArray *possibleFormats = [[NSMutableArray alloc] init];
  /** @type {number} */
  NSUInteger indexOfLeadingDigitsPattern = leadingDigits.length - NBMinLeadingDigitsLength;
  /** @type {number} */
  NSUInteger possibleFormatsLength = self.possibleFormats_.count;

  for (NSUInteger i = 0; i < possibleFormatsLength; ++i) {
    /** @type {i18n.phonenumbers.NumberFormat} */
    NBNumberFormat *format =
        [self.possibleFormats_ nb_safeObjectAtIndex:i class:[NBNumberFormat class]];

    if (format.leadingDigitsPatterns.count == 0) {
      // Keep everything that isn't restricted by leading digits.
      [possibleFormats addObject:format];
      continue;
    }

    /** @type {number} */
    NSInteger lastLeadingDigitsPattern =
        MIN(indexOfLeadingDigitsPattern, format.leadingDigitsPatterns.count - 1);

    /** @type {string} */
    NSString *leadingDigitsPattern =
        [format.leadingDigitsPatterns nb_safeStringAtIndex:lastLeadingDigitsPattern];

    if ([self.phoneUtil_ stringPositionByRegex:leadingDigits regex:leadingDigitsPattern] == 0) {
      [possibleFormats addObject:format];
    }
  }
  self.possibleFormats_ = possibleFormats;
}

/**
 * - param {i18n.phonenumbers.NumberFormat} format
 * @return {BOOL}
 * @private
 */
- (BOOL)createFormattingTemplate_:(NBNumberFormat *)format {
  /** @type {string} */
  NSString *numberPattern = format.pattern;

  // The formatter doesn't format numbers when numberPattern contains '|', e.g.
  // (20|3)\d{4}. In those cases we quickly return.
  NSRange stringRange = [numberPattern rangeOfString:@"|"];
  if (stringRange.location != NSNotFound) {
    return NO;
  }

  // Replace anything in the form of [..] with \d
  numberPattern = [self.CHARACTER_CLASS_PATTERN_
      stringByReplacingMatchesInString:numberPattern
                               options:0
                                 range:NSMakeRange(0, [numberPattern length])
                          withTemplate:@"\\\\d"];

  // Replace any standalone digit (not the one in d{}) with \d
  numberPattern = [self.STANDALONE_DIGIT_PATTERN_
      stringByReplacingMatchesInString:numberPattern
                               options:0
                                 range:NSMakeRange(0, [numberPattern length])
                          withTemplate:@"\\\\d"];
  [self.formattingTemplate_ setString:@""];

  /** @type {string} */
  NSString *tempTemplate = [self getFormattingTemplate_:numberPattern numberFormat:format.format];
  if (tempTemplate.length > 0) {
    [self.formattingTemplate_ appendString:tempTemplate];
    return YES;
  }
  return NO;
}

/**
 * Gets a formatting template which can be used to efficiently format a
 * partial number where digits are added one by one.
 *
 * - param {string} numberPattern
 * - param {string} numberFormat
 * @return {string}
 * @private
 */
- (NSString *)getFormattingTemplate_:(NSString *)numberPattern
                        numberFormat:(NSString *)numberFormat {
  // Creates a phone number consisting only of the digit 9 that matches the
  // numberPattern by applying the pattern to the longestPhoneNumber string.
  /** @type {string} */
  NSString *longestPhoneNumber = @"999999999999999";

  /** @type {Array.<string>} */
  NSArray *m = [self.phoneUtil_ matchedStringByRegex:longestPhoneNumber regex:numberPattern];

  // this match will always succeed
  /** @type {string} */
  NSString *aPhoneNumber = [m nb_safeStringAtIndex:0];
  // No formatting template can be created if the number of digits entered so
  // far is longer than the maximum the current formatting rule can accommodate.
  if (aPhoneNumber.length < self.nationalNumber_.length) {
    return @"";
  }
  // Formats the number according to numberFormat
  /** @type {string} */
  NSString *template = [self.phoneUtil_ replaceStringByRegex:aPhoneNumber
                                                       regex:numberPattern
                                                withTemplate:numberFormat];

  // Replaces each digit with character DIGIT_PLACEHOLDER
  template =
      [self.phoneUtil_ replaceStringByRegex:template regex:@"9" withTemplate:NBDigitPlaceHolder];
  return template;
}

/**
 * Clears the internal state of the formatter, so it can be reused.
 */
- (void)clear {
  self.currentOutput_ = @"";
  [self.accruedInput_ setString:@""];
  [self.accruedInputWithoutFormatting_ setString:@""];
  [self.formattingTemplate_ setString:@""];
  self.lastMatchPosition_ = 0;
  self.currentFormattingPattern_ = @"";
  [self.prefixBeforeNationalNumber_ setString:@""];
  self.nationalPrefixExtracted_ = @"";
  [self.nationalNumber_ setString:@""];
  self.ableToFormat_ = YES;
  self.inputHasFormatting_ = NO;
  self.positionToRemember_ = 0;
  self.originalPosition_ = 0;
  self.isCompleteNumber_ = NO;
  self.isExpectingCountryCallingCode_ = NO;
  [self.possibleFormats_ removeAllObjects];
  self.shouldAddSpaceAfterNationalPrefix_ = NO;

  if (self.currentMetaData_ != self.defaultMetaData_) {
    self.currentMetaData_ = [self getMetadataForRegion_:self.defaultCountry_];
  }
}

- (NSString *)removeLastDigitAndRememberPosition {
  NSString *accruedInputWithoutFormatting = [self.accruedInput_ copy];
  [self clear];

  NSString *result = @"";

  NSUInteger length = accruedInputWithoutFormatting.length;
  if (length == 0) {
    return result;
  }

  for (NSUInteger i = 0; i < length - 1; i++) {
    NSString *ch = [accruedInputWithoutFormatting substringWithRange:NSMakeRange(i, 1)];
    result = [self inputDigitAndRememberPosition:ch];
  }

  return result;
}

- (NSString *)removeLastDigit {
  NSString *accruedInputWithoutFormatting = [self.accruedInput_ copy];
  [self clear];

  NSString *result = @"";

  NSUInteger length = accruedInputWithoutFormatting.length;
  if (length == 0) {
    return result;
  }

  for (NSUInteger i = 0; i < length - 1; i++) {
    NSString *ch = [accruedInputWithoutFormatting substringWithRange:NSMakeRange(i, 1)];
    result = [self inputDigit:ch];
  }

  return result;
}

- (NSString *)inputStringAndRememberPosition:(NSString *)string {
  [self clear];

  NSString *result = @"";

  NSUInteger length = string.length;
  for (NSUInteger i = 0; i < length; i++) {
    NSString *ch = [string substringWithRange:NSMakeRange(i, 1)];
    result = [self inputDigitAndRememberPosition:ch];
  }

  return result;
}

- (NSString *)inputString:(NSString *)string {
  [self clear];

  NSString *result = @"";

  NSUInteger length = string.length;
  for (NSUInteger i = 0; i < length; i++) {
    NSString *ch = [string substringWithRange:NSMakeRange(i, 1)];
    result = [self inputDigit:ch];
  }

  return result;
}

/**
 * Formats a phone number on-the-fly as each digit is entered.
 *
 * - param {string} nextChar the most recently entered digit of a phone number.
 *     Formatting characters are allowed, but as soon as they are encountered
 *     this method formats the number as entered and not 'as you type' anymore.
 *     Full width digits and Arabic-indic digits are allowed, and will be shown
 *     as they are.
 * @return {string} the partially formatted phone number.
 */
- (NSString *)inputDigit:(NSString *)nextChar {
  if (!nextChar || nextChar.length <= 0) {
    return self.currentOutput_;
  }
  self.currentOutput_ = [self inputDigitWithOptionToRememberPosition_:nextChar rememberPosition:NO];
  return self.currentOutput_;
}

/**
 * Same as {@link #inputDigit}, but remembers the position where
 * {@code nextChar} is inserted, so that it can be retrieved later by using
 * {@link #getRememberedPosition}. The remembered position will be automatically
 * adjusted if additional formatting characters are later inserted/removed in
 * front of {@code nextChar}.
 *
 * - param {string} nextChar
 * @return {string}
 */
- (NSString *)inputDigitAndRememberPosition:(NSString *)nextChar {
  if (!nextChar || nextChar.length <= 0) {
    return self.currentOutput_;
  }
  self.currentOutput_ =
      [self inputDigitWithOptionToRememberPosition_:nextChar rememberPosition:YES];
  return self.currentOutput_;
}

/**
 * - param {string} nextChar
 * - param {BOOL} rememberPosition
 * @return {string}
 * @private
 */

- (NSString *)inputDigitWithOptionToRememberPosition_:(NSString *)nextChar
                                     rememberPosition:(BOOL)rememberPosition {
  if (!nextChar || nextChar.length <= 0) {
    _isSuccessfulFormatting = NO;
    return self.currentOutput_;
  }

  [self.accruedInput_ appendString:nextChar];

  if (rememberPosition) {
    self.originalPosition_ = self.accruedInput_.length;
  }

  // We do formatting on-the-fly only when each character entered is either a
  // digit, or a plus sign (accepted at the start of the number only).
  if (![self isDigitOrLeadingPlusSign_:nextChar]) {
    self.ableToFormat_ = NO;
    self.inputHasFormatting_ = YES;
  } else {
    nextChar =
        [self normalizeAndAccrueDigitsAndPlusSign_:nextChar rememberPosition:rememberPosition];
  }

  if (!self.ableToFormat_) {
    // When we are unable to format because of reasons other than that
    // formatting chars have been entered, it can be due to really long IDDs or
    // NDDs. If that is the case, we might be able to do formatting again after
    // extracting them.

    if (self.inputHasFormatting_) {
      _isSuccessfulFormatting = YES;
      return [NSString stringWithString:self.accruedInput_];
    } else if ([self attemptToExtractIdd_]) {
      if ([self attemptToExtractCountryCallingCode_]) {
        _isSuccessfulFormatting = YES;
        return [self attemptToChoosePatternWithPrefixExtracted_];
      }
    } else if ([self ableToExtractLongerNdd_]) {
      // Add an additional space to separate long NDD and national significant
      // number for readability. We don't set shouldAddSpaceAfterNationalPrefix_
      // to YES, since we don't want this to change later when we choose
      // formatting templates.
      [self.prefixBeforeNationalNumber_ appendString:NBSeparatorBeforeNationalNumber];
      _isSuccessfulFormatting = YES;
      return [self attemptToChoosePatternWithPrefixExtracted_];
    }

    _isSuccessfulFormatting = NO;
    return self.accruedInput_;
  }

  // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
  // digits (the plus sign is counted as a digit as well for this purpose) have
  // been entered.
  switch (self.accruedInputWithoutFormatting_.length) {
    case 0:
    case 1:
    case 2:
      _isSuccessfulFormatting = YES;
      return self.accruedInput_;
    case 3:
      if ([self attemptToExtractIdd_]) {
        self.isExpectingCountryCallingCode_ = YES;
      } else {
        // No IDD or plus sign is found, might be entering in national format.
        self.nationalPrefixExtracted_ = [self removeNationalPrefixFromNationalNumber_];
        _isSuccessfulFormatting = YES;
        return [self attemptToChooseFormattingPattern_];
      }
    default:
      if (self.isExpectingCountryCallingCode_) {
        if ([self attemptToExtractCountryCallingCode_]) {
          self.isExpectingCountryCallingCode_ = NO;
        }
        _isSuccessfulFormatting = YES;
        return [NSString
            stringWithFormat:@"%@%@", self.prefixBeforeNationalNumber_, self.nationalNumber_];
      }

      if (self.possibleFormats_.count > 0) {
        // The formatting patterns are already chosen.
        /** @type {string} */
        NSString *tempNationalNumber = [self inputDigitHelper_:nextChar];
        // See if the accrued digits can be formatted properly already. If not,
        // use the results from inputDigitHelper, which does formatting based on
        // the formatting pattern chosen.
        /** @type {string} */
        NSString *formattedNumber = [self attemptToFormatAccruedDigits_];
        if (formattedNumber.length > 0) {
          _isSuccessfulFormatting = YES;
          return formattedNumber;
        }

        [self narrowDownPossibleFormats_:self.nationalNumber_];

        if ([self maybeCreateNewTemplate_]) {
          _isSuccessfulFormatting = YES;
          return [self inputAccruedNationalNumber_];
        }

        if (self.ableToFormat_) {
          _isSuccessfulFormatting = YES;
          return [self appendNationalNumber_:tempNationalNumber];
        } else {
          _isSuccessfulFormatting = NO;
          return self.accruedInput_;
        }
      } else {
        _isSuccessfulFormatting = NO;
        return [self attemptToChooseFormattingPattern_];
      }
  }

  _isSuccessfulFormatting = NO;
}

/**
 * @return {string}
 * @private
 */
- (NSString *)attemptToChoosePatternWithPrefixExtracted_ {
  self.ableToFormat_ = YES;
  self.isExpectingCountryCallingCode_ = NO;
  [self.possibleFormats_ removeAllObjects];
  return [self attemptToChooseFormattingPattern_];
}

/**
 * Some national prefixes are a substring of others. If extracting the shorter
 * NDD doesn't result in a number we can format, we try to see if we can extract
 * a longer version here.
 * @return {BOOL}
 * @private
 */
- (BOOL)ableToExtractLongerNdd_ {
  if (self.nationalPrefixExtracted_.length > 0) {
    // Put the extracted NDD back to the national number before attempting to
    // extract a new NDD.
    /** @type {string} */
    NSString *nationalNumberStr = [NSString stringWithString:self.nationalNumber_];
    self.nationalNumber_ = [self.nationalPrefixExtracted_ mutableCopy];
    [self.nationalNumber_ appendString:nationalNumberStr];
    // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
    // cannot simply set it to empty string because people sometimes incorrectly
    // enter national prefix after the country code, e.g. +44 (0)20-1234-5678.
    /** @type {string} */
    NSString *prefixBeforeNationalNumberStr = [self.prefixBeforeNationalNumber_ copy];
    NSRange lastRange = [prefixBeforeNationalNumberStr rangeOfString:self.nationalPrefixExtracted_
                                                             options:NSBackwardsSearch];
    /** @type {number} */
    NSUInteger indexOfPreviousNdd = lastRange.location;
    self.prefixBeforeNationalNumber_ = [[prefixBeforeNationalNumberStr
        substringWithRange:NSMakeRange(0, indexOfPreviousNdd)] mutableCopy];
  }

  return self.nationalPrefixExtracted_ != [self removeNationalPrefixFromNationalNumber_];
}

/**
 * - param {string} nextChar
 * @return {BOOL}
 * @private
 */
- (BOOL)isDigitOrLeadingPlusSign_:(NSString *)nextChar {
  NSString *digitPattern = [NSString stringWithFormat:@"([%@])", NB_VALID_DIGITS_STRING];
  NSString *plusPattern = [NSString stringWithFormat:@"[%@]+", NB_PLUS_CHARS];

  BOOL isDigitPattern = [[self.phoneUtil_ matchesByRegex:nextChar regex:digitPattern] count] > 0;
  BOOL isPlusPattern = [[self.phoneUtil_ matchesByRegex:nextChar regex:plusPattern] count] > 0;

  return isDigitPattern || (self.accruedInput_.length == 1 && isPlusPattern);
}

/**
 * Check to see if there is an exact pattern match for these digits. If so, we
 * should use this instead of any other formatting template whose
 * leadingDigitsPattern also matches the input.
 * @return {string}
 * @private
 */
- (NSString *)attemptToFormatAccruedDigits_ {
  /** @type {string} */
  NSString *nationalNumber = [NSString stringWithString:self.nationalNumber_];

  /** @type {number} */
  NSUInteger possibleFormatsLength = self.possibleFormats_.count;
  for (NSUInteger i = 0; i < possibleFormatsLength; ++i) {
    /** @type {i18n.phonenumbers.NumberFormat} */
    NBNumberFormat *numberFormat = self.possibleFormats_[i];
    /** @type {string} */
    NSString *pattern = numberFormat.pattern;
    /** @type {RegExp} */
    NSString *patternRegExp = [NSString stringWithFormat:@"^(?:%@)$", pattern];
    BOOL isPatternRegExp =
        [[self.phoneUtil_ matchesByRegex:nationalNumber regex:patternRegExp] count] > 0;
    if (isPatternRegExp) {
      if (numberFormat.nationalPrefixFormattingRule.length > 0) {
        NSArray *matches = [self.NATIONAL_PREFIX_SEPARATORS_PATTERN_
            matchesInString:numberFormat.nationalPrefixFormattingRule
                    options:0
                      range:NSMakeRange(0, numberFormat.nationalPrefixFormattingRule.length)];
        self.shouldAddSpaceAfterNationalPrefix_ = [matches count] > 0;
      } else {
        self.shouldAddSpaceAfterNationalPrefix_ = NO;
      }

      /** @type {string} */
      NSString *formattedNumber = [self.phoneUtil_ replaceStringByRegex:nationalNumber
                                                                  regex:pattern
                                                           withTemplate:numberFormat.format];
      return [self appendNationalNumber_:formattedNumber];
    }
  }
  return @"";
}

/**
 * Combines the national number with any prefix (IDD/+ and country code or
 * national prefix) that was collected. A space will be inserted between them if
 * the current formatting template indicates this to be suitable.
 * - param {string} nationalNumber The number to be appended.
 * @return {string} The combined number.
 * @private
 */
- (NSString *)appendNationalNumber_:(NSString *)nationalNumber {
  /** @type {number} */
  NSUInteger prefixBeforeNationalNumberLength = self.prefixBeforeNationalNumber_.length;
  unichar blank_char = [NBSeparatorBeforeNationalNumber characterAtIndex:0];
  if (self.shouldAddSpaceAfterNationalPrefix_ && prefixBeforeNationalNumberLength > 0 &&
      [self.prefixBeforeNationalNumber_ characterAtIndex:prefixBeforeNationalNumberLength - 1] !=
          blank_char) {
    // We want to add a space after the national prefix if the national prefix
    // formatting rule indicates that this would normally be done, with the
    // exception of the case where we already appended a space because the NDD
    // was surprisingly long.

    return [NSString stringWithFormat:@"%@%@%@", self.prefixBeforeNationalNumber_,
                                      NBSeparatorBeforeNationalNumber, nationalNumber];
  } else {
    return [NSString stringWithFormat:@"%@%@", self.prefixBeforeNationalNumber_, nationalNumber];
  }
}

/**
 * Returns the current position in the partially formatted phone number of the
 * character which was previously passed in as the parameter of
 * {@link #inputDigitAndRememberPosition}.
 *
 * @return {number}
 */
- (NSInteger)getRememberedPosition {
  if (!self.ableToFormat_) {
    return self.originalPosition_;
  }
  /** @type {number} */
  NSInteger accruedInputIndex = 0;
  /** @type {number} */
  NSInteger currentOutputIndex = 0;
  /** @type {string} */
  NSString *accruedInputWithoutFormatting = self.accruedInputWithoutFormatting_;
  /** @type {string} */
  NSString *currentOutput = self.currentOutput_;

  while (accruedInputIndex < self.positionToRemember_ &&
         currentOutputIndex < currentOutput.length) {
    if ([accruedInputWithoutFormatting characterAtIndex:accruedInputIndex] ==
        [currentOutput characterAtIndex:currentOutputIndex]) {
      accruedInputIndex++;
    }
    currentOutputIndex++;
  }
  return currentOutputIndex;
}

/**
 * Attempts to set the formatting template and returns a string which contains
 * the formatted version of the digits entered so far.
 *
 * @return {string}
 * @private
 */
- (NSString *)attemptToChooseFormattingPattern_ {
  /** @type {string} */
  NSString *nationalNumber = [self.nationalNumber_ copy];
  // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
  // digits of national number (excluding national prefix) have been entered.
  if (nationalNumber.length >= NBMinLeadingDigitsLength) {
    [self getAvailableFormats_:nationalNumber];
    // See if the accrued digits can be formatted properly already.
    NSString *formattedNumber = [self attemptToFormatAccruedDigits_];
    if (formattedNumber.length > 0) {
      return formattedNumber;
    }
    return [self maybeCreateNewTemplate_] ? [self inputAccruedNationalNumber_] : self.accruedInput_;
  } else {
    return [self appendNationalNumber_:nationalNumber];
  }
}

/**
 * Invokes inputDigitHelper on each digit of the national number accrued, and
 * returns a formatted string in the end.
 *
 * @return {string}
 * @private
 */
- (NSString *)inputAccruedNationalNumber_ {
  /** @type {string} */
  NSString *nationalNumber = [self.nationalNumber_ copy];
  /** @type {number} */
  NSUInteger lengthOfNationalNumber = nationalNumber.length;
  if (lengthOfNationalNumber > 0) {
    /** @type {string} */
    NSString *tempNationalNumber = @"";
    for (NSUInteger i = 0; i < lengthOfNationalNumber; i++) {
      tempNationalNumber = [self
          inputDigitHelper_:[NSString stringWithFormat:@"%C", [nationalNumber characterAtIndex:i]]];
    }
    return self.ableToFormat_ ? [self appendNationalNumber_:tempNationalNumber]
                              : self.accruedInput_;
  } else {
    return self.prefixBeforeNationalNumber_;
  }
}

/**
 * @return {BOOL} YES if the current country is a NANPA country and the
 *     national number begins with the national prefix.
 * @private
 */
- (BOOL)isNanpaNumberWithNationalPrefix_ {
  // For NANPA numbers beginning with 1[2-9], treat the 1 as the national
  // prefix. The reason is that national significant numbers in NANPA always
  // start with [2-9] after the national prefix. Numbers beginning with 1[01]
  // can only be short/emergency numbers, which don't need the national prefix.
  if (![self.currentMetaData_.countryCode isEqual:@1]) {
    return NO;
  }

  /** @type {string} */
  NSString *nationalNumber = [self.nationalNumber_ copy];
  return ([nationalNumber characterAtIndex:0] == '1') &&
         ([nationalNumber characterAtIndex:1] != '0') &&
         ([nationalNumber characterAtIndex:1] != '1');
}

/**
 * Returns the national prefix extracted, or an empty string if it is not
 * present.
 * @return {string}
 * @private
 */
- (NSString *)removeNationalPrefixFromNationalNumber_ {
  /** @type {string} */
  NSString *nationalNumber = [self.nationalNumber_ copy];
  /** @type {number} */
  NSUInteger startOfNationalNumber = 0;

  if ([self isNanpaNumberWithNationalPrefix_]) {
    startOfNationalNumber = 1;
    [self.prefixBeforeNationalNumber_ appendFormat:@"1%@", NBSeparatorBeforeNationalNumber];
    self.isCompleteNumber_ = YES;
  } else if (self.currentMetaData_.nationalPrefixForParsing != nil &&
             self.currentMetaData_.nationalPrefixForParsing.length > 0) {
    /** @type {RegExp} */
    NSString *nationalPrefixForParsing =
        [NSString stringWithFormat:@"^(?:%@)", self.currentMetaData_.nationalPrefixForParsing];
    /** @type {Array.<string>} */
    NSArray *m =
        [self.phoneUtil_ matchedStringByRegex:nationalNumber regex:nationalPrefixForParsing];
    NSString *firstString = [m nb_safeStringAtIndex:0];
    if (m != nil && firstString != nil && firstString.length > 0) {
      // When the national prefix is detected, we use international formatting
      // rules instead of national ones, because national formatting rules could
      // contain local formatting rules for numbers entered without area code.
      self.isCompleteNumber_ = YES;
      startOfNationalNumber = firstString.length;
      [self.prefixBeforeNationalNumber_
          appendString:[nationalNumber substringWithRange:NSMakeRange(0, startOfNationalNumber)]];
    }
  }

  self.nationalNumber_ = [[nationalNumber substringFromIndex:startOfNationalNumber] mutableCopy];
  return [nationalNumber substringWithRange:NSMakeRange(0, startOfNationalNumber)];
}

/**
 * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
 * available, and places the remaining input into nationalNumber.
 *
 * @return {BOOL} YES when accruedInputWithoutFormatting begins with the
 *     plus sign or valid IDD for defaultCountry.
 * @private
 */
- (BOOL)attemptToExtractIdd_ {
  /** @type {string} */
  NSString *accruedInputWithoutFormatting = [self.accruedInputWithoutFormatting_ copy];
  /** @type {RegExp} */
  NSString *internationalPrefix =
      [NSString stringWithFormat:@"^(?:\\+|%@)", self.currentMetaData_.internationalPrefix];
  /** @type {Array.<string>} */
  NSArray *m = [self.phoneUtil_ matchedStringByRegex:accruedInputWithoutFormatting
                                               regex:internationalPrefix];

  NSString *firstString = [m nb_safeStringAtIndex:0];

  if (m != nil && firstString != nil && firstString.length > 0) {
    self.isCompleteNumber_ = YES;
    /** @type {number} */
    NSUInteger startOfCountryCallingCode = firstString.length;
    self.nationalNumber_ =
        [[accruedInputWithoutFormatting substringFromIndex:startOfCountryCallingCode] mutableCopy];
    self.prefixBeforeNationalNumber_ = [[accruedInputWithoutFormatting
        substringWithRange:NSMakeRange(0, startOfCountryCallingCode)] mutableCopy];

    if ([accruedInputWithoutFormatting characterAtIndex:0] != '+') {
      [self.prefixBeforeNationalNumber_ appendString:NBSeparatorBeforeNationalNumber];
    }
    return YES;
  }
  return NO;
}

/**
 * Extracts the country calling code from the beginning of nationalNumber to
 * prefixBeforeNationalNumber when they are available, and places the remaining
 * input into nationalNumber.
 *
 * @return {BOOL} YES when a valid country calling code can be found.
 * @private
 */
- (BOOL)attemptToExtractCountryCallingCode_ {
  if (self.nationalNumber_.length == 0) {
    return NO;
  }

  /** @type {!goog.string.StringBuffer} */
  NSString *numberWithoutCountryCallingCode = @"";

  /** @type {number} */
  NSNumber *countryCode = [self.phoneUtil_ extractCountryCode:self.nationalNumber_
                                               nationalNumber:&numberWithoutCountryCallingCode];

  if ([countryCode isEqualToNumber:@0]) {
    return NO;
  }

  self.nationalNumber_ = [numberWithoutCountryCallingCode mutableCopy];

  /** @type {string} */
  NSString *newRegionCode = [self.phoneUtil_ getRegionCodeForCountryCode:countryCode];

  if ([NB_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:newRegionCode]) {
    NBMetadataHelper *helper = [[NBMetadataHelper alloc] init];
    self.currentMetaData_ = [helper getMetadataForNonGeographicalRegion:countryCode];
  } else if (newRegionCode != self.defaultCountry_) {
    self.currentMetaData_ = [self getMetadataForRegion_:newRegionCode];
  }

  /** @type {string} */
  [self.prefixBeforeNationalNumber_
      appendFormat:@"%@%@", countryCode, NBSeparatorBeforeNationalNumber];
  return YES;
}

/**
 * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
 * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
 * version of digits), it is first normalized to the ASCII version. The return
 * value is nextChar itself, or its normalized version, if nextChar is a digit
 * in non-ASCII format. This method assumes its input is either a digit or the
 * plus sign.
 *
 * - param {string} nextChar
 * - param {BOOL} rememberPosition
 * @return {string}
 * @private
 */
- (NSString *)normalizeAndAccrueDigitsAndPlusSign_:(NSString *)nextChar
                                  rememberPosition:(BOOL)rememberPosition {
  /** @type {string} */
  NSString *normalizedChar;

  if ([nextChar isEqualToString:@"+"]) {
    normalizedChar = nextChar;
    [self.accruedInputWithoutFormatting_ appendString:nextChar];
  } else {
    normalizedChar = [[self.phoneUtil_ DIGIT_MAPPINGS] objectForKey:nextChar];
    if (!normalizedChar) return @"";

    [self.accruedInputWithoutFormatting_ appendString:normalizedChar];
    [self.nationalNumber_ appendString:normalizedChar];
  }

  if (rememberPosition) {
    self.positionToRemember_ = self.accruedInputWithoutFormatting_.length;
  }

  return normalizedChar;
}

/**
 * - param {string} nextChar
 * @return {string}
 * @private
 */
- (NSString *)inputDigitHelper_:(NSString *)nextChar {
  /** @type {string} */
  NSString *formattingTemplate = [self.formattingTemplate_ copy];
  NSString *subedString = @"";

  if (formattingTemplate.length > self.lastMatchPosition_) {
    subedString = [formattingTemplate substringFromIndex:self.lastMatchPosition_];
  }

  if ([self.phoneUtil_ stringPositionByRegex:subedString regex:NBDigitPlaceHolder] >= 0) {
    /** @type {number} */
    int digitPatternStart =
        [self.phoneUtil_ stringPositionByRegex:formattingTemplate regex:NBDigitPlaceHolder];

    /** @type {string} */
    NSRange tempRange = [formattingTemplate rangeOfString:NBDigitPlaceHolder];
    NSString *tempTemplate =
        [formattingTemplate stringByReplacingOccurrencesOfString:NBDigitPlaceHolder
                                                      withString:nextChar
                                                         options:NSLiteralSearch
                                                           range:tempRange];
    self.formattingTemplate_ = [tempTemplate mutableCopy];
    self.lastMatchPosition_ = digitPatternStart;
    return [tempTemplate substringWithRange:NSMakeRange(0, self.lastMatchPosition_ + 1)];
  } else {
    if (self.possibleFormats_.count == 1) {
      // More digits are entered than we could handle, and there are no other
      // valid patterns to try.
      self.ableToFormat_ = NO;
    }  // else, we just reset the formatting pattern.
    self.currentFormattingPattern_ = @"";
    return self.accruedInput_;
  }
}

/**
 * Returns the formatted number.
 *
 * @return {string}
 */
- (NSString *)description {
  return self.currentOutput_;
}

@end
