//
//  NBPhoneNumberUtil.m
//  libPhoneNumber
//
//  Created by tabby on 2015. 2. 8..
//  Copyright (c) 2015년 ohtalk.me. All rights reserved.
//

#import "NBPhoneNumberUtil.h"
#import <math.h>
#import "NBMetadataHelper.h"
#import "NBNumberFormat.h"
#import "NBPhoneMetaData.h"
#import "NBPhoneNumber.h"
#import "NBPhoneNumberDefines.h"
#import "NBPhoneNumberDesc.h"
#import "NBRegExMatcher.h"

#if TARGET_OS_IOS
#import <CoreTelephony/CTCarrier.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif

static NSString *NormalizeNonBreakingSpace(NSString *aString) {
  return [aString stringByReplacingOccurrencesOfString:NB_NON_BREAKING_SPACE withString:@" "];
}

static BOOL isNan(NSString *sourceString) {
  static dispatch_once_t onceToken;
  static NSCharacterSet *nonDecimalCharacterSet;
  dispatch_once(&onceToken, ^{
    nonDecimalCharacterSet = [[NSMutableCharacterSet decimalDigitCharacterSet] invertedSet];
  });

  // Return YES if the sourceString doesn't have any characters that can be represented as a Float.
  return !([sourceString rangeOfCharacterFromSet:nonDecimalCharacterSet].location == NSNotFound);
}

#pragma mark - NBPhoneNumberUtil interface -

@interface NBPhoneNumberUtil ()

@property(nonatomic, strong) NSLock *entireStringCacheLock;
@property(nonatomic, strong) NSMutableDictionary *entireStringRegexCache;

@property(nonatomic, strong) NSLock *lockPatternCache;
@property(nonatomic, strong) NSMutableDictionary *regexPatternCache;

@property(nonatomic, strong) NSRegularExpression *CAPTURING_DIGIT_PATTERN;
@property(nonatomic, strong) NSRegularExpression *VALID_ALPHA_PHONE_PATTERN;

@property(nonatomic, strong, readwrite) NBMetadataHelper *helper;
@property(nonatomic, strong, readwrite) NBRegExMatcher *matcher;

#if TARGET_OS_IOS
@property(nonatomic, readonly) CTTelephonyNetworkInfo *telephonyNetworkInfo;
#endif

@end

@implementation NBPhoneNumberUtil

#pragma mark - Static Int variables -
const static NSUInteger NANPA_COUNTRY_CODE_ = 1;
const static NSUInteger MIN_LENGTH_FOR_NSN_ = 2;
const static NSUInteger MAX_LENGTH_FOR_NSN_ = 16;
const static NSUInteger MAX_LENGTH_COUNTRY_CODE_ = 3;
const static NSUInteger MAX_INPUT_STRING_LENGTH_ = 250;

#pragma mark - Static String variables -
static NSString *VALID_PUNCTUATION =
    @"-x‐-―−ー－-／ ­​⁠　()（）［］.\\[\\]/~⁓∼～";

static NSString *COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = @"3";
static NSString *PLUS_SIGN = @"+";
static NSString *STAR_SIGN = @"*";
static NSString *RFC3966_EXTN_PREFIX = @";ext=";
static NSString *RFC3966_PREFIX = @"tel:";
static NSString *RFC3966_PHONE_CONTEXT = @";phone-context=";
static NSString *RFC3966_ISDN_SUBADDRESS = @";isub=";
static NSString *DEFAULT_EXTN_PREFIX = @" ext. ";
static NSString *VALID_ALPHA = @"A-Za-z";

#pragma mark - Static regular expression strings -
static NSString *NON_DIGITS_PATTERN = @"\\D+";
static NSString *CC_PATTERN = @"\\$CC";
static NSString *FIRST_GROUP_PATTERN = @"(\\$\\d)";
static NSString *FIRST_GROUP_ONLY_PREFIX_PATTERN = @"^\\(?\\$1\\)?";
static NSString *NP_PATTERN = @"\\$NP";
static NSString *FG_PATTERN = @"\\$FG";
static NSString *VALID_ALPHA_PHONE_PATTERN_STRING = @"(?:.*?[A-Za-z]){3}.*";

static NSString *UNIQUE_INTERNATIONAL_PREFIX = @"[\\d]+(?:[~\\u2053\\u223C\\uFF5E][\\d]+)?";

static NSString *LEADING_PLUS_CHARS_PATTERN;
static NSString *EXTN_PATTERN;
static NSString *SEPARATOR_PATTERN;
static NSString *VALID_PHONE_NUMBER_PATTERN;
static NSString *VALID_START_CHAR_PATTERN;
static NSString *UNWANTED_END_CHAR_PATTERN;
static NSString *SECOND_NUMBER_START_PATTERN;

static NSDictionary *ALL_NORMALIZATION_MAPPINGS;
static NSDictionary *DIALLABLE_CHAR_MAPPINGS;
static NSDictionary *ALL_PLUS_NUMBER_GROUPING_SYMBOLS;

// Map of country calling codes that use a mobile token before the area code. One example of when
// this is relevant is when determining the length of the national destination code, which should
// be the length of the area code plus the length of the mobile token.
static NSDictionary<NSNumber *, NSString *> *MOBILE_TOKEN_MAPPINGS;

static NSDictionary *DIGIT_MAPPINGS;

static NSArray *GEO_MOBILE_COUNTRIES;

#pragma mark - Deprecated methods

+ (NBPhoneNumberUtil *)sharedInstance {
  static NBPhoneNumberUtil *sharedOnceInstance = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedOnceInstance = [[self alloc] init];
  });
  return sharedOnceInstance;
}

#pragma mark - NSError

- (NSError *)errorWithObject:(id)obj withDomain:(NSString *)domain {
  NSDictionary *userInfo = [NSDictionary dictionaryWithObject:obj forKey:NSLocalizedDescriptionKey];
  NSError *error = [NSError errorWithDomain:domain code:0 userInfo:userInfo];
  return error;
}

- (NSRegularExpression *)entireRegularExpressionWithPattern:(NSString *)regexPattern
                                                    options:(NSRegularExpressionOptions)options
                                                      error:(NSError **)error {
  [_entireStringCacheLock lock];

  @try {
    if (!_entireStringRegexCache) {
      _entireStringRegexCache = [[NSMutableDictionary alloc] init];
    }

    NSRegularExpression *regex = [_entireStringRegexCache objectForKey:regexPattern];
    if (!regex) {
      NSString *finalRegexString = regexPattern;
      if ([regexPattern rangeOfString:@"^"].location == NSNotFound) {
        finalRegexString = [NSString stringWithFormat:@"^(?:%@)$", regexPattern];
      }

      regex = [self regularExpressionWithPattern:finalRegexString options:0 error:error];
      [_entireStringRegexCache setObject:regex forKey:regexPattern];
    }

    return regex;
  } @finally {
    [_entireStringCacheLock unlock];
  }
}

- (NSRegularExpression *)regularExpressionWithPattern:(NSString *)pattern
                                              options:(NSRegularExpressionOptions)options
                                                error:(NSError **)error {
  [_lockPatternCache lock];

  @try {
    if (!_regexPatternCache) {
      _regexPatternCache = [[NSMutableDictionary alloc] init];
    }

    NSRegularExpression *regex = [_regexPatternCache objectForKey:pattern];
    if (!regex) {
      regex =
          [NSRegularExpression regularExpressionWithPattern:pattern options:options error:error];
      [_regexPatternCache setObject:regex forKey:pattern];
    }
    return regex;
  } @finally {
    [_lockPatternCache unlock];
  }
}

- (NSMutableArray *)componentsSeparatedByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  NSString *replacedString =
      [self replaceStringByRegex:sourceString regex:pattern withTemplate:@"<SEP>"];
  NSMutableArray *resArray = [[replacedString componentsSeparatedByString:@"<SEP>"] mutableCopy];
  [resArray removeObject:@""];
  return resArray;
}

- (int)stringPositionByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  if (sourceString == nil || sourceString.length <= 0 || pattern == nil || pattern.length <= 0) {
    return -1;
  }

  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:pattern options:0 error:&error];
  NSArray *matches = [currentPattern matchesInString:sourceString
                                             options:0
                                               range:NSMakeRange(0, sourceString.length)];

  int foundPosition = -1;

  if (matches.count > 0) {
    NSTextCheckingResult *match = [matches objectAtIndex:0];
    return (int)match.range.location;
  }

  return foundPosition;
}

- (int)indexOfStringByString:(NSString *)sourceString target:(NSString *)targetString {
  NSRange finded = [sourceString rangeOfString:targetString];
  if (finded.location != NSNotFound) {
    return (int)finded.location;
  }

  return -1;
}

- (NSString *)replaceFirstStringByRegex:(NSString *)sourceString
                                  regex:(NSString *)pattern
                           withTemplate:(NSString *)templateString {
  NSString *replacementResult = [sourceString copy];
  NSError *error = nil;

  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:pattern options:0 error:&error];
  NSRange replaceRange =
      [currentPattern rangeOfFirstMatchInString:sourceString
                                        options:0
                                          range:NSMakeRange(0, sourceString.length)];

  if (replaceRange.location != NSNotFound) {
    replacementResult = [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy]
                                                                 options:0
                                                                   range:replaceRange
                                                            withTemplate:templateString];
  }

  return replacementResult;
}

- (NSString *)replaceStringByRegex:(NSString *)sourceString
                             regex:(NSString *)pattern
                      withTemplate:(NSString *)templateString {
  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:pattern options:0 error:&error];
  NSArray *matches = [currentPattern matchesInString:sourceString
                                             options:0
                                               range:NSMakeRange(0, sourceString.length)];

  if ([matches count] == 1) {
    NSString *replacementResult;
    NSRange replaceRange =
        [currentPattern rangeOfFirstMatchInString:sourceString
                                          options:0
                                            range:NSMakeRange(0, sourceString.length)];

    if (replaceRange.location != NSNotFound) {
      replacementResult =
          [currentPattern stringByReplacingMatchesInString:[sourceString mutableCopy]
                                                   options:0
                                                     range:replaceRange
                                              withTemplate:templateString];
    } else {
      replacementResult = [sourceString copy];
    }
    return replacementResult;
  }

  if ([matches count] > 1) {
    return [currentPattern stringByReplacingMatchesInString:sourceString
                                                    options:0
                                                      range:NSMakeRange(0, sourceString.length)
                                               withTemplate:templateString];
  }

  return [sourceString copy];
}

- (NSTextCheckingResult *)matchFirstByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:pattern options:0 error:&error];
  NSArray *matches = [currentPattern matchesInString:sourceString
                                             options:0
                                               range:NSMakeRange(0, sourceString.length)];
  if ([matches count] > 0) return [matches objectAtIndex:0];
  return nil;
}

- (NSArray *)matchesByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:pattern options:0 error:&error];
  NSArray *matches = [currentPattern matchesInString:sourceString
                                             options:0
                                               range:NSMakeRange(0, sourceString.length)];
  return matches;
}

- (NSArray *)matchedStringByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  NSArray *matches = [self matchesByRegex:sourceString regex:pattern];
  NSMutableArray *matchString = [[NSMutableArray alloc] init];

  for (NSTextCheckingResult *match in matches) {
    NSString *curString = [sourceString substringWithRange:match.range];
    [matchString addObject:curString];
  }

  return matchString;
}

- (BOOL)isStartingStringByRegex:(NSString *)sourceString regex:(NSString *)pattern {
  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:pattern options:0 error:&error];
  NSArray *matches = [currentPattern matchesInString:sourceString
                                             options:0
                                               range:NSMakeRange(0, sourceString.length)];

  for (NSTextCheckingResult *match in matches) {
    if (match.range.location == 0) {
      return YES;
    }
  }

  return NO;
}

- (NSString *)stringByReplacingOccurrencesString:(NSString *)sourceString
                                         withMap:(NSDictionary *)dicMap
                                removeNonMatches:(BOOL)bRemove {
  NSMutableString *targetString = [[NSMutableString alloc] init];
  NSUInteger length = sourceString.length;
  for (NSUInteger i = 0; i < length; i++) {
    unichar oneChar = [sourceString characterAtIndex:i];
    NSString *keyString = [NSString stringWithCharacters:&oneChar length:1];
    NSString *mappedValue = [dicMap objectForKey:keyString];
    if (mappedValue != nil) {
      [targetString appendString:mappedValue];
    } else {
      if (bRemove == NO) {
        [targetString appendString:keyString];
      }
    }
  }

  return targetString;
}

- (BOOL)isAllDigits:(NSString *)sourceString {
  NSCharacterSet *nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
  NSRange r = [sourceString rangeOfCharacterFromSet:nonNumbers];
  return r.location == NSNotFound;
}

/**
 * Gets the national significant number of the a phone number. Note a national
 * significant number doesn't contain a national prefix or any formatting.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number for which the
 *     national significant number is needed.
 * @return {string} the national significant number of the PhoneNumber object
 *     passed in.
 */
- (NSString *)getNationalSignificantNumber:(NBPhoneNumber *)phoneNumber {
  // If leading zero(s) have been set, we prefix this now. Note this is not a
  // national prefix.
  NSString *nationalNumber = [phoneNumber.nationalNumber stringValue];
  if (phoneNumber.italianLeadingZero) {
    NSString *zeroNumbers =
        [@"" stringByPaddingToLength:phoneNumber.numberOfLeadingZeros.integerValue
                          withString:@"0"
                     startingAtIndex:0];
    return [NSString stringWithFormat:@"%@%@", zeroNumbers, nationalNumber];
  }

  return [phoneNumber.nationalNumber stringValue];
}

#pragma mark - Initializations -

+ (void)initialize {
  [super initialize];

  /**
   * Set of country calling codes that have geographically assigned mobile
   * numbers. This may not be complete; we add calling codes case by case, as we
   * find geographical mobile numbers or hear from user reports.
   *
   * @const
   * @type {!Array.<number>}
   * @private
   */
  //                     @[ Mexico, Argentina, Brazil ]
  GEO_MOBILE_COUNTRIES = @[ @52, @54, @55 ];
}

- (instancetype)init {
  self = [super init];
  if (self) {
    _lockPatternCache = [[NSLock alloc] init];
    _entireStringCacheLock = [[NSLock alloc] init];
    _helper = [[NBMetadataHelper alloc] init];
    _matcher = [[NBRegExMatcher alloc] init];
    [self initRegularExpressionSet];
    [self initNormalizationMappings];
  }

  return self;
}

- (void)initRegularExpressionSet {
  NSError *error = nil;

  if (!_CAPTURING_DIGIT_PATTERN) {
    _CAPTURING_DIGIT_PATTERN = [self
        regularExpressionWithPattern:[NSString stringWithFormat:@"([%@])", NB_VALID_DIGITS_STRING]
                             options:0
                               error:&error];
  }

  if (!_VALID_ALPHA_PHONE_PATTERN) {
    _VALID_ALPHA_PHONE_PATTERN =
        [self regularExpressionWithPattern:VALID_ALPHA_PHONE_PATTERN_STRING options:0 error:&error];
  }

  static dispatch_once_t onceToken;
  dispatch_once(
      &onceToken, ^{
        NSString *EXTN_PATTERNS_FOR_PARSING =
            @"(?:;ext=([0-9０-９٠-٩۰-۹]{1,7})|[  "
            @"\\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|ｅ?ｘｔｎ?|[,xｘX#＃~～]|int|anexo|ｉｎｔ)[:\\.．]?["
            @"  \\t,-]*([0-9０-９٠-٩۰-۹]{1,7})#?|[- ]+([0-9０-９٠-٩۰-۹]{1,5})#)$";

        LEADING_PLUS_CHARS_PATTERN = [NSString stringWithFormat:@"^[%@]+", NB_PLUS_CHARS];

        VALID_START_CHAR_PATTERN =
            [NSString stringWithFormat:@"[%@%@]", NB_PLUS_CHARS, NB_VALID_DIGITS_STRING];

        SECOND_NUMBER_START_PATTERN = @"[\\\\\\/] *x";

        UNWANTED_END_CHAR_PATTERN =
            [NSString stringWithFormat:@"[^%@%@#]+$", NB_VALID_DIGITS_STRING, VALID_ALPHA];

        EXTN_PATTERN = [NSString stringWithFormat:@"(?:%@)$", EXTN_PATTERNS_FOR_PARSING];

        SEPARATOR_PATTERN = [NSString stringWithFormat:@"[%@]+", VALID_PUNCTUATION];

        VALID_PHONE_NUMBER_PATTERN =
            @"^[0-9０-９٠-٩۰-۹]{2}$|^[+＋]*(?:[-x‐-―−ー－-／  "
            @"­​⁠　()（）［］.\\[\\]/~⁓∼～*]*[0-9０-９٠-٩۰-۹]){3,}[-x‐-―−ー－-／  "
            @"­​⁠　()（）［］.\\[\\]/"
            @"~⁓∼～*A-Za-z0-9０-９٠-٩۰-۹]*(?:;ext=([0-9０-９٠-٩۰-۹]{1,7})|[  "
            @"\\t,]*(?:e?xt(?:ensi(?:ó?|ó))?n?|ｅ?ｘｔｎ?|[,xｘ#＃~～]|int|anexo|ｉｎｔ)[:\\.．]?[ "
            @" \\t,-]*([0-9０-９٠-٩۰-۹]{1,7})#?|[- ]+([0-9０-９٠-٩۰-۹]{1,5})#)?$";
      });
}

- (NSDictionary *)DIGIT_MAPPINGS {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    DIGIT_MAPPINGS = [NSDictionary
        dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
                                     @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
                                     // Fullwidth digit 0 to 9
                                     @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3",
                                     @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16",
                                     @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19",
                                     // Arabic-indic digit 0 to 9
                                     @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3",
                                     @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666",
                                     @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669",
                                     // Eastern-Arabic digit 0 to 9
                                     @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3",
                                     @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6",
                                     @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9",
                                     // BENGALI digit 0 to 9
                                     @"0", @"\u09E6", @"1", @"\u09E7", @"2", @"\u09E8", @"3",
                                     @"\u09E9", @"4", @"\u09EA", @"5", @"\u09EB", @"6", @"\u09EC",
                                     @"7", @"\u09ED", @"8", @"\u09EE", @"9", @"\u09EF",
                                     // DEVANAGARI digit 0 to 9
                                     @"0", @"\u0966", @"1", @"\u0967", @"2", @"\u0968", @"3",
                                     @"\u0969", @"4", @"\u096A", @"5", @"\u096B", @"6", @"\u096C",
                                     @"7", @"\u096D", @"8", @"\u096E", @"9", @"\u096F", nil];
  });

  return DIGIT_MAPPINGS;
}

- (void)initNormalizationMappings {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    DIALLABLE_CHAR_MAPPINGS = [NSDictionary
        dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
                                     @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
                                     @"+", @"+", @"*", @"*", @"#", @"#", nil];

    ALL_NORMALIZATION_MAPPINGS = [NSDictionary
        dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
                                     @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
                                     // Fullwidth digit 0 to 9
                                     @"0", @"\uFF10", @"1", @"\uFF11", @"2", @"\uFF12", @"3",
                                     @"\uFF13", @"4", @"\uFF14", @"5", @"\uFF15", @"6", @"\uFF16",
                                     @"7", @"\uFF17", @"8", @"\uFF18", @"9", @"\uFF19",
                                     // Arabic-indic digit 0 to 9
                                     @"0", @"\u0660", @"1", @"\u0661", @"2", @"\u0662", @"3",
                                     @"\u0663", @"4", @"\u0664", @"5", @"\u0665", @"6", @"\u0666",
                                     @"7", @"\u0667", @"8", @"\u0668", @"9", @"\u0669",
                                     // Eastern-Arabic digit 0 to 9
                                     @"0", @"\u06F0", @"1", @"\u06F1", @"2", @"\u06F2", @"3",
                                     @"\u06F3", @"4", @"\u06F4", @"5", @"\u06F5", @"6", @"\u06F6",
                                     @"7", @"\u06F7", @"8", @"\u06F8", @"9", @"\u06F9", @"2", @"A",
                                     @"2", @"B", @"2", @"C", @"3", @"D", @"3", @"E", @"3", @"F",
                                     @"4", @"G", @"4", @"H", @"4", @"I", @"5", @"J", @"5", @"K",
                                     @"5", @"L", @"6", @"M", @"6", @"N", @"6", @"O", @"7", @"P",
                                     @"7", @"Q", @"7", @"R", @"7", @"S", @"8", @"T", @"8", @"U",
                                     @"8", @"V", @"9", @"W", @"9", @"X", @"9", @"Y", @"9", @"Z",
                                     nil];

    ALL_PLUS_NUMBER_GROUPING_SYMBOLS = [NSDictionary
        dictionaryWithObjectsAndKeys:@"0", @"0", @"1", @"1", @"2", @"2", @"3", @"3", @"4", @"4",
                                     @"5", @"5", @"6", @"6", @"7", @"7", @"8", @"8", @"9", @"9",
                                     @"A", @"A", @"B", @"B", @"C", @"C", @"D", @"D", @"E", @"E",
                                     @"F", @"F", @"G", @"G", @"H", @"H", @"I", @"I", @"J", @"J",
                                     @"K", @"K", @"L", @"L", @"M", @"M", @"N", @"N", @"O", @"O",
                                     @"P", @"P", @"Q", @"Q", @"R", @"R", @"S", @"S", @"T", @"T",
                                     @"U", @"U", @"V", @"V", @"W", @"W", @"X", @"X", @"Y", @"Y",
                                     @"Z", @"Z", @"A", @"a", @"B", @"b", @"C", @"c", @"D", @"d",
                                     @"E", @"e", @"F", @"f", @"G", @"g", @"H", @"h", @"I", @"i",
                                     @"J", @"j", @"K", @"k", @"L", @"l", @"M", @"m", @"N", @"n",
                                     @"O", @"o", @"P", @"p", @"Q", @"q", @"R", @"r", @"S", @"s",
                                     @"T", @"t", @"U", @"u", @"V", @"v", @"W", @"w", @"X", @"x",
                                     @"Y", @"y", @"Z", @"z", @"-", @"-", @"-", @"\uFF0D", @"-",
                                     @"\u2010", @"-", @"\u2011", @"-", @"\u2012", @"-", @"\u2013",
                                     @"-", @"\u2014", @"-", @"\u2015", @"-", @"\u2212", @"/", @"/",
                                     @"/", @"\uFF0F", @" ", @" ", @" ", @"\u3000", @" ", @"\u2060",
                                     @".", @".", @".", @"\uFF0E", nil];

    MOBILE_TOKEN_MAPPINGS = @{
      @52: @"1",
      @54: @"9",
    };
  });
}

#pragma mark - Metadata manager (phonenumberutil.js) functions -
/**
 * Attempts to extract a possible number from the string passed in. This
 * currently strips all leading characters that cannot be used to start a phone
 * number. Characters that can be used to start a phone number are defined in
 * the VALID_START_CHAR_PATTERN. If none of these characters are found in the
 * number passed in, an empty string is returned. This function also attempts to
 * strip off any alternative extensions or endings if two or more are present,
 * such as in the case of: (530) 583-6985 x302/x2303. The second extension here
 * makes this actually two phone numbers, (530) 583-6985 x302 and (530) 583-6985
 * x2303. We remove the second extension so that the first number is parsed
 * correctly.
 *
 * - param {string} number the string that might contain a phone number.
 * @return {string} the number, stripped of any non-phone-number prefix (such as
 *     'Tel:') or an empty string if no character used to start phone numbers
 *     (such as + or any digit) is found in the number.
 */
- (NSString *)extractPossibleNumber:(NSString *)number {
  number = NormalizeNonBreakingSpace(number);

  NSString *possibleNumber = @"";
  int start = [self stringPositionByRegex:number regex:VALID_START_CHAR_PATTERN];

  if (start >= 0) {
    possibleNumber = [number substringFromIndex:start];
    // Remove trailing non-alpha non-numerical characters.
    possibleNumber =
        [self replaceStringByRegex:possibleNumber regex:UNWANTED_END_CHAR_PATTERN withTemplate:@""];

    // Check for extra numbers at the end.
    int secondNumberStart =
        [self stringPositionByRegex:possibleNumber regex:SECOND_NUMBER_START_PATTERN];
    if (secondNumberStart > 0) {
      possibleNumber = [possibleNumber substringWithRange:NSMakeRange(0, secondNumberStart)];
    }
  } else {
    possibleNumber = @"";
  }

  return possibleNumber;
}

/**
 * Checks to see if the string of characters could possibly be a phone number at
 * all. At the moment, checks to see that the string begins with at least 2
 * digits, ignoring any punctuation commonly found in phone numbers. This method
 * does not require the number to be normalized in advance - but does assume
 * that leading non-number symbols have been removed, such as by the method
 * extractPossibleNumber.
 *
 * - param {string} number string to be checked for viability as a phone number.
 * @return {boolean} NO if the number could be a phone number of some sort,
 *     otherwise NO.
 */
- (BOOL)isViablePhoneNumber:(NSString *)phoneNumber {
  phoneNumber = NormalizeNonBreakingSpace(phoneNumber);

  if (phoneNumber.length < MIN_LENGTH_FOR_NSN_) {
    return NO;
  }

  return [self matchesEntirely:VALID_PHONE_NUMBER_PATTERN string:phoneNumber];
}

/**
 * Normalizes a string of characters representing a phone number. This performs
 * the following conversions:
 *   Punctuation is stripped.
 *   For ALPHA/VANITY numbers:
 *   Letters are converted to their numeric representation on a telephone
 *       keypad. The keypad used here is the one defined in ITU Recommendation
 *       E.161. This is only done if there are 3 or more letters in the number,
 *       to lessen the risk that such letters are typos.
 *   For other numbers:
 *   Wide-ascii digits are converted to normal ASCII (European) digits.
 *   Arabic-Indic numerals are converted to European numerals.
 *   Spurious alpha characters are stripped.
 *
 * - param {string} number a string of characters representing a phone number.
 * @return {string} the normalized string version of the phone number.
 */
- (NSString *)normalize:(NSString *)number {
  if ([self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:number]) {
    return [self normalizeHelper:number
        normalizationReplacements:ALL_NORMALIZATION_MAPPINGS
                 removeNonMatches:true];
  } else {
    return [self normalizeDigitsOnly:number];
  }

  return nil;
}

/**
 * Normalizes a string of characters representing a phone number. This is a
 * wrapper for normalize(String number) but does in-place normalization of the
 * StringBuffer provided.
 *
 * - param {!goog.string.StringBuffer} number a StringBuffer of characters
 *     representing a phone number that will be normalized in place.
 * @private
 */

- (void)normalizeSB:(NSString **)number {
  if (number == NULL) {
    return;
  }

  (*number) = [self normalize:(*number)];
}

/**
 * Normalizes a string of characters representing a phone number. This converts
 * wide-ascii and arabic-indic numerals to European numerals, and strips
 * punctuation and alpha characters.
 *
 * - param {string} number a string of characters representing a phone number.
 * @return {string} the normalized string version of the phone number.
 */
- (NSString *)normalizeDigitsOnly:(NSString *)number {
  number = NormalizeNonBreakingSpace(number);

  return [self stringByReplacingOccurrencesString:number
                                          withMap:self.DIGIT_MAPPINGS
                                 removeNonMatches:YES];
}

/**
 * Normalizes a string of characters representing a phone number. This strips
 * all characters which are not diallable on a mobile phone keypad (including
 * all non-ASCII digits).
 *
 * - param {string} number a string of characters representing a phone number.
 * @return {string} the normalized string version of the phone number.
 */
- (NSString *)normalizeDiallableCharsOnly:(NSString *)number {
  number = NormalizeNonBreakingSpace(number);

  return [self stringByReplacingOccurrencesString:number
                                          withMap:DIALLABLE_CHAR_MAPPINGS
                                 removeNonMatches:YES];
}

/**
 * Converts all alpha characters in a number to their respective digits on a
 * keypad, but retains existing formatting. Also converts wide-ascii digits to
 * normal ascii digits, and converts Arabic-Indic numerals to European numerals.
 *
 * - param {string} number a string of characters representing a phone number.
 * @return {string} the normalized string version of the phone number.
 */
- (NSString *)convertAlphaCharactersInNumber:(NSString *)number {
  number = NormalizeNonBreakingSpace(number);
  return [self stringByReplacingOccurrencesString:number
                                          withMap:ALL_NORMALIZATION_MAPPINGS
                                 removeNonMatches:NO];
}

/**
 * Gets the length of the geographical area code from the
 * {@code national_number} field of the PhoneNumber object passed in, so that
 * clients could use it to split a national significant number into geographical
 * area code and subscriber number. It works in such a way that the resultant
 * subscriber number should be diallable, at least on some devices. An example
 * of how this could be used:
 *
 * <pre>
 * var phoneUtil = getInstance();
 * var number = phoneUtil.parse('16502530000', 'US');
 * var nationalSignificantNumber =
 *     phoneUtil.getNationalSignificantNumber(number);
 * var areaCode;
 * var subscriberNumber;
 *
 * var areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
 * if (areaCodeLength > 0) {
 *   areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
 *   subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
 * } else {
 *   areaCode = '';
 *   subscriberNumber = nationalSignificantNumber;
 * }
 * </pre>
 *
 * N.B.: area code is a very ambiguous concept, so the I18N team generally
 * recommends against using it for most purposes, but recommends using the more
 * general {@code national_number} instead. Read the following carefully before
 * deciding to use this method:
 * <ul>
 *  <li> geographical area codes change over time, and this method honors those
 *    changes; therefore, it doesn't guarantee the stability of the result it
 *    produces.
 *  <li> subscriber numbers may not be diallable from all devices (notably
 *    mobile devices, which typically requires the full national_number to be
 *    dialled in most regions).
 *  <li> most non-geographical numbers have no area codes, including numbers
 *    from non-geographical entities.
 *  <li> some geographical numbers have no area codes.
 * </ul>
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
 *     which clients want to know the length of the area code.
 * @return {number} the length of area code of the PhoneNumber object passed in.
 */
- (int)getLengthOfGeographicalAreaCode:(NBPhoneNumber *)phoneNumber error:(NSError **)error {
  int res = 0;
  @try {
    res = [self getLengthOfGeographicalAreaCode:phoneNumber];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) {
      (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
    }
  }
  return res;
}

- (int)getLengthOfGeographicalAreaCode:(NBPhoneNumber *)phoneNumber {
  NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];

  NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];

  if (metadata == nil) {
    return 0;
  }
  // If a country doesn't use a national prefix, and this number doesn't have
  // an Italian leading zero, we assume it is a closed dialling plan with no
  // area codes.
  if (metadata.nationalPrefix == nil && phoneNumber.italianLeadingZero == NO) {
    return 0;
  }

  if ([self isNumberGeographical:phoneNumber] == NO) {
    return 0;
  }

  return [self getLengthOfNationalDestinationCode:phoneNumber];
}

/**
 * Gets the length of the national destination code (NDC) from the PhoneNumber
 * object passed in, so that clients could use it to split a national
 * significant number into NDC and subscriber number. The NDC of a phone number
 * is normally the first group of digit(s) right after the country calling code
 * when the number is formatted in the international format, if there is a
 * subscriber number part that follows. An example of how this could be used:
 *
 * <pre>
 * var phoneUtil = getInstance();
 * var number = phoneUtil.parse('18002530000', 'US');
 * var nationalSignificantNumber =
 *     phoneUtil.getNationalSignificantNumber(number);
 * var nationalDestinationCode;
 * var subscriberNumber;
 *
 * var nationalDestinationCodeLength =
 *     phoneUtil.getLengthOfNationalDestinationCode(number);
 * if (nationalDestinationCodeLength > 0) {
 *   nationalDestinationCode =
 *       nationalSignificantNumber.substring(0, nationalDestinationCodeLength);
 *   subscriberNumber =
 *       nationalSignificantNumber.substring(nationalDestinationCodeLength);
 * } else {
 *   nationalDestinationCode = '';
 *   subscriberNumber = nationalSignificantNumber;
 * }
 * </pre>
 *
 * Refer to the unittests to see the difference between this function and
 * {@link #getLengthOfGeographicalAreaCode}.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
 *     which clients want to know the length of the NDC.
 * @return {number} the length of NDC of the PhoneNumber object passed in.
 */
- (int)getLengthOfNationalDestinationCode:(NBPhoneNumber *)phoneNumber error:(NSError **)error {
  int res = 0;

  @try {
    res = [self getLengthOfNationalDestinationCode:phoneNumber];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) {
      (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
    }
  }

  return res;
}

- (int)getLengthOfNationalDestinationCode:(NBPhoneNumber *)phoneNumber {
  NBPhoneNumber *copiedProto = nil;

  if ([NBMetadataHelper hasValue:phoneNumber.extension]) {
    copiedProto = [phoneNumber copy];
    copiedProto.extension = nil;
  } else {
    copiedProto = phoneNumber;
  }

  NSString *nationalSignificantNumber =
      [self format:copiedProto numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  NSMutableArray *numberGroups = [[self componentsSeparatedByRegex:nationalSignificantNumber
                                                             regex:NON_DIGITS_PATTERN] mutableCopy];

  // The pattern will start with '+COUNTRY_CODE ' so the first group will always
  // be the empty string (before the + symbol) and the second group will be the
  // country calling code. The third group will be area code if it is not the
  // last group.
  // NOTE: On IE the first group that is supposed to be the empty string does
  // not appear in the array of number groups... so make the result on non-IE
  // browsers to be that of IE.
  if ([numberGroups count] > 0 && ((NSString *)[numberGroups objectAtIndex:0]).length <= 0) {
    [numberGroups removeObjectAtIndex:0];
  }

  if ([numberGroups count] <= 2) {
    return 0;
  }

  NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:phoneNumber.countryCode];
  BOOL isExists = NO;

  for (NSString *regCode in regionCodes) {
    if ([regCode isEqualToString:@"AR"]) {
      isExists = YES;
      break;
    }
  }

  if (isExists && [self getNumberType:phoneNumber] == NBEPhoneNumberTypeMOBILE) {
    // Argentinian mobile numbers, when formatted in the international format,
    // are in the form of +54 9 NDC XXXX.... As a result, we take the length of
    // the third group (NDC) and add 1 for the digit 9, which also forms part of
    // the national significant number.
    //
    // TODO: Investigate the possibility of better modeling the metadata to make
    // it easier to obtain the NDC.
    return (int)((NSString *)[numberGroups objectAtIndex:2]).length + 1;
  }

  return (int)((NSString *)[numberGroups objectAtIndex:1]).length;
}

- (NSString *)getCountryMobileTokenFromCountryCode:(NSInteger)countryCallingCode {
    NSString *mobileToken = MOBILE_TOKEN_MAPPINGS[@(countryCallingCode)];
    if (mobileToken != nil) {
        return mobileToken;
    }
    return @"";
}

/**
 * Normalizes a string of characters representing a phone number by replacing
 * all characters found in the accompanying map with the values therein, and
 * stripping all other characters if removeNonMatches is NO.
 *
 * - param {string} number a string of characters representing a phone number.
 * - param {!Object.<string, string>} normalizationReplacements a mapping of
 *     characters to what they should be replaced by in the normalized version
 *     of the phone number.
 * - param {boolean} removeNonMatches indicates whether characters that are not
 *     able to be replaced should be stripped from the number. If this is NO,
 *     they will be left unchanged in the number.
 * @return {string} the normalized string version of the phone number.
 * @private
 */
- (NSString *)normalizeHelper:(NSString *)sourceString
    normalizationReplacements:(NSDictionary *)normalizationReplacements
             removeNonMatches:(BOOL)removeNonMatches {
  NSUInteger numberLength = sourceString.length;
  NSMutableString *normalizedNumber = [[NSMutableString alloc] init];

  for (NSUInteger i = 0; i < numberLength; ++i) {
    NSString *charString = [sourceString substringWithRange:NSMakeRange(i, 1)];
    NSString *newDigit = [normalizationReplacements objectForKey:[charString uppercaseString]];
    if (newDigit != nil) {
      [normalizedNumber appendString:newDigit];
    } else if (removeNonMatches == NO) {
      [normalizedNumber appendString:charString];
    }
    // If neither of the above are NO, we remove this character.

    // NSLog(@"[%@]", normalizedNumber);
  }

  return normalizedNumber;
}

/**
 * Helper function to check if the national prefix formatting rule has the first
 * group only, i.e., does not start with the national prefix.
 *
 * - param {string} nationalPrefixFormattingRule The formatting rule for the
 *     national prefix.
 * @return {boolean} NO if the national prefix formatting rule has the first
 *     group only.
 */
- (BOOL)formattingRuleHasFirstGroupOnly:(NSString *)nationalPrefixFormattingRule {
  BOOL hasFound = [self stringPositionByRegex:nationalPrefixFormattingRule
                                        regex:FIRST_GROUP_ONLY_PREFIX_PATTERN] >= 0;
  return (([nationalPrefixFormattingRule length] == 0) || hasFound);
}

/**
 * Tests whether a phone number has a geographical association. It checks if
 * the number is associated to a certain region in the country where it belongs
 * to. Note that this doesn't verify if the number is actually in use.
 *
 * - param {i18n.phonenumbers.PhoneNumber} phoneNumber The phone number to test.
 * @return {boolean} NO if the phone number has a geographical association.
 * @private
 */
- (BOOL)isNumberGeographical:(NBPhoneNumber *)phoneNumber {
  NBEPhoneNumberType numberType = [self getNumberType:phoneNumber];
  // TODO: Include mobile phone numbers from countries like Indonesia, which
  // has some mobile numbers that are geographical.

  BOOL containGeoMobileContries = [GEO_MOBILE_COUNTRIES containsObject:phoneNumber.countryCode] &&
                                  numberType == NBEPhoneNumberTypeMOBILE;
  BOOL isFixedLine = (numberType == NBEPhoneNumberTypeFIXED_LINE);
  BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE);
  return isFixedLine || isFixedLineOrMobile || containGeoMobileContries;
}

/**
 * Helper function to check region code is not unknown or nil.
 *
 * - param {?string} regionCode the ISO 3166-1 two-letter region code.
 * @return {boolean} NO if region code is valid.
 * @private
 */
- (BOOL)isValidRegionCode:(NSString *)regionCode {
  // In Java we check whether the regionCode is contained in supportedRegions
  // that is built out of all the values of countryCallingCodeToRegionCodeMap
  // (countryCodeToRegionCodeMap in JS) minus REGION_CODE_FOR_NON_GEO_ENTITY.
  // In JS we check whether the regionCode is contained in the keys of
  // countryToMetadata but since for non-geographical country calling codes
  // (e.g. +800) we use the country calling codes instead of the region code as
  // key in the map we have to make sure regionCode is not a number to prevent
  // returning NO for non-geographical country calling codes.
  return [NBMetadataHelper hasValue:regionCode] && isNan(regionCode) &&
         [self.helper getMetadataForRegion:regionCode.uppercaseString] != nil;
}

/**
 * Helper function to check the country calling code is valid.
 *
 * - param {number} countryCallingCode the country calling code.
 * @return {boolean} NO if country calling code code is valid.
 * @private
 */
- (BOOL)hasValidCountryCallingCode:(NSNumber *)countryCallingCode {
  id res = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  if (res != nil) {
    return YES;
  }

  return NO;
}

/**
 * Formats a phone number in the specified format using default rules. Note that
 * this does not promise to produce a phone number that the user can dial from
 * where they are - although we do format in either 'national' or
 * 'international' format depending on what the client asks for, we do not
 * currently support a more abbreviated format, such as for users in the same
 * 'area' who could potentially dial the number without area code. Note that if
 * the phone number has a country calling code of 0 or an otherwise invalid
 * country calling code, we cannot work out which formatting rules to apply so
 * we return the national significant number with no formatting applied.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
 *     formatted.
 * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
 *     phone number should be formatted into.
 * @return {string} the formatted phone number.
 */
- (NSString *)format:(NBPhoneNumber *)phoneNumber
        numberFormat:(NBEPhoneNumberFormat)numberFormat
               error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self format:phoneNumber numberFormat:numberFormat];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (NSString *)format:(NBPhoneNumber *)phoneNumber numberFormat:(NBEPhoneNumberFormat)numberFormat {
  if ([phoneNumber.nationalNumber isEqualToNumber:@0] &&
      [NBMetadataHelper hasValue:phoneNumber.rawInput]) {
    // Unparseable numbers that kept their raw input just use that.
    // This is the only case where a number can be formatted as E164 without a
    // leading '+' symbol (but the original number wasn't parseable anyway).
    // TODO: Consider removing the 'if' above so that unparseable strings
    // without raw input format to the empty string instead of "+00"
    /** @type {string} */
    NSString *rawInput = phoneNumber.rawInput;
    if ([NBMetadataHelper hasValue:rawInput]) {
      return rawInput;
    }
  }

  NSNumber *countryCallingCode = phoneNumber.countryCode;
  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber];

  if (numberFormat == NBEPhoneNumberFormatE164) {
    // Early exit for E164 case (even if the country calling code is invalid)
    // since no formatting of the national number needs to be applied.
    // Extensions are not formatted.
    return [self prefixNumberWithCountryCallingCode:countryCallingCode
                                  phoneNumberFormat:NBEPhoneNumberFormatE164
                            formattedNationalNumber:nationalSignificantNumber
                                 formattedExtension:@""];
  }

  if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
    return nationalSignificantNumber;
  }

  // Note getRegionCodeForCountryCode() is used because formatting information
  // for regions which share a country calling code is contained by only one
  // region for performance reasons. For example, for NANPA regions it will be
  // contained in the metadata for US.
  NSArray *regionCodeArray = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  NSString *regionCode = [regionCodeArray objectAtIndex:0];

  // Metadata cannot be nil because the country calling code is valid (which
  // means that the region code cannot be ZZ and must be one of our supported
  // region codes).
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  NSString *formattedExtension =
      [self maybeGetFormattedExtension:phoneNumber metadata:metadata numberFormat:numberFormat];
  NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber
                                             metadata:metadata
                                    phoneNumberFormat:numberFormat
                                          carrierCode:nil];

  return [self prefixNumberWithCountryCallingCode:countryCallingCode
                                phoneNumberFormat:numberFormat
                          formattedNationalNumber:formattedNationalNumber
                               formattedExtension:formattedExtension];
}

/**
 * Formats a phone number in the specified format using client-defined
 * formatting rules. Note that if the phone number has a country calling code of
 * zero or an otherwise invalid country calling code, we cannot work out things
 * like whether there should be a national prefix applied, or how to format
 * extensions, so we return the national significant number with no formatting
 * applied.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone  number to be
 *     formatted.
 * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
 *     phone number should be formatted into.
 * - param {Array.<i18n.phonenumbers.NumberFormat>} userDefinedFormats formatting
 *     rules specified by clients.
 * @return {string} the formatted phone number.
 */
- (NSString *)formatByPattern:(NBPhoneNumber *)number
                 numberFormat:(NBEPhoneNumberFormat)numberFormat
           userDefinedFormats:(NSArray *)userDefinedFormats
                        error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatByPattern:number
                   numberFormat:numberFormat
             userDefinedFormats:userDefinedFormats];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (NSString *)formatByPattern:(NBPhoneNumber *)number
                 numberFormat:(NBEPhoneNumberFormat)numberFormat
           userDefinedFormats:(NSArray *)userDefinedFormats {
  NSNumber *countryCallingCode = number.countryCode;
  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];

  if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
    return nationalSignificantNumber;
  }

  // Note getRegionCodeForCountryCode() is used because formatting information
  // for regions which share a country calling code is contained by only one
  // region for performance reasons. For example, for NANPA regions it will be
  // contained in the metadata for US.
  NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  NSString *regionCode = nil;
  if (regionCodes != nil && regionCodes.count > 0) {
    regionCode = [regionCodes objectAtIndex:0];
  }

  // Metadata cannot be nil because the country calling code is valid
  /** @type {i18n.phonenumbers.PhoneMetadata} */
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];

  NSString *formattedNumber = @"";
  NBNumberFormat *formattingPattern =
      [self chooseFormattingPatternForNumber:userDefinedFormats
                              nationalNumber:nationalSignificantNumber];

  if (formattingPattern == nil) {
    // If no pattern above is matched, we format the number as a whole.
    formattedNumber = nationalSignificantNumber;
  } else {
    // Before we do a replacement of the national prefix pattern $NP with the
    // national prefix, we need to copy the rule so that subsequent replacements
    // for different numbers have the appropriate national prefix.
    NBNumberFormat *numFormatCopy = [formattingPattern copy];
    NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule;

    if (nationalPrefixFormattingRule.length > 0) {
      NSString *nationalPrefix = metadata.nationalPrefix;
      if (nationalPrefix.length > 0) {
        // Replace $NP with national prefix and $FG with the first group ($1).
        nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule
                                                            regex:NP_PATTERN
                                                     withTemplate:nationalPrefix];
        nationalPrefixFormattingRule = [self replaceStringByRegex:nationalPrefixFormattingRule
                                                            regex:FG_PATTERN
                                                     withTemplate:@"\\$1"];
        numFormatCopy.nationalPrefixFormattingRule = nationalPrefixFormattingRule;
      } else {
        // We don't want to have a rule for how to format the national prefix if
        // there isn't one.
        numFormatCopy.nationalPrefixFormattingRule = @"";
      }
    }

    formattedNumber = [self formatNsnUsingPattern:nationalSignificantNumber
                                formattingPattern:numFormatCopy
                                     numberFormat:numberFormat
                                      carrierCode:nil];
  }

  NSString *formattedExtension =
      [self maybeGetFormattedExtension:number metadata:metadata numberFormat:numberFormat];

  // NSLog(@"!@#  prefixNumberWithCountryCallingCode called [%@]", formattedExtension);
  return [self prefixNumberWithCountryCallingCode:countryCallingCode
                                phoneNumberFormat:numberFormat
                          formattedNationalNumber:formattedNumber
                               formattedExtension:formattedExtension];
}

/**
 * Formats a phone number in national format for dialing using the carrier as
 * specified in the {@code carrierCode}. The {@code carrierCode} will always be
 * used regardless of whether the phone number already has a preferred domestic
 * carrier code stored. If {@code carrierCode} contains an empty string, returns
 * the number in national format without any carrier code.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
 *     formatted.
 * - param {string} carrierCode the carrier selection code to be used.
 * @return {string} the formatted phone number in national format for dialing
 *     using the carrier as specified in the {@code carrierCode}.
 */
- (NSString *)formatNationalNumberWithCarrierCode:(NBPhoneNumber *)number
                                      carrierCode:(NSString *)carrierCode
                                            error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatNationalNumberWithCarrierCode:number carrierCode:carrierCode];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) {
      (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
    }
  }
  return res;
}

- (NSString *)formatNationalNumberWithCarrierCode:(NBPhoneNumber *)number
                                      carrierCode:(NSString *)carrierCode {
  NSNumber *countryCallingCode = number.countryCode;
  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];

  if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
    return nationalSignificantNumber;
  }

  // Note getRegionCodeForCountryCode() is used because formatting information
  // for regions which share a country calling code is contained by only one
  // region for performance reasons. For example, for NANPA regions it will be
  // contained in the metadata for US.
  NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
  // Metadata cannot be nil because the country calling code is valid.
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  NSString *formattedExtension = [self maybeGetFormattedExtension:number
                                                         metadata:metadata
                                                     numberFormat:NBEPhoneNumberFormatNATIONAL];
  NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber
                                             metadata:metadata
                                    phoneNumberFormat:NBEPhoneNumberFormatNATIONAL
                                          carrierCode:carrierCode];
  return [self prefixNumberWithCountryCallingCode:countryCallingCode
                                phoneNumberFormat:NBEPhoneNumberFormatNATIONAL
                          formattedNationalNumber:formattedNationalNumber
                               formattedExtension:formattedExtension];
}

/**
 * - param {number} countryCallingCode
 * - param {?string} regionCode
 * @return {i18n.phonenumbers.PhoneMetadata}
 * @private
 */
- (NBPhoneMetaData *)getMetadataForRegionOrCallingCode:(NSNumber *)countryCallingCode
                                            regionCode:(NSString *)regionCode {
  NBMetadataHelper *helper = self.helper;

  return [regionCode isEqualToString:NB_REGION_CODE_FOR_NON_GEO_ENTITY]
             ? [helper getMetadataForNonGeographicalRegion:countryCallingCode]
             : [helper getMetadataForRegion:regionCode];
}

/**
 * Formats a phone number in national format for dialing using the carrier as
 * specified in the preferred_domestic_carrier_code field of the PhoneNumber
 * object passed in. If that is missing, use the {@code fallbackCarrierCode}
 * passed in instead. If there is no {@code preferred_domestic_carrier_code},
 * and the {@code fallbackCarrierCode} contains an empty string, return the
 * number in national format without any carrier code.
 *
 * <p>Use {@link #formatNationalNumberWithCarrierCode} instead if the carrier
 * code passed in should take precedence over the number's
 * {@code preferred_domestic_carrier_code} when formatting.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
 *     formatted.
 * - param {string} fallbackCarrierCode the carrier selection code to be used, if
 *     none is found in the phone number itself.
 * @return {string} the formatted phone number in national format for dialing
 *     using the number's preferred_domestic_carrier_code, or the
 *     {@code fallbackCarrierCode} passed in if none is found.
 */
- (NSString *)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber *)number
                                       fallbackCarrierCode:(NSString *)fallbackCarrierCode
                                                     error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatNationalNumberWithCarrierCode:number carrierCode:fallbackCarrierCode];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) {
      (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
    }
  }

  return res;
}

- (NSString *)formatNationalNumberWithPreferredCarrierCode:(NBPhoneNumber *)number
                                       fallbackCarrierCode:(NSString *)fallbackCarrierCode {
  NSString *domesticCarrierCode = number.preferredDomesticCarrierCode != nil
                                      ? number.preferredDomesticCarrierCode
                                      : fallbackCarrierCode;
  return [self formatNationalNumberWithCarrierCode:number carrierCode:domesticCarrierCode];
}

/**
 * Returns a number formatted in such a way that it can be dialed from a mobile
 * phone in a specific region. If the number cannot be reached from the region
 * (e.g. some countries block toll-free numbers from being called outside of the
 * country), the method returns an empty string.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
 *     formatted.
 * - param {string} regionCallingFrom the region where the call is being placed.
 * - param {boolean} withFormatting whether the number should be returned with
 *     formatting symbols, such as spaces and dashes.
 * @return {string} the formatted phone number.
 */
- (NSString *)formatNumberForMobileDialing:(NBPhoneNumber *)number
                         regionCallingFrom:(NSString *)regionCallingFrom
                            withFormatting:(BOOL)withFormatting
                                     error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatNumberForMobileDialing:number
                           regionCallingFrom:regionCallingFrom
                              withFormatting:withFormatting];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (NSString *)formatNumberForMobileDialing:(NBPhoneNumber *)number
                         regionCallingFrom:(NSString *)regionCallingFrom
                            withFormatting:(BOOL)withFormatting {
  NSNumber *countryCallingCode = number.countryCode;

  if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
    return [NBMetadataHelper hasValue:number.rawInput] ? number.rawInput : @"";
  }

  NSString *formattedNumber = @"";
  // Clear the extension, as that part cannot normally be dialed together with
  // the main number.
  NBPhoneNumber *numberNoExt = [number copy];
  numberNoExt.extension = @"";

  NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
  if ([regionCallingFrom isEqualToString:regionCode]) {
    NBEPhoneNumberType numberType = [self getNumberType:numberNoExt];
    BOOL isFixedLineOrMobile = (numberType == NBEPhoneNumberTypeFIXED_LINE) ||
                               (numberType == NBEPhoneNumberTypeMOBILE) ||
                               (numberType == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE);
    // Carrier codes may be needed in some countries. We handle this here.
    if ([regionCode isEqualToString:@"CO"] && numberType == NBEPhoneNumberTypeFIXED_LINE) {
      formattedNumber =
          [self formatNationalNumberWithCarrierCode:numberNoExt
                                        carrierCode:COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX];
    } else if ([regionCode isEqualToString:@"BR"] && isFixedLineOrMobile) {
      formattedNumber = [NBMetadataHelper hasValue:numberNoExt.preferredDomesticCarrierCode]
                            ? [self formatNationalNumberWithPreferredCarrierCode:numberNoExt
                                                             fallbackCarrierCode:@""]
                            : @"";
      // Brazilian fixed line and mobile numbers need to be dialed with a
      // carrier code when called within Brazil. Without that, most of the
      // carriers won't connect the call. Because of that, we return an
      // empty string here.
    } else {
      // For NANPA countries, non-geographical countries, and Mexican fixed
      // line and mobile numbers, we output international format for numbersi
      // that can be dialed internationally as that always works.
      if ((countryCallingCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_ ||
           [regionCode isEqualToString:NB_REGION_CODE_FOR_NON_GEO_ENTITY] ||
           // MX fixed line and mobile numbers should always be formatted in
           // international format, even when dialed within MX. For national
           // format to work, a carrier code needs to be used, and the correct
           // carrier code depends on if the caller and callee are from the
           // same local area. It is trickier to get that to work correctly than
           // using international format, which is tested to work fine on all
           // carriers.
           ([regionCode isEqualToString:@"MX"] && isFixedLineOrMobile)) &&
          [self canBeInternationallyDialled:numberNoExt]) {
        formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
      } else {
        formattedNumber = [self format:numberNoExt numberFormat:NBEPhoneNumberFormatNATIONAL];
      }
    }
  } else if ([self canBeInternationallyDialled:numberNoExt]) {
    return withFormatting ? [self format:numberNoExt numberFormat:NBEPhoneNumberFormatINTERNATIONAL]
                          : [self format:numberNoExt numberFormat:NBEPhoneNumberFormatE164];
  }

  return withFormatting ? formattedNumber
                        : [self normalizeHelper:formattedNumber
                              normalizationReplacements:DIALLABLE_CHAR_MAPPINGS
                                       removeNonMatches:YES];
}

/**
 * Formats a phone number for out-of-country dialing purposes. If no
 * regionCallingFrom is supplied, we format the number in its INTERNATIONAL
 * format. If the country calling code is the same as that of the region where
 * the number is from, then NATIONAL formatting will be applied.
 *
 * <p>If the number itself has a country calling code of zero or an otherwise
 * invalid country calling code, then we return the number with no formatting
 * applied.
 *
 * <p>Note this function takes care of the case for calling inside of NANPA and
 * between Russia and Kazakhstan (who share the same country calling code). In
 * those cases, no international prefix is used. For regions which have multiple
 * international prefixes, the number in its INTERNATIONAL format will be
 * returned instead.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number to be
 *     formatted.
 * - param {string} regionCallingFrom the region where the call is being placed.
 * @return {string} the formatted phone number.
 */
- (NSString *)formatOutOfCountryCallingNumber:(NBPhoneNumber *)number
                            regionCallingFrom:(NSString *)regionCallingFrom
                                        error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }

  return res;
}

- (NSString *)formatOutOfCountryCallingNumber:(NBPhoneNumber *)number
                            regionCallingFrom:(NSString *)regionCallingFrom {
  if ([self isValidRegionCode:regionCallingFrom] == NO) {
    return [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
  }

  NSNumber *countryCallingCode = [number.countryCode copy];
  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];

  if ([self hasValidCountryCallingCode:countryCallingCode] == NO) {
    return nationalSignificantNumber;
  }

  if (countryCallingCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_) {
    if ([self isNANPACountry:regionCallingFrom]) {
      // For NANPA regions, return the national format for these regions but
      // prefix it with the country calling code.
      return [NSString
          stringWithFormat:@"%@ %@", countryCallingCode,
                           [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL]];
    }
  } else if ([countryCallingCode
                 isEqualToNumber:[self getCountryCodeForValidRegion:regionCallingFrom error:nil]]) {
    // If regions share a country calling code, the country calling code need
    // not be dialled. This also applies when dialling within a region, so this
    // if clause covers both these cases. Technically this is the case for
    // dialling from La Reunion to other overseas departments of France (French
    // Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover
    // this edge case for now and for those cases return the version including
    // country calling code. Details here:
    // http://www.petitfute.com/voyage/225-info-pratiques-reunion
    return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
  }

  // Metadata cannot be nil because we checked 'isValidRegionCode()' above.
  NBPhoneMetaData *metadataForRegionCallingFrom =
      [self.helper getMetadataForRegion:regionCallingFrom];
  NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix;

  // For regions that have multiple international prefixes, the international
  // format of the number is returned, unless there is a preferred international
  // prefix.
  NSString *internationalPrefixForFormatting = @"";

  if ([self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX string:internationalPrefix]) {
    internationalPrefixForFormatting = internationalPrefix;
  } else if ([NBMetadataHelper
                 hasValue:metadataForRegionCallingFrom.preferredInternationalPrefix]) {
    internationalPrefixForFormatting = metadataForRegionCallingFrom.preferredInternationalPrefix;
  }

  NSString *regionCode = [self getRegionCodeForCountryCode:countryCallingCode];
  // Metadata cannot be nil because the country calling code is valid.
  NBPhoneMetaData *metadataForRegion =
      [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:regionCode];
  NSString *formattedNationalNumber = [self formatNsn:nationalSignificantNumber
                                             metadata:metadataForRegion
                                    phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
                                          carrierCode:nil];
  NSString *formattedExtension =
      [self maybeGetFormattedExtension:number
                              metadata:metadataForRegion
                          numberFormat:NBEPhoneNumberFormatINTERNATIONAL];

  NSString *hasLenth =
      [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting,
                                 countryCallingCode, formattedNationalNumber, formattedExtension];
  NSString *hasNotLength =
      [self prefixNumberWithCountryCallingCode:countryCallingCode
                             phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
                       formattedNationalNumber:formattedNationalNumber
                            formattedExtension:formattedExtension];

  return internationalPrefixForFormatting.length > 0 ? hasLenth : hasNotLength;
}

/**
 * A helper function that is used by format and formatByPattern.
 *
 * - param {number} countryCallingCode the country calling code.
 * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
 *     phone number should be formatted into.
 * - param {string} formattedNationalNumber
 * - param {string} formattedExtension
 * @return {string} the formatted phone number.
 * @private
 */
- (NSString *)prefixNumberWithCountryCallingCode:(NSNumber *)countryCallingCode
                               phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat
                         formattedNationalNumber:(NSString *)formattedNationalNumber
                              formattedExtension:(NSString *)formattedExtension {
  switch (numberFormat) {
    case NBEPhoneNumberFormatE164:
      return [NSString stringWithFormat:@"+%@%@%@", countryCallingCode, formattedNationalNumber,
                                        formattedExtension];
    case NBEPhoneNumberFormatINTERNATIONAL:
      return [NSString stringWithFormat:@"+%@ %@%@", countryCallingCode, formattedNationalNumber,
                                        formattedExtension];
    case NBEPhoneNumberFormatRFC3966:
      return [NSString stringWithFormat:@"%@+%@-%@%@", RFC3966_PREFIX, countryCallingCode,
                                        formattedNationalNumber, formattedExtension];
    case NBEPhoneNumberFormatNATIONAL:
    default:
      return [NSString stringWithFormat:@"%@%@", formattedNationalNumber, formattedExtension];
  }
}

/**
 * Formats a phone number using the original phone number format that the number
 * is parsed from. The original format is embedded in the country_code_source
 * field of the PhoneNumber object passed in. If such information is missing,
 * the number will be formatted into the NATIONAL format by default. When the
 * number contains a leading zero and this is unexpected for this country, or we
 * don't have a formatting pattern for the number, the method returns the raw
 * input when it is available.
 *
 * Note this method guarantees no digit will be inserted, removed or modified as
 * a result of formatting.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to
 *     be formatted in its original number format.
 * - param {string} regionCallingFrom the region whose IDD needs to be prefixed
 *     if the original number has one.
 * @return {string} the formatted phone number in its original number format.
 */
- (NSString *)formatInOriginalFormat:(NBPhoneNumber *)number
                   regionCallingFrom:(NSString *)regionCallingFrom
                               error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatInOriginalFormat:number regionCallingFrom:regionCallingFrom];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }

  return res;
}

- (NSString *)formatInOriginalFormat:(NBPhoneNumber *)number
                   regionCallingFrom:(NSString *)regionCallingFrom {
  if ([NBMetadataHelper hasValue:number.rawInput] &&
      ([self hasFormattingPatternForNumber:number] == NO)) {
    // We check if we have the formatting pattern because without that, we might
    // format the number as a group without national prefix.
    return number.rawInput;
  }

  if (number.countryCodeSource == nil) {
    return [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
  }

  NSString *formattedNumber = @"";

  switch ([number.countryCodeSource integerValue]) {
    case NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN:
      formattedNumber = [self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL];
      break;
    case NBECountryCodeSourceFROM_NUMBER_WITH_IDD:
      formattedNumber =
          [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
      break;
    case NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN:
      formattedNumber = [[self format:number numberFormat:NBEPhoneNumberFormatINTERNATIONAL]
          substringFromIndex:1];
      break;
    case NBECountryCodeSourceFROM_DEFAULT_COUNTRY:
    // Fall-through to default case.
    default: {
      NSString *regionCode = [self getRegionCodeForCountryCode:number.countryCode];
      // We strip non-digits from the NDD here, and from the raw input later,
      // so that we can compare them easily.
      NSString *nationalPrefix = [self getNddPrefixForRegion:regionCode stripNonDigits:YES];
      NSString *nationalFormat = [self format:number numberFormat:NBEPhoneNumberFormatNATIONAL];
      if (nationalPrefix == nil || nationalPrefix.length == 0) {
        // If the region doesn't have a national prefix at all, we can safely
        // return the national format without worrying about a national prefix
        // being added.
        formattedNumber = nationalFormat;
        break;
      }
      // Otherwise, we check if the original number was entered with a national
      // prefix.
      if ([self rawInputContainsNationalPrefix:number.rawInput
                                nationalPrefix:nationalPrefix
                                    regionCode:regionCode]) {
        // If so, we can safely return the national format.
        formattedNumber = nationalFormat;
        break;
      }
      // Metadata cannot be nil here because getNddPrefixForRegion() (above)
      // returns nil if there is no metadata for the region.
      NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
      NSString *nationalNumber = [self getNationalSignificantNumber:number];
      NBNumberFormat *formatRule = [self chooseFormattingPatternForNumber:metadata.numberFormats
                                                           nationalNumber:nationalNumber];
      // The format rule could still be nil here if the national number was 0
      // and there was no raw input (this should not be possible for numbers
      // generated by the phonenumber library as they would also not have a
      // country calling code and we would have exited earlier).
      if (formatRule == nil) {
        formattedNumber = nationalFormat;
        break;
      }
      // When the format we apply to this number doesn't contain national
      // prefix, we can just return the national format.
      // TODO: Refactor the code below with the code in
      // isNationalPrefixPresentIfRequired.
      NSString *candidateNationalPrefixRule = formatRule.nationalPrefixFormattingRule;
      // We assume that the first-group symbol will never be _before_ the
      // national prefix.
      NSRange firstGroupRange = [candidateNationalPrefixRule rangeOfString:@"$1"];
      if (firstGroupRange.location == NSNotFound) {
        formattedNumber = nationalFormat;
        break;
      }

      if (firstGroupRange.location <= 0) {
        formattedNumber = nationalFormat;
        break;
      }
      candidateNationalPrefixRule =
          [candidateNationalPrefixRule substringWithRange:NSMakeRange(0, firstGroupRange.location)];
      candidateNationalPrefixRule = [self normalizeDigitsOnly:candidateNationalPrefixRule];
      if (candidateNationalPrefixRule.length == 0) {
        // National prefix not used when formatting this number.
        formattedNumber = nationalFormat;
        break;
      }
      // Otherwise, we need to remove the national prefix from our output.
      NBNumberFormat *numFormatCopy = [formatRule copy];
      numFormatCopy.nationalPrefixFormattingRule = nil;
      formattedNumber = [self formatByPattern:number
                                 numberFormat:NBEPhoneNumberFormatNATIONAL
                           userDefinedFormats:@[ numFormatCopy ]];
      break;
    }
  }

  NSString *rawInput = number.rawInput;
  // If no digit is inserted/removed/modified as a result of our formatting, we
  // return the formatted phone number; otherwise we return the raw input the
  // user entered.
  if (formattedNumber != nil && rawInput.length > 0) {
    NSString *normalizedFormattedNumber = [self normalizeHelper:formattedNumber
                                      normalizationReplacements:DIALLABLE_CHAR_MAPPINGS
                                               removeNonMatches:YES];
    /** @type {string} */
    NSString *normalizedRawInput = [self normalizeHelper:rawInput
                               normalizationReplacements:DIALLABLE_CHAR_MAPPINGS
                                        removeNonMatches:YES];

    if ([normalizedFormattedNumber isEqualToString:normalizedRawInput] == NO) {
      formattedNumber = rawInput;
    }
  }
  return formattedNumber;
}

/**
 * Check if rawInput, which is assumed to be in the national format, has a
 * national prefix. The national prefix is assumed to be in digits-only form.
 * - param {string} rawInput
 * - param {string} nationalPrefix
 * - param {string} regionCode
 * @return {boolean}
 * @private
 */
- (BOOL)rawInputContainsNationalPrefix:(NSString *)rawInput
                        nationalPrefix:(NSString *)nationalPrefix
                            regionCode:(NSString *)regionCode {
  BOOL isValid = NO;
  NSString *normalizedNationalNumber = [self normalizeDigitsOnly:rawInput];
  if ([self isStartingStringByRegex:normalizedNationalNumber regex:nationalPrefix]) {
    // Some Japanese numbers (e.g. 00777123) might be mistaken to contain the
    // national prefix when written without it (e.g. 0777123) if we just do
    // prefix matching. To tackle that, we check the validity of the number if
    // the assumed national prefix is removed (777123 won't be valid in
    // Japan).
    NSString *subString = [normalizedNationalNumber substringFromIndex:nationalPrefix.length];
    NSError *anError = nil;
    isValid = [self isValidNumber:[self parse:subString defaultRegion:regionCode error:&anError]];

    if (anError != nil) return NO;
  }
  return isValid;
}

/**
 * Returns NO if a number is from a region whose national significant number
 * couldn't contain a leading zero, but has the italian_leading_zero field set
 * to NO.
 * - param {i18n.phonenumbers.PhoneNumber} number
 * @return {boolean}
 * @private
 */
- (BOOL)hasUnexpectedItalianLeadingZero:(NBPhoneNumber *)number {
  return number.italianLeadingZero && [self isLeadingZeroPossible:number.countryCode] == NO;
}

/**
 * - param {i18n.phonenumbers.PhoneNumber} number
 * @return {boolean}
 * @private
 */
- (BOOL)hasFormattingPatternForNumber:(NBPhoneNumber *)number {
  NSNumber *countryCallingCode = number.countryCode;
  NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCallingCode];
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:countryCallingCode regionCode:phoneNumberRegion];

  if (metadata == nil) {
    return NO;
  }

  NSString *nationalNumber = [self getNationalSignificantNumber:number];
  NBNumberFormat *formatRule =
      [self chooseFormattingPatternForNumber:metadata.numberFormats nationalNumber:nationalNumber];
  return formatRule != nil;
}

/**
 * Formats a phone number for out-of-country dialing purposes.
 *
 * Note that in this version, if the number was entered originally using alpha
 * characters and this version of the number is stored in raw_input, this
 * representation of the number will be used rather than the digit
 * representation. Grouping information, as specified by characters such as '-'
 * and ' ', will be retained.
 *
 * <p><b>Caveats:</b></p>
 * <ul>
 * <li>This will not produce good results if the country calling code is both
 * present in the raw input _and_ is the start of the national number. This is
 * not a problem in the regions which typically use alpha numbers.
 * <li>This will also not produce good results if the raw input has any grouping
 * information within the first three digits of the national number, and if the
 * function needs to strip preceding digits/words in the raw input before these
 * digits. Normally people group the first three digits together so this is not
 * a huge problem - and will be fixed if it proves to be so.
 * </ul>
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number that needs to
 *     be formatted.
 * - param {string} regionCallingFrom the region where the call is being placed.
 * @return {string} the formatted phone number.
 */
- (NSString *)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber *)number
                                regionCallingFrom:(NSString *)regionCallingFrom
                                            error:(NSError **)error {
  NSString *res = nil;
  @try {
    res = [self formatOutOfCountryKeepingAlphaChars:number regionCallingFrom:regionCallingFrom];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (NSString *)formatOutOfCountryKeepingAlphaChars:(NBPhoneNumber *)number
                                regionCallingFrom:(NSString *)regionCallingFrom {
  NSString *rawInput = number.rawInput;
  // If there is no raw input, then we can't keep alpha characters because there
  // aren't any. In this case, we return formatOutOfCountryCallingNumber.
  if (rawInput == nil || rawInput.length == 0) {
    return [self formatOutOfCountryCallingNumber:number regionCallingFrom:regionCallingFrom];
  }

  NSNumber *countryCode = number.countryCode;
  if ([self hasValidCountryCallingCode:countryCode] == NO) {
    return rawInput;
  }
  // Strip any prefix such as country calling code, IDD, that was present. We do
  // this by comparing the number in raw_input with the parsed number. To do
  // this, first we normalize punctuation. We retain number grouping symbols
  // such as ' ' only.
  rawInput = [self normalizeHelper:rawInput
         normalizationReplacements:ALL_PLUS_NUMBER_GROUPING_SYMBOLS
                  removeNonMatches:NO];
  // NSLog(@"---- formatOutOfCountryKeepingAlphaChars normalizeHelper rawInput [%@]", rawInput);
  // Now we trim everything before the first three digits in the parsed number.
  // We choose three because all valid alpha numbers have 3 digits at the start
  // - if it does not, then we don't trim anything at all. Similarly, if the
  // national number was less than three digits, we don't trim anything at all.
  NSString *nationalNumber = [self getNationalSignificantNumber:number];
  if (nationalNumber.length > 3) {
    int firstNationalNumberDigit =
        [self indexOfStringByString:rawInput
                             target:[nationalNumber substringWithRange:NSMakeRange(0, 3)]];
    if (firstNationalNumberDigit != -1) {
      rawInput = [rawInput substringFromIndex:firstNationalNumberDigit];
    }
  }

  NBPhoneMetaData *metadataForRegionCallingFrom =
      [self.helper getMetadataForRegion:regionCallingFrom];

  if (countryCode.unsignedIntegerValue == NANPA_COUNTRY_CODE_) {
    if ([self isNANPACountry:regionCallingFrom]) {
      return [NSString stringWithFormat:@"%@ %@", countryCode, rawInput];
    }
  } else if (metadataForRegionCallingFrom != nil &&
             [countryCode
                 isEqualToNumber:[self getCountryCodeForValidRegion:regionCallingFrom error:nil]]) {
    NBNumberFormat *formattingPattern =
        [self chooseFormattingPatternForNumber:metadataForRegionCallingFrom.numberFormats
                                nationalNumber:nationalNumber];
    if (formattingPattern == nil) {
      // If no pattern above is matched, we format the original input.
      return rawInput;
    }

    NBNumberFormat *newFormat = [formattingPattern copy];
    // The first group is the first group of digits that the user wrote
    // together.
    newFormat.pattern = @"(\\d+)(.*)";
    // Here we just concatenate them back together after the national prefix
    // has been fixed.
    newFormat.format = @"$1$2";
    // Now we format using this pattern instead of the default pattern, but
    // with the national prefix prefixed if necessary.
    // This will not work in the cases where the pattern (and not the leading
    // digits) decide whether a national prefix needs to be used, since we have
    // overridden the pattern to match anything, but that is not the case in the
    // metadata to date.

    return [self formatNsnUsingPattern:rawInput
                     formattingPattern:newFormat
                          numberFormat:NBEPhoneNumberFormatNATIONAL
                           carrierCode:nil];
  }

  NSString *internationalPrefixForFormatting = @"";
  // If an unsupported region-calling-from is entered, or a country with
  // multiple international prefixes, the international format of the number is
  // returned, unless there is a preferred international prefix.
  if (metadataForRegionCallingFrom != nil) {
    NSString *internationalPrefix = metadataForRegionCallingFrom.internationalPrefix;
    internationalPrefixForFormatting =
        [self matchesEntirely:UNIQUE_INTERNATIONAL_PREFIX string:internationalPrefix]
            ? internationalPrefix
            : metadataForRegionCallingFrom.preferredInternationalPrefix;
  }

  NSString *regionCode = [self getRegionCodeForCountryCode:countryCode];
  // Metadata cannot be nil because the country calling code is valid.
  NBPhoneMetaData *metadataForRegion =
      [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
  NSString *formattedExtension =
      [self maybeGetFormattedExtension:number
                              metadata:metadataForRegion
                          numberFormat:NBEPhoneNumberFormatINTERNATIONAL];

  if (internationalPrefixForFormatting.length > 0) {
    return [NSString stringWithFormat:@"%@ %@ %@%@", internationalPrefixForFormatting, countryCode,
                                      rawInput, formattedExtension];
  } else {
    // Invalid region entered as country-calling-from (so no metadata was found
    // for it) or the region chosen has multiple international dialling
    // prefixes.
    return [self prefixNumberWithCountryCallingCode:countryCode
                                  phoneNumberFormat:NBEPhoneNumberFormatINTERNATIONAL
                            formattedNationalNumber:rawInput
                                 formattedExtension:formattedExtension];
  }
}

/**
 * Note in some regions, the national number can be written in two completely
 * different ways depending on whether it forms part of the NATIONAL format or
 * INTERNATIONAL format. The numberFormat parameter here is used to specify
 * which format to use for those cases. If a carrierCode is specified, this will
 * be inserted into the formatted string to replace $CC.
 *
 * - param {string} number a string of characters representing a phone number.
 * - param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
 *     region that we think this number is from.
 * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
 *     phone number should be formatted into.
 * - param {string=} opt_carrierCode
 * @return {string} the formatted phone number.
 * @private
 */
- (NSString *)formatNsn:(NSString *)phoneNumber
               metadata:(NBPhoneMetaData *)metadata
      phoneNumberFormat:(NBEPhoneNumberFormat)numberFormat
            carrierCode:(NSString *)opt_carrierCode {
  NSArray *intlNumberFormats = metadata.intlNumberFormats;
  // When the intlNumberFormats exists, we use that to format national number
  // for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
  NSArray *availableFormats =
      ([intlNumberFormats count] <= 0 || numberFormat == NBEPhoneNumberFormatNATIONAL)
          ? metadata.numberFormats
          : intlNumberFormats;
  NBNumberFormat *formattingPattern =
      [self chooseFormattingPatternForNumber:availableFormats nationalNumber:phoneNumber];

  if (formattingPattern == nil) {
    return phoneNumber;
  }

  return [self formatNsnUsingPattern:phoneNumber
                   formattingPattern:formattingPattern
                        numberFormat:numberFormat
                         carrierCode:opt_carrierCode];
}

/**
 * - param {Array.<i18n.phonenumbers.NumberFormat>} availableFormats the
 *     available formats the phone number could be formatted into.
 * - param {string} nationalNumber a string of characters representing a phone
 *     number.
 * @return {i18n.phonenumbers.NumberFormat}
 * @private
 */
- (NBNumberFormat *)chooseFormattingPatternForNumber:(NSArray *)availableFormats
                                      nationalNumber:(NSString *)nationalNumber {
  for (NBNumberFormat *numFormat in availableFormats) {
    NSUInteger size = [numFormat.leadingDigitsPatterns count];
    // We always use the last leading_digits_pattern, as it is the most detailed.
    if (size == 0 ||
        [self stringPositionByRegex:nationalNumber
                              regex:[numFormat.leadingDigitsPatterns lastObject]] == 0) {
      if ([self matchesEntirely:numFormat.pattern string:nationalNumber]) {
        return numFormat;
      }
    }
  }

  return nil;
}

/**
 * Note that carrierCode is optional - if nil or an empty string, no carrier
 * code replacement will take place.
 *
 * - param {string} nationalNumber a string of characters representing a phone
 *     number.
 * - param {i18n.phonenumbers.NumberFormat} formattingPattern the formatting rule
 *     the phone number should be formatted into.
 * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
 *     phone number should be formatted into.
 * - param {string=} opt_carrierCode
 * @return {string} the formatted phone number.
 * @private
 */
- (NSString *)formatNsnUsingPattern:(NSString *)nationalNumber
                  formattingPattern:(NBNumberFormat *)formattingPattern
                       numberFormat:(NBEPhoneNumberFormat)numberFormat
                        carrierCode:(NSString *)opt_carrierCode {
  NSString *numberFormatRule = formattingPattern.format;
  NSString *domesticCarrierCodeFormattingRule = formattingPattern.domesticCarrierCodeFormattingRule;
  NSString *formattedNationalNumber = @"";

  if (numberFormat == NBEPhoneNumberFormatNATIONAL && [NBMetadataHelper hasValue:opt_carrierCode] &&
      domesticCarrierCodeFormattingRule.length > 0) {
    // Replace the $CC in the formatting rule with the desired carrier code.
    NSString *carrierCodeFormattingRule =
        [self replaceStringByRegex:domesticCarrierCodeFormattingRule
                             regex:CC_PATTERN
                      withTemplate:opt_carrierCode];
    // Now replace the $FG in the formatting rule with the first group and
    // the carrier code combined in the appropriate way.
    numberFormatRule = [self replaceFirstStringByRegex:numberFormatRule
                                                 regex:FIRST_GROUP_PATTERN
                                          withTemplate:carrierCodeFormattingRule];
    formattedNationalNumber = [self replaceStringByRegex:nationalNumber
                                                   regex:formattingPattern.pattern
                                            withTemplate:numberFormatRule];
  } else {
    // Use the national prefix formatting rule instead.
    NSString *nationalPrefixFormattingRule = formattingPattern.nationalPrefixFormattingRule;
    if (numberFormat == NBEPhoneNumberFormatNATIONAL &&
        [NBMetadataHelper hasValue:nationalPrefixFormattingRule]) {
      NSString *replacePattern = [self replaceFirstStringByRegex:numberFormatRule
                                                           regex:FIRST_GROUP_PATTERN
                                                    withTemplate:nationalPrefixFormattingRule];
      formattedNationalNumber = [self replaceStringByRegex:nationalNumber
                                                     regex:formattingPattern.pattern
                                              withTemplate:replacePattern];
    } else {
      formattedNationalNumber = [self replaceStringByRegex:nationalNumber
                                                     regex:formattingPattern.pattern
                                              withTemplate:numberFormatRule];
    }
  }

  if (numberFormat == NBEPhoneNumberFormatRFC3966) {
    // Strip any leading punctuation.
    formattedNationalNumber =
        [self replaceStringByRegex:formattedNationalNumber
                             regex:[NSString stringWithFormat:@"^%@", SEPARATOR_PATTERN]
                      withTemplate:@""];

    // Replace the rest with a dash between each number group.
    formattedNationalNumber = [self replaceStringByRegex:formattedNationalNumber
                                                   regex:SEPARATOR_PATTERN
                                            withTemplate:@"-"];
  }
  return formattedNationalNumber;
}

/**
 * Gets a valid number for the specified region.
 *
 * - param {string} regionCode the region for which an example number is needed.
 * @return {i18n.phonenumbers.PhoneNumber} a valid fixed-line number for the
 *     specified region. Returns nil when the metadata does not contain such
 *     information, or the region 001 is passed in. For 001 (representing non-
 *     geographical numbers), call {@link #getExampleNumberForNonGeoEntity}
 *     instead.
 */
- (NBPhoneNumber *)getExampleNumber:(NSString *)regionCode error:(NSError *__autoreleasing *)error {
  NBPhoneNumber *res =
      [self getExampleNumberForType:regionCode type:NBEPhoneNumberTypeFIXED_LINE error:error];
  return res;
}

/**
 * Gets a valid number for the specified region and number type.
 *
 * - param {string} regionCode the region for which an example number is needed.
 * - param {i18n.phonenumbers.PhoneNumberType} type the type of number that is
 *     needed.
 * @return {i18n.phonenumbers.PhoneNumber} a valid number for the specified
 *     region and type. Returns nil when the metadata does not contain such
 *     information or if an invalid region or region 001 was entered.
 *     For 001 (representing non-geographical numbers), call
 *     {@link #getExampleNumberForNonGeoEntity} instead.
 */
- (NBPhoneNumber *)getExampleNumberForType:(NSString *)regionCode
                                      type:(NBEPhoneNumberType)type
                                     error:(NSError *__autoreleasing *)error {
  NBPhoneNumber *res = nil;

  if ([self isValidRegionCode:regionCode] == NO) {
    return nil;
  }

  NBPhoneNumberDesc *desc =
      [self getNumberDescByType:[self.helper getMetadataForRegion:regionCode] type:type];

  if ([NBMetadataHelper hasValue:desc.exampleNumber]) {
    return [self parse:desc.exampleNumber defaultRegion:regionCode error:error];
  }

  return res;
}

/**
 * Gets a valid number for the specified country calling code for a
 * non-geographical entity.
 *
 * - param {number} countryCallingCode the country calling code for a
 *     non-geographical entity.
 * @return {i18n.phonenumbers.PhoneNumber} a valid number for the
 *     non-geographical entity. Returns nil when the metadata does not contain
 *     such information, or the country calling code passed in does not belong
 *     to a non-geographical entity.
 */
- (NBPhoneNumber *)getExampleNumberForNonGeoEntity:(NSNumber *)countryCallingCode
                                             error:(NSError *__autoreleasing *)error {
  NBPhoneNumber *res = nil;

  NBPhoneMetaData *metadata = [self.helper getMetadataForNonGeographicalRegion:countryCallingCode];

  if (metadata != nil) {
    NSString *fetchedExampleNumber = nil;
    if ([NBMetadataHelper hasValue:metadata.mobile.exampleNumber]) {
      fetchedExampleNumber = metadata.mobile.exampleNumber;
    } else if ([NBMetadataHelper hasValue:metadata.tollFree.exampleNumber]) {
      fetchedExampleNumber = metadata.tollFree.exampleNumber;
    } else if ([NBMetadataHelper hasValue:metadata.sharedCost.exampleNumber]) {
      fetchedExampleNumber = metadata.sharedCost.exampleNumber;
    } else if ([NBMetadataHelper hasValue:metadata.voip.exampleNumber]) {
      fetchedExampleNumber = metadata.voip.exampleNumber;
    } else if ([NBMetadataHelper hasValue:metadata.voicemail.exampleNumber]) {
      fetchedExampleNumber = metadata.voicemail.exampleNumber;
    } else if ([NBMetadataHelper hasValue:metadata.uan.exampleNumber]) {
      fetchedExampleNumber = metadata.uan.exampleNumber;
    } else if ([NBMetadataHelper hasValue:metadata.premiumRate.exampleNumber]) {
      fetchedExampleNumber = metadata.premiumRate.exampleNumber;
    }

    if (fetchedExampleNumber != nil) {
      NSString *callCode =
          [NSString stringWithFormat:@"+%@%@", countryCallingCode, fetchedExampleNumber];
      res = [self parse:callCode defaultRegion:NB_UNKNOWN_REGION error:error];
    }
  }

  return res;
}

/**
 * Gets the formatted extension of a phone number, if the phone number had an
 * extension specified. If not, it returns an empty string.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have
 *     an extension.
 * - param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
 *     region that we think this number is from.
 * - param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
 *     phone number should be formatted into.
 * @return {string} the formatted extension if any.
 * @private
 */
- (NSString *)maybeGetFormattedExtension:(NBPhoneNumber *)number
                                metadata:(NBPhoneMetaData *)metadata
                            numberFormat:(NBEPhoneNumberFormat)numberFormat {
  if ([NBMetadataHelper hasValue:number.extension] == NO) {
    return @"";
  } else {
    if (numberFormat == NBEPhoneNumberFormatRFC3966) {
      return [NSString stringWithFormat:@"%@%@", RFC3966_EXTN_PREFIX, number.extension];
    } else {
      if ([NBMetadataHelper hasValue:metadata.preferredExtnPrefix]) {
        return [NSString stringWithFormat:@"%@%@", metadata.preferredExtnPrefix, number.extension];
      } else {
        return [NSString stringWithFormat:@"%@%@", DEFAULT_EXTN_PREFIX, number.extension];
      }
    }
  }
}

/**
 * - param {i18n.phonenumbers.PhoneMetadata} metadata
 * - param {i18n.phonenumbers.PhoneNumberType} type
 * @return {i18n.phonenumbers.PhoneNumberDesc}
 * @private
 */
- (NBPhoneNumberDesc *)getNumberDescByType:(NBPhoneMetaData *)metadata
                                      type:(NBEPhoneNumberType)type {
  switch (type) {
    case NBEPhoneNumberTypePREMIUM_RATE:
      return metadata.premiumRate;
    case NBEPhoneNumberTypeTOLL_FREE:
      return metadata.tollFree;
    case NBEPhoneNumberTypeMOBILE:
      if (metadata.mobile == nil) return metadata.generalDesc;
      return metadata.mobile;
    case NBEPhoneNumberTypeFIXED_LINE:
    case NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE:
      if (metadata.fixedLine == nil) return metadata.generalDesc;
      return metadata.fixedLine;
    case NBEPhoneNumberTypeSHARED_COST:
      return metadata.sharedCost;
    case NBEPhoneNumberTypeVOIP:
      return metadata.voip;
    case NBEPhoneNumberTypePERSONAL_NUMBER:
      return metadata.personalNumber;
    case NBEPhoneNumberTypePAGER:
      return metadata.pager;
    case NBEPhoneNumberTypeUAN:
      return metadata.uan;
    case NBEPhoneNumberTypeVOICEMAIL:
      return metadata.voicemail;
    default:
      return metadata.generalDesc;
  }
}

/**
 * Gets the type of a phone number.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
 *     to know the type.
 * @return {i18n.phonenumbers.PhoneNumberType} the type of the phone number.
 */
- (NBEPhoneNumberType)getNumberType:(NBPhoneNumber *)phoneNumber {
  NSString *regionCode = [self getRegionCodeForNumber:phoneNumber];
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:phoneNumber.countryCode regionCode:regionCode];
  if (metadata == nil) {
    return NBEPhoneNumberTypeUNKNOWN;
  }

  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:phoneNumber];
  return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata];
}

/**
 * - param {string} nationalNumber
 * - param {i18n.phonenumbers.PhoneMetadata} metadata
 * @return {i18n.phonenumbers.PhoneNumberType}
 * @private
 */
- (NBEPhoneNumberType)getNumberTypeHelper:(NSString *)nationalNumber
                                 metadata:(NBPhoneMetaData *)metadata {
  NBPhoneNumberDesc *generalNumberDesc = metadata.generalDesc;

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:generalNumberDesc] == NO) {
    return NBEPhoneNumberTypeUNKNOWN;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.premiumRate]) {
    return NBEPhoneNumberTypePREMIUM_RATE;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.tollFree]) {
    return NBEPhoneNumberTypeTOLL_FREE;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.sharedCost]) {
    return NBEPhoneNumberTypeSHARED_COST;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voip]) {
    return NBEPhoneNumberTypeVOIP;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.personalNumber]) {
    return NBEPhoneNumberTypePERSONAL_NUMBER;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.pager]) {
    return NBEPhoneNumberTypePAGER;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.uan]) {
    return NBEPhoneNumberTypeUAN;
  }

  if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.voicemail]) {
    return NBEPhoneNumberTypeVOICEMAIL;
  }

  BOOL isFixedLine = [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.fixedLine];
  if (isFixedLine) {
    if (metadata.sameMobileAndFixedLinePattern) {
      return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
    } else if ([self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) {
      return NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE;
    }
    return NBEPhoneNumberTypeFIXED_LINE;
  }

  // Otherwise, test to see if the number is mobile. Only do this if certain
  // that the patterns for mobile and fixed line aren't the same.
  if ([metadata sameMobileAndFixedLinePattern] == NO &&
      [self isNumberMatchingDesc:nationalNumber numberDesc:metadata.mobile]) {
    return NBEPhoneNumberTypeMOBILE;
  }

  return NBEPhoneNumberTypeUNKNOWN;
}

/**
 * - param {string} nationalNumber
 * - param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
 * @return {boolean}
 * @private
 */
- (BOOL)isNumberMatchingDesc:(NSString *)nationalNumber numberDesc:(NBPhoneNumberDesc *)numberDesc {
  NSNumber *actualLength = [NSNumber numberWithUnsignedInteger:nationalNumber.length];

  if (numberDesc.possibleLength.count > 0 &&
      [numberDesc.possibleLength indexOfObject:actualLength] == NSNotFound) {
    return NO;
  }

  return [self matchesEntirely:numberDesc.nationalNumberPattern string:nationalNumber];
}

/**
 * Tests whether a phone number matches a valid pattern. Note this doesn't
 * verify the number is actually in use, which is impossible to tell by just
 * looking at a number itself.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
 *     to validate.
 * @return {boolean} a boolean that indicates whether the number is of a valid
 *     pattern.
 */
- (BOOL)isValidNumber:(NBPhoneNumber *)number {
  NSString *regionCode = [self getRegionCodeForNumber:number];
  return [self isValidNumberForRegion:number regionCode:regionCode];
}

/**
 * Tests whether a phone number is valid for a certain region. Note this doesn't
 * verify the number is actually in use, which is impossible to tell by just
 * looking at a number itself. If the country calling code is not the same as
 * the country calling code for the region, this immediately exits with NO.
 * After this, the specific number pattern rules for the region are examined.
 * This is useful for determining for example whether a particular number is
 * valid for Canada, rather than just a valid NANPA number.
 * Warning: In most cases, you want to use {@link #isValidNumber} instead. For
 * example, this method will mark numbers from British Crown dependencies such
 * as the Isle of Man as invalid for the region "GB" (United Kingdom), since it
 * has its own region code, "IM", which may be undesirable.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
 *     to validate.
 * - param {?string} regionCode the region that we want to validate the phone
 *     number for.
 * @return {boolean} a boolean that indicates whether the number is of a valid
 *     pattern.
 */
- (BOOL)isValidNumberForRegion:(NBPhoneNumber *)number regionCode:(NSString *)regionCode {
  NSNumber *countryCode = [number.countryCode copy];
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
  if (metadata == nil ||
      ([NB_REGION_CODE_FOR_NON_GEO_ENTITY isEqualToString:regionCode] == NO &&
       ![countryCode isEqualToNumber:[self getCountryCodeForValidRegion:regionCode error:nil]])) {
    // Either the region code was invalid, or the country calling code for this
    // number does not match that of the region code.
    return NO;
  }

  NBPhoneNumberDesc *generalNumDesc = metadata.generalDesc;
  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];

  // For regions where we don't have metadata for PhoneNumberDesc, we treat any
  // number passed in as a valid number if its national significant number is
  // between the minimum and maximum lengths defined by ITU for a national
  // significant number.

  if ([NBMetadataHelper hasValue:generalNumDesc.nationalNumberPattern] == NO) {
    NSUInteger numberLength = nationalSignificantNumber.length;
    return numberLength > MIN_LENGTH_FOR_NSN_ && numberLength <= MAX_LENGTH_FOR_NSN_;
  }

  return [self getNumberTypeHelper:nationalSignificantNumber metadata:metadata] !=
         NBEPhoneNumberTypeUNKNOWN;
}

/**
 * Returns the region where a phone number is from. This could be used for
 * geocoding at the region level.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone number whose origin
 *     we want to know.
 * @return {?string} the region where the phone number is from, or nil
 *     if no region matches this calling code.
 */
- (NSString *)getRegionCodeForNumber:(NBPhoneNumber *)phoneNumber {
  if (phoneNumber == nil) {
    return nil;
  }

  NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:phoneNumber.countryCode];
  if (regionCodes == nil || [regionCodes count] <= 0) {
    return nil;
  }

  if ([regionCodes count] == 1) {
    return [regionCodes objectAtIndex:0];
  } else {
    return [self getRegionCodeForNumberFromRegionList:phoneNumber regionCodes:regionCodes];
  }
}

/**
 * - param {i18n.phonenumbers.PhoneNumber} number
 * - param {Array.<string>} regionCodes
 * @return {?string}
 * @private

 */
- (NSString *)getRegionCodeForNumberFromRegionList:(NBPhoneNumber *)phoneNumber
                                       regionCodes:(NSArray *)regionCodes {
  NSString *nationalNumber = [self getNationalSignificantNumber:phoneNumber];
  NSUInteger regionCodesCount = [regionCodes count];

  NBMetadataHelper *helper = self.helper;

  for (NSUInteger i = 0; i < regionCodesCount; i++) {
    NSString *regionCode = [regionCodes objectAtIndex:i];
    NBPhoneMetaData *metadata = [helper getMetadataForRegion:regionCode];

    if ([NBMetadataHelper hasValue:metadata.leadingDigits]) {
      if ([self stringPositionByRegex:nationalNumber regex:metadata.leadingDigits] == 0) {
        return regionCode;
      }
    } else if ([self getNumberTypeHelper:nationalNumber metadata:metadata] !=
               NBEPhoneNumberTypeUNKNOWN) {
      return regionCode;
    }
  }

  return nil;
}

/**
 * Returns the region code that matches the specific country calling code. In
 * the case of no region code being found, ZZ will be returned. In the case of
 * multiple regions, the one designated in the metadata as the 'main' region for
 * this calling code will be returned.
 *
 * - param {number} countryCallingCode the country calling code.
 * @return {string}
 */
- (NSString *)getRegionCodeForCountryCode:(NSNumber *)countryCallingCode {
  NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  return regionCodes == nil ? NB_UNKNOWN_REGION : [regionCodes objectAtIndex:0];
}

/**
 * Returns a list with the region codes that match the specific country calling
 * code. For non-geographical country calling codes, the region code 001 is
 * returned. Also, in the case of no region code being found, an empty list is
 * returned.
 *
 * - param {number} countryCallingCode the country calling code.
 * @return {Array.<string>}
 */
- (NSArray *)getRegionCodesForCountryCode:(NSNumber *)countryCallingCode {
  NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:countryCallingCode];
  return regionCodes == nil ? nil : regionCodes;
}

/**
 * Returns the country calling code for a specific region. For example, this
 * would be 1 for the United States, and 64 for New Zealand.
 *
 * - param {?string} regionCode the region that we want to get the country
 *     calling code for.
 * @return {number} the country calling code for the region denoted by
 *     regionCode.
 */
- (NSNumber *)getCountryCodeForRegion:(NSString *)regionCode {
  if ([self isValidRegionCode:regionCode] == NO) {
    return @0;
  }

  NSError *error = nil;
  NSNumber *res = [self getCountryCodeForValidRegion:regionCode error:&error];
  if (error != nil) {
    return @0;
  }

  return res;
}

/**
 * Returns the country calling code for a specific region. For example, this
 * would be 1 for the United States, and 64 for New Zealand. Assumes the region
 * is already valid.
 *
 * - param {?string} regionCode the region that we want to get the country
 *     calling code for.
 * @return {number} the country calling code for the region denoted by
 *     regionCode.
 * @throws {string} if the region is invalid
 * @private
 */
- (NSNumber *)getCountryCodeForValidRegion:(NSString *)regionCode error:(NSError **)error {
  NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];

  if (metadata == nil) {
    NSDictionary *userInfo = [NSDictionary
        dictionaryWithObject:[NSString stringWithFormat:@"Invalid region code:%@", regionCode]
                      forKey:NSLocalizedDescriptionKey];
    if (error != NULL) {
      (*error) = [NSError errorWithDomain:@"INVALID_REGION_CODE" code:0 userInfo:userInfo];
    }

    return @-1;
  }

  return metadata.countryCode;
}

/**
 * Returns the national dialling prefix for a specific region. For example, this
 * would be 1 for the United States, and 0 for New Zealand. Set stripNonDigits
 * to NO to strip symbols like '~' (which indicates a wait for a dialling
 * tone) from the prefix returned. If no national prefix is present, we return
 * nil.
 *
 * <p>Warning: Do not use this method for do-your-own formatting - for some
 * regions, the national dialling prefix is used only for certain types of
 * numbers. Use the library's formatting functions to prefix the national prefix
 * when required.
 *
 * - param {?string} regionCode the region that we want to get the dialling
 *     prefix for.
 * - param {boolean} stripNonDigits NO to strip non-digits from the national
 *     dialling prefix.
 * @return {?string} the dialling prefix for the region denoted by
 *     regionCode.
 */
- (NSString *)getNddPrefixForRegion:(NSString *)regionCode stripNonDigits:(BOOL)stripNonDigits {
  NBPhoneMetaData *metadata = [self.helper getMetadataForRegion:regionCode];
  if (metadata == nil) {
    return nil;
  }

  NSString *nationalPrefix = metadata.nationalPrefix;
  // If no national prefix was found, we return nil.
  if (nationalPrefix.length == 0) {
    return nil;
  }

  if (stripNonDigits) {
    // Note: if any other non-numeric symbols are ever used in national
    // prefixes, these would have to be removed here as well.
    nationalPrefix = [nationalPrefix stringByReplacingOccurrencesOfString:@"~" withString:@""];
  }
  return nationalPrefix;
}

/**
 * Checks if this is a region under the North American Numbering Plan
 * Administration (NANPA).
 *
 * - param {?string} regionCode the ISO 3166-1 two-letter region code.
 * @return {boolean} NO if regionCode is one of the regions under NANPA.
 */
- (BOOL)isNANPACountry:(NSString *)regionCode {
  BOOL isExists = NO;
  NSArray *res = [NBMetadataHelper
      regionCodeFromCountryCode:[NSNumber numberWithUnsignedInteger:NANPA_COUNTRY_CODE_]];

  for (NSString *inRegionCode in res) {
    if ([inRegionCode isEqualToString:regionCode.uppercaseString]) {
      isExists = YES;
    }
  }

  return regionCode != nil && isExists;
}

/**
 * Checks whether countryCode represents the country calling code from a region
 * whose national significant number could contain a leading zero. An example of
 * such a region is Italy. Returns NO if no metadata for the country is
 * found.
 *
 * - param {number} countryCallingCode the country calling code.
 * @return {boolean}
 */
- (BOOL)isLeadingZeroPossible:(NSNumber *)countryCallingCode {
  NBPhoneMetaData *mainMetadataForCallingCode = [self
      getMetadataForRegionOrCallingCode:countryCallingCode
                             regionCode:[self getRegionCodeForCountryCode:countryCallingCode]];

  return mainMetadataForCallingCode != nil && mainMetadataForCallingCode.leadingZeroPossible;
}

/**
 * Checks if the number is a valid vanity (alpha) number such as 800 MICROSOFT.
 * A valid vanity number will start with at least 3 digits and will have three
 * or more alpha characters. This does not do region-specific checks - to work
 * out if this number is actually valid for a region, it should be parsed and
 * methods such as {@link #isPossibleNumberWithReason} and
 * {@link #isValidNumber} should be used.
 *
 * - param {string} number the number that needs to be checked.
 * @return {boolean} NO if the number is a valid vanity number.
 */
- (BOOL)isAlphaNumber:(NSString *)number {
  if ([self isViablePhoneNumber:number] == NO) {
    // Number is too short, or doesn't match the basic phone number pattern.
    return NO;
  }

  number = NormalizeNonBreakingSpace(number);

  /** @type {!goog.string.StringBuffer} */
  NSString *strippedNumber = [number copy];
  [self maybeStripExtension:&strippedNumber];

  return [self matchesEntirely:VALID_ALPHA_PHONE_PATTERN_STRING string:strippedNumber];
}

/**
 * Convenience wrapper around {@link #isPossibleNumberWithReason}. Instead of
 * returning the reason for failure, this method returns a boolean value.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
 *     checked.
 * @return {boolean} NO if the number is possible.
 */
- (BOOL)isPossibleNumber:(NBPhoneNumber *)number error:(NSError **)error {
  BOOL res = NO;
  @try {
    res = [self isPossibleNumber:number];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (BOOL)isPossibleNumber:(NBPhoneNumber *)number {
  return [self isPossibleNumberWithReason:number] == NBEValidationResultIS_POSSIBLE;
}

/**
 * Helper method to check a number against possible lengths for this region, based on the metadata
 * being passed in, and determine whether it matches, or is too short or too long.
 */
- (NBEValidationResult)validateNumberLength:(NSString *)number
                                   metadata:(NBPhoneMetaData *)metadata {
  return [self validateNumberLength:number metadata:metadata type:NBEPhoneNumberTypeUNKNOWN];
}

/**
 * Helper method to check a number against possible lengths for this number type, and determine
 * whether it matches, or is too short or too long.
 */
- (NBEValidationResult)validateNumberLength:(NSString *)number
                                   metadata:(NBPhoneMetaData *)metadata
                                       type:(NBEPhoneNumberType)type {
  NBPhoneNumberDesc *descForType = [self getNumberDescByType:metadata type:type];
  // There should always be "possibleLengths" set for every element.
  // For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths
  // as the parent, this is missing, so we fall back to the general desc (where no numbers of the
  // type exist at all, there is one possible length (-1) which is guaranteed not to match the
  // length of any real phone number).
  NSArray<NSNumber *> *possibleLengths = [descForType.possibleLength count] == 0
                                             ? metadata.generalDesc.possibleLength
                                             : descForType.possibleLength;
  NSArray<NSNumber *> *localLengths = descForType.possibleLengthLocalOnly;

  if (type == NBEPhoneNumberTypeFIXED_LINE_OR_MOBILE) {
    if ([self descHasPossibleNumberData:[self getNumberDescByType:metadata
                                                             type:NBEPhoneNumberTypeFIXED_LINE]]) {
      // The rare case has been encountered where no fixedLine data is available (true for some
      // non-geographical entities), so we just check mobile.
      return [self validateNumberLength:number metadata:metadata type:NBEPhoneNumberTypeMOBILE];
    } else {
      NBPhoneNumberDesc *mobileDesc =
          [self getNumberDescByType:metadata type:NBEPhoneNumberTypeMOBILE];
      if ([self descHasPossibleNumberData:mobileDesc]) {
        // Merge the mobile data in if there was any. We have to make a copy to do this.
        // Note that when adding the possible lengths from mobile, we have to again check they
        // aren't empty since if they are this indicates they are the same as the general desc and
        // should be obtained from there.
        NSArray *combinedArray =
            [possibleLengths arrayByAddingObjectsFromArray:[mobileDesc.possibleLength count] == 0
                                                               ? metadata.generalDesc.possibleLength
                                                               : mobileDesc.possibleLength];

        // The current list is sorted; we need to merge in the new list and re-sort (duplicates
        // are okay). Sorting isn't so expensive because the lists are very small.
        possibleLengths = [combinedArray sortedArrayUsingSelector:@selector(compare:)];

        if (![localLengths count]) {
          localLengths = mobileDesc.possibleLengthLocalOnly;
        } else {
          NSArray *combinedArray =
              [localLengths arrayByAddingObjectsFromArray:mobileDesc.possibleLengthLocalOnly];
          localLengths = [combinedArray sortedArrayUsingSelector:@selector(compare:)];
        }
      }
    }
  }

  // If the type is not supported at all (indicated by the possible lengths containing -1 at this
  // point) we return invalid length.
  if ([possibleLengths.firstObject isEqualToNumber:@(-1)]) {
    return NBEValidationResultINVALID_LENGTH;
  }

  NSNumber *actualLength = @(number.length);
  // This is safe because there is never an overlap beween the possible lengths and the local-only
  // lengths; this is checked at build time.
  if ([localLengths containsObject:actualLength]) {
    return NBEValidationResultIS_POSSIBLE_LOCAL_ONLY;
  }

  NSNumber *minimumLength = possibleLengths.firstObject;
  NSComparisonResult comparisionResult = [minimumLength compare:actualLength];

  if (comparisionResult == NSOrderedSame) {
    return NBEValidationResultIS_POSSIBLE;
  } else if (comparisionResult == NSOrderedDescending) {
    return NBEValidationResultTOO_SHORT;
  } else if ([possibleLengths.lastObject compare:actualLength] == NSOrderedAscending) {
    return NBEValidationResultTOO_LONG;
  }

  // We skip the first element; we've already checked it.
  NSArray *possibleLengthsSubarray =
      [possibleLengths subarrayWithRange:NSMakeRange(1, possibleLengths.count - 1)];
  return [possibleLengthsSubarray containsObject:actualLength] ? NBEValidationResultIS_POSSIBLE
                                                               : NBEValidationResultINVALID_LENGTH;
}

/**
 * Helper method to check a number against a particular pattern and determine
 * whether it matches, or is too short or too long. Currently, if a number
 * pattern suggests that numbers of length 7 and 10 are possible, and a number
 * in between these possible lengths is entered, such as of length 8, this will
 * return TOO_LONG.
 *
 * - param {string} number
 * - param {i18n.phonenumbers.PhoneNumberDesc} phoneNumberDesc
 * - return {i18n.phonenumbers.PhoneNumberUtil.ValidationResult}
 * @private
 */
- (NBEValidationResult)testNumberLength:(NSString *)number
                                   desc:(NBPhoneNumberDesc *)phoneNumberDesc {
  NSArray *possibleLengths = phoneNumberDesc.possibleLength;
  NSArray *localLengths = phoneNumberDesc.possibleLengthLocalOnly;

  NSUInteger actualLength = number.length;

  if ([localLengths containsObject:@(actualLength)]) {
    return NBEValidationResultIS_POSSIBLE;
  }

  // There should always be "possibleLengths" set for every element. This will
  // be a build-time check once ShortNumberMetadata.xml is migrated to contain
  // this information as well.
  NSNumber *minimumLength = possibleLengths[0];
  if (minimumLength.unsignedIntegerValue == actualLength) {
    return NBEValidationResultIS_POSSIBLE;
  } else if (minimumLength.unsignedIntegerValue > actualLength) {
    return NBEValidationResultTOO_SHORT;
  } else if (possibleLengths.count - 1 < possibleLengths.count) {
    if (((NSNumber *)possibleLengths[possibleLengths.count - 1]).integerValue < actualLength) {
      return NBEValidationResultTOO_LONG;
    }
  }
  // Note that actually the number is not too long if possible_lengths does not
  // contain the length: we know it is less than the highest possible number
  // length, and higher than the lowest possible number length. However, we
  // don't currently have an enum to express this, so we return TOO_LONG in the
  // short-term.
  // We skip the first element since we've already checked it.
  return [possibleLengths containsObject:@(actualLength)] ? NBEValidationResultIS_POSSIBLE
                                                          : NBEValidationResultTOO_LONG;
}

/**
 * Check whether a phone number is a possible number. It provides a more lenient
 * check than {@link #isValidNumber} in the following sense:
 * <ol>
 * <li>It only checks the length of phone numbers. In particular, it doesn't
 * check starting digits of the number.
 * <li>It doesn't attempt to figure out the type of the number, but uses general
 * rules which applies to all types of phone numbers in a region. Therefore, it
 * is much faster than isValidNumber.
 * <li>For fixed line numbers, many regions have the concept of area code, which
 * together with subscriber number constitute the national significant number.
 * It is sometimes okay to dial the subscriber number only when dialing in the
 * same area. This function will return NO if the subscriber-number-only
 * version is passed in. On the other hand, because isValidNumber validates
 * using information on both starting digits (for fixed line numbers, that would
 * most likely be area codes) and length (obviously includes the length of area
 * codes for fixed line numbers), it will return NO for the
 * subscriber-number-only version.
 * </ol>
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
 *     checked.
 * @return {ValidationResult} a
 *     ValidationResult object which indicates whether the number is possible.
 */
- (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber *)number
                                            error:(NSError *__autoreleasing *)error {
  NBEValidationResult res = NBEValidationResultUNKNOWN;
  @try {
    res = [self isPossibleNumberWithReason:number];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }

  return res;
}

- (NBEValidationResult)isPossibleNumberWithReason:(NBPhoneNumber *)number {
  NSString *nationalNumber = [self getNationalSignificantNumber:number];
  NSNumber *countryCode = number.countryCode;
  // Note: For Russian Fed and NANPA numbers, we just use the rules from the
  // default region (US or Russia) since the getRegionCodeForNumber will not
  // work if the number is possible but not valid. This would need to be
  // revisited if the possible number pattern ever differed between various
  // regions within those plans.
  if ([self hasValidCountryCallingCode:countryCode] == NO) {
    return NBEValidationResultINVALID_COUNTRY_CODE;
  }

  NSString *regionCode = [self getRegionCodeForCountryCode:countryCode];
  // Metadata cannot be nil because the country calling code is valid.
  NBPhoneMetaData *metadata =
      [self getMetadataForRegionOrCallingCode:countryCode regionCode:regionCode];
  return [self testNumberLength:nationalNumber desc:metadata.generalDesc];
}

/**
 * Check whether a phone number is a possible number given a number in the form
 * of a string, and the region where the number could be dialed from. It
 * provides a more lenient check than {@link #isValidNumber}. See
 * {@link #isPossibleNumber} for details.
 *
 * <p>This method first parses the number, then invokes
 * {@link #isPossibleNumber} with the resultant PhoneNumber object.
 *
 * - param {string} number the number that needs to be checked, in the form of a
 *     string.
 * - param {string} regionDialingFrom the region that we are expecting the number
 *     to be dialed from.
 *     Note this is different from the region where the number belongs.
 *     For example, the number +1 650 253 0000 is a number that belongs to US.
 *     When written in this form, it can be dialed from any region. When it is
 *     written as 00 1 650 253 0000, it can be dialed from any region which uses
 *     an international dialling prefix of 00. When it is written as
 *     650 253 0000, it can only be dialed from within the US, and when written
 *     as 253 0000, it can only be dialed from within a smaller area in the US
 *     (Mountain View, CA, to be more specific).
 * @return {boolean} NO if the number is possible.
 */
- (BOOL)isPossibleNumberString:(NSString *)number
             regionDialingFrom:(NSString *)regionDialingFrom
                         error:(NSError **)error {
  number = NormalizeNonBreakingSpace(number);

  BOOL res =
      [self isPossibleNumber:[self parse:number defaultRegion:regionDialingFrom error:error]];
  return res;
}

/**
 * Attempts to extract a valid number from a phone number that is too long to be
 * valid, and resets the PhoneNumber object passed in to that valid version. If
 * no valid number could be extracted, the PhoneNumber object passed in will not
 * be modified.
 * - param {i18n.phonenumbers.PhoneNumber} number a PhoneNumber object which
 *     contains a number that is too long to be valid.
 * @return {boolean} NO if a valid phone number can be successfully extracted.
 */

- (BOOL)truncateTooLongNumber:(NBPhoneNumber *)number {
  if ([self isValidNumber:number]) {
    return YES;
  }

  NBPhoneNumber *numberCopy = [number copy];
  NSNumber *nationalNumber = number.nationalNumber;
  do {
    nationalNumber =
        [NSNumber numberWithLongLong:(long long)floor(nationalNumber.unsignedLongLongValue / 10)];
    numberCopy.nationalNumber = [nationalNumber copy];
    if ([nationalNumber isEqualToNumber:@0] ||
        [self isPossibleNumberWithReason:numberCopy] == NBEValidationResultTOO_SHORT) {
      return NO;
    }
  } while ([self isValidNumber:numberCopy] == NO);

  number.nationalNumber = nationalNumber;
  return YES;
}

/**
 * Extracts country calling code from fullNumber, returns it and places the
 * remaining number in nationalNumber. It assumes that the leading plus sign or
 * IDD has already been removed. Returns 0 if fullNumber doesn't start with a
 * valid country calling code, and leaves nationalNumber unmodified.
 *
 * - param {!goog.string.StringBuffer} fullNumber
 * - param {!goog.string.StringBuffer} nationalNumber
 * @return {number}
 */
- (NSNumber *)extractCountryCode:(NSString *)fullNumber nationalNumber:(NSString **)nationalNumber {
  fullNumber = NormalizeNonBreakingSpace(fullNumber);

  if ((fullNumber.length == 0) || ([[fullNumber substringToIndex:1] isEqualToString:@"0"])) {
    // Country codes do not begin with a '0'.
    return @0;
  }

  NSUInteger numberLength = fullNumber.length;
  NSUInteger maxCountryCode = MAX_LENGTH_COUNTRY_CODE_;

  if ([fullNumber hasPrefix:@"+"]) {
    maxCountryCode = MAX_LENGTH_COUNTRY_CODE_ + 1;
  }

  for (NSUInteger i = 1; i <= maxCountryCode && i <= numberLength; ++i) {
    NSString *subNumber = [fullNumber substringWithRange:NSMakeRange(0, i)];
    NSNumber *potentialCountryCode = [NSNumber numberWithInteger:[subNumber integerValue]];

    NSArray *regionCodes = [NBMetadataHelper regionCodeFromCountryCode:potentialCountryCode];
    if (regionCodes != nil && regionCodes.count > 0) {
      if (nationalNumber != NULL) {
        if ((*nationalNumber) == nil) {
          (*nationalNumber) = [NSString stringWithFormat:@"%@", [fullNumber substringFromIndex:i]];
        } else {
          (*nationalNumber) = [NSString
              stringWithFormat:@"%@%@", (*nationalNumber), [fullNumber substringFromIndex:i]];
        }
      }
      return potentialCountryCode;
    }
  }

  return @0;
}

/**
 * Convenience method to get a list of what regions the library has metadata
 * for.
 * @return {!Array.<string>} region codes supported by the library.
 */

- (NSArray *)getSupportedRegions {
  NSArray *allKeys = [[NBMetadataHelper CCode2CNMap] allKeys];
  NSPredicate *predicateIsNaN =
      [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
        return isNan(evaluatedObject);
      }];

  NSArray *supportedRegions = [allKeys filteredArrayUsingPredicate:predicateIsNaN];
  return supportedRegions;
}

/*
 i18n.phonenumbers.PhoneNumberUtil.prototype.getSupportedRegions = function() {
 return goog.array.filter(
 Object.keys(i18n.phonenumbers.metadata.countryToMetadata),
 function(regionCode) {
 return isNaN(regionCode);
 });
 };
 */

/**
 * Convenience method to get a list of what global network calling codes the
 * library has metadata for.
 * @return {!Array.<number>} global network calling codes supported by the
 *     library.
 */
/*
 i18n.phonenumbers.PhoneNumberUtil.prototype.
 getSupportedGlobalNetworkCallingCodes = function() {
 var callingCodesAsStrings = goog.array.filter(
 Object.keys(i18n.phonenumbers.metadata.countryToMetadata),
 function(regionCode) {
 return !isNaN(regionCode);
 });
 return goog.array.map(callingCodesAsStrings,
 function(callingCode) {
 return parseInt(callingCode, 10);
 });
 };
 */

/**
 * Tries to extract a country calling code from a number. This method will
 * return zero if no country calling code is considered to be present. Country
 * calling codes are extracted in the following ways:
 * <ul>
 * <li>by stripping the international dialing prefix of the region the person is
 * dialing from, if this is present in the number, and looking at the next
 * digits
 * <li>by stripping the '+' sign if present and then looking at the next digits
 * <li>by comparing the start of the number and the country calling code of the
 * default region. If the number is not considered possible for the numbering
 * plan of the default region initially, but starts with the country calling
 * code of this region, validation will be reattempted after stripping this
 * country calling code. If this number is considered a possible number, then
 * the first digits will be considered the country calling code and removed as
 * such.
 * </ul>
 *
 * It will throw a i18n.phonenumbers.Error if the number starts with a '+' but
 * the country calling code supplied after this does not match that of any known
 * region.
 *
 * - param {string} number non-normalized telephone number that we wish to
 *     extract a country calling code from - may begin with '+'.
 * - param {i18n.phonenumbers.PhoneMetadata} defaultRegionMetadata metadata
 *     about the region this number may be from.
 * - param {!goog.string.StringBuffer} nationalNumber a string buffer to store
 *     the national significant number in, in the case that a country calling
 *     code was extracted. The number is appended to any existing contents. If
 *     no country calling code was extracted, this will be left unchanged.
 * - param {boolean} keepRawInput NO if the country_code_source and
 *     preferred_carrier_code fields of phoneNumber should be populated.
 * - param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object
 *     where the country_code and country_code_source need to be populated.
 *     Note the country_code is always populated, whereas country_code_source is
 *     only populated when keepCountryCodeSource is NO.
 * @return {number} the country calling code extracted or 0 if none could be
 *     extracted.
 * @throws {i18n.phonenumbers.Error}
 */
- (NSNumber *)maybeExtractCountryCode:(NSString *)number
                             metadata:(NBPhoneMetaData *)defaultRegionMetadata
                       nationalNumber:(NSString **)nationalNumber
                         keepRawInput:(BOOL)keepRawInput
                          phoneNumber:(NBPhoneNumber **)phoneNumber
                                error:(NSError **)error {
  if (nationalNumber == NULL || phoneNumber == NULL || number.length <= 0) {
    return @0;
  }

  NSString *fullNumber = [number copy];
  // Set the default prefix to be something that will never match.
  NSString *possibleCountryIddPrefix = @"";
  if (defaultRegionMetadata != nil) {
    possibleCountryIddPrefix = defaultRegionMetadata.internationalPrefix;
  }

  if (possibleCountryIddPrefix == nil) {
    possibleCountryIddPrefix = @"NonMatch";
  }

  /** @type {i18n.phonenumbers.PhoneNumber.CountryCodeSource} */
  NBECountryCodeSource countryCodeSource =
      [self maybeStripInternationalPrefixAndNormalize:&fullNumber
                                    possibleIddPrefix:possibleCountryIddPrefix];
  if (keepRawInput) {
    (*phoneNumber).countryCodeSource = [NSNumber numberWithInteger:countryCodeSource];
  }

  if (countryCodeSource != NBECountryCodeSourceFROM_DEFAULT_COUNTRY) {
    if (fullNumber.length <= MIN_LENGTH_FOR_NSN_) {
      NSDictionary *userInfo = [NSDictionary
          dictionaryWithObject:[NSString stringWithFormat:@"TOO_SHORT_AFTER_IDD:%@", fullNumber]
                        forKey:NSLocalizedDescriptionKey];
      if (error != NULL) {
        (*error) = [NSError errorWithDomain:@"TOO_SHORT_AFTER_IDD" code:0 userInfo:userInfo];
      }
      return @0;
    }

    NSNumber *potentialCountryCode =
        [self extractCountryCode:fullNumber nationalNumber:nationalNumber];

    if (![potentialCountryCode isEqualToNumber:@0]) {
      (*phoneNumber).countryCode = potentialCountryCode;
      return potentialCountryCode;
    }

    // If this fails, they must be using a strange country calling code that we
    // don't recognize, or that doesn't exist.
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@",
                                                                      potentialCountryCode]
                                    forKey:NSLocalizedDescriptionKey];
    if (error != NULL) {
      (*error) = [NSError errorWithDomain:@"INVALID_COUNTRY_CODE" code:0 userInfo:userInfo];
    }

    return @0;
  } else if (defaultRegionMetadata != nil) {
    // Check to see if the number starts with the country calling code for the
    // default region. If so, we remove the country calling code, and do some
    // checks on the validity of the number before and after.
    NSNumber *defaultCountryCode = defaultRegionMetadata.countryCode;
    NSString *defaultCountryCodeString = [NSString stringWithFormat:@"%@", defaultCountryCode];
    NSString *normalizedNumber = [fullNumber copy];

    if ([normalizedNumber hasPrefix:defaultCountryCodeString]) {
      NSString *potentialNationalNumber =
          [normalizedNumber substringFromIndex:defaultCountryCodeString.length];
      NBPhoneNumberDesc *generalDesc = defaultRegionMetadata.generalDesc;

      NSString *validNumberPattern = generalDesc.nationalNumberPattern;
      // Passing null since we don't need the carrier code.
      [self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber
                                          metadata:defaultRegionMetadata
                                       carrierCode:nil];

      NSString *potentialNationalNumberStr = [potentialNationalNumber copy];
      // If the number was not valid before but is valid now, or if it was too
      // long before, we consider the number with the country calling code
      // stripped to be a better result and keep that instead.
      if ((![self matchesEntirely:validNumberPattern string:fullNumber] &&
           [self matchesEntirely:validNumberPattern string:potentialNationalNumberStr]) ||
          [self testNumberLength:fullNumber desc:generalDesc] == NBEValidationResultTOO_LONG) {
        (*nationalNumber) = [(*nationalNumber) stringByAppendingString:potentialNationalNumberStr];
        if (keepRawInput) {
          (*phoneNumber).countryCodeSource =
              [NSNumber numberWithInteger:NBECountryCodeSourceFROM_NUMBER_WITHOUT_PLUS_SIGN];
        }
        (*phoneNumber).countryCode = defaultCountryCode;
        return defaultCountryCode;
      }
    }
  }
  // No country calling code present.
  (*phoneNumber).countryCode = @0;
  return @0;
}

/**
 * Returns true if there is any possible number data set for a particular PhoneNumberDesc.
 */
- (BOOL)descHasPossibleNumberData:(NBPhoneNumberDesc *)desc {
  // If this is empty, it means numbers of this type inherit from the "general desc" -> the value
  // "-1" means that no numbers exist for this type.
  return [desc.possibleLength count] != 1 ||
         ![[desc.possibleLength firstObject] isEqualToNumber:@(-1)];
}

/**
 * Strips the IDD from the start of the number if present. Helper function used
 * by maybeStripInternationalPrefixAndNormalize.
 *
 * - param {!RegExp} iddPattern the regular expression for the international
 *     prefix.
 * - param {!goog.string.StringBuffer} number the phone number that we wish to
 *     strip any international dialing prefix from.
 * @return {boolean} NO if an international prefix was present.
 * @private
 */
- (BOOL)parsePrefixAsIdd:(NSString *)iddPattern sourceString:(NSString **)number {
  if (number == NULL) {
    return NO;
  }

  NSString *numberStr = [(*number)copy];

  if ([self stringPositionByRegex:numberStr regex:iddPattern] == 0) {
    NSTextCheckingResult *matched =
        [[self matchesByRegex:numberStr regex:iddPattern] objectAtIndex:0];
    NSString *matchedString = [numberStr substringWithRange:matched.range];
    NSUInteger matchEnd = matchedString.length;
    NSString *remainString = [numberStr substringFromIndex:matchEnd];

    NSArray *matchedGroups =
        [_CAPTURING_DIGIT_PATTERN matchesInString:remainString
                                          options:0
                                            range:NSMakeRange(0, remainString.length)];

    if (matchedGroups && [matchedGroups count] > 0 && [matchedGroups objectAtIndex:0] != nil) {
      NSString *digitMatched = [remainString
          substringWithRange:((NSTextCheckingResult *)[matchedGroups objectAtIndex:0]).range];
      if (digitMatched.length > 0) {
        NSString *normalizedGroup = [self normalizeDigitsOnly:digitMatched];
        if ([normalizedGroup isEqualToString:@"0"]) {
          return NO;
        }
      }
    }

    (*number) = [remainString copy];
    return YES;
  }

  return NO;
}

/**
 * Strips any international prefix (such as +, 00, 011) present in the number
 * provided, normalizes the resulting number, and indicates if an international
 * prefix was present.
 *
 * - param {!goog.string.StringBuffer} number the non-normalized telephone number
 *     that we wish to strip any international dialing prefix from.
 * - param {string} possibleIddPrefix the international direct dialing prefix
 *     from the region we think this number may be dialed in.
 * @return {CountryCodeSource} the corresponding
 *     CountryCodeSource if an international dialing prefix could be removed
 *     from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if
 *     the number did not seem to be in international format.
 */
- (NBECountryCodeSource)maybeStripInternationalPrefixAndNormalize:(NSString **)numberStr
                                                possibleIddPrefix:(NSString *)possibleIddPrefix {
  if (numberStr == NULL || (*numberStr).length == 0) {
    return NBECountryCodeSourceFROM_DEFAULT_COUNTRY;
  }

  // Check to see if the number begins with one or more plus signs.
  if ([self isStartingStringByRegex:(*numberStr)regex:LEADING_PLUS_CHARS_PATTERN]) {
    (*numberStr) =
        [self replaceStringByRegex:(*numberStr)regex:LEADING_PLUS_CHARS_PATTERN withTemplate:@""];
    // Can now normalize the rest of the number since we've consumed the '+'
    // sign at the start.
    (*numberStr) = [self normalize:(*numberStr)];
    return NBECountryCodeSourceFROM_NUMBER_WITH_PLUS_SIGN;
  }

  // Attempt to parse the first digits as an international prefix.
  NSString *iddPattern = [possibleIddPrefix copy];
  [self normalizeSB:numberStr];

  return [self parsePrefixAsIdd:iddPattern sourceString:numberStr]
             ? NBECountryCodeSourceFROM_NUMBER_WITH_IDD
             : NBECountryCodeSourceFROM_DEFAULT_COUNTRY;
}

/**
 * Strips any national prefix (such as 0, 1) present in the number provided.
 *
 * - param {!goog.string.StringBuffer} number the normalized telephone number
 *     that we wish to strip any national dialing prefix from.
 * - param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
 *     region that we think this number is from.
 * - param {goog.string.StringBuffer} carrierCode a place to insert the carrier
 *     code if one is extracted.
 * @return {boolean} NO if a national prefix or carrier code (or both) could
 *     be extracted.
 */
- (BOOL)maybeStripNationalPrefixAndCarrierCode:(NSString **)number
                                      metadata:(NBPhoneMetaData *)metadata
                                   carrierCode:(NSString **)carrierCode {
  if (number == NULL) {
    return NO;
  }

  NSString *numberStr = [(*number)copy];
  NSUInteger numberLength = numberStr.length;
  NSString *possibleNationalPrefix = metadata.nationalPrefixForParsing;

  if (numberLength == 0 || [NBMetadataHelper hasValue:possibleNationalPrefix] == NO) {
    // Early return for numbers of zero length.
    return NO;
  }

  // Attempt to parse the first digits as a national prefix.
  NSString *prefixPattern = [NSString stringWithFormat:@"^(?:%@)", possibleNationalPrefix];
  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self regularExpressionWithPattern:prefixPattern options:0 error:&error];

  NSArray *prefixMatcher =
      [currentPattern matchesInString:numberStr options:0 range:NSMakeRange(0, numberLength)];
  if (prefixMatcher && [prefixMatcher count] > 0) {
    NSString *nationalNumberRule = metadata.generalDesc.nationalNumberPattern;
    NSTextCheckingResult *firstMatch = [prefixMatcher objectAtIndex:0];
    NSString *firstMatchString = [numberStr substringWithRange:firstMatch.range];

    // prefixMatcher[numOfGroups] == null implies nothing was captured by the
    // capturing groups in possibleNationalPrefix; therefore, no transformation
    // is necessary, and we just remove the national prefix.
    NSUInteger numOfGroups = firstMatch.numberOfRanges - 1;
    NSString *transformRule = metadata.nationalPrefixTransformRule;
    NSString *transformedNumber = @"";
    NSRange firstRange = [firstMatch rangeAtIndex:numOfGroups];
    NSString *firstMatchStringWithGroup =
        (firstRange.location != NSNotFound && firstRange.location < numberStr.length)
            ? [numberStr substringWithRange:firstRange]
            : nil;
    BOOL noTransform = (transformRule == nil || transformRule.length == 0 ||
                        [NBMetadataHelper hasValue:firstMatchStringWithGroup] == NO);

    if (noTransform) {
      transformedNumber = [numberStr substringFromIndex:firstMatchString.length];
    } else {
      transformedNumber =
          [self replaceFirstStringByRegex:numberStr regex:prefixPattern withTemplate:transformRule];
    }
    // If the original number was viable, and the resultant number is not,
    // we return.
    if ([NBMetadataHelper hasValue:nationalNumberRule] &&
        [self matchesEntirely:nationalNumberRule string:numberStr] &&
        [self matchesEntirely:nationalNumberRule string:transformedNumber] == NO) {
      return NO;
    }

    if ((noTransform && numOfGroups > 0 && [NBMetadataHelper hasValue:firstMatchStringWithGroup]) ||
        (!noTransform && numOfGroups > 1)) {
      if (carrierCode != NULL && (*carrierCode) != nil) {
        (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchStringWithGroup];
      }
    } else if ((noTransform && numOfGroups > 0 && [NBMetadataHelper hasValue:firstMatchString]) ||
               (!noTransform && numOfGroups > 1)) {
      if (carrierCode != NULL && (*carrierCode) != nil) {
        (*carrierCode) = [(*carrierCode) stringByAppendingString:firstMatchString];
      }
    }

    (*number) = transformedNumber;
    return YES;
  }
  return NO;
}

/**
 * Strips any extension (as in, the part of the number dialled after the call is
 * connected, usually indicated with extn, ext, x or similar) from the end of
 * the number, and returns it.
 *
 * - param {!goog.string.StringBuffer} number the non-normalized telephone number
 *     that we wish to strip the extension from.
 * @return {string} the phone extension.
 */
- (NSString *)maybeStripExtension:(NSString **)number {
  if (number == NULL) {
    return @"";
  }

  NSString *numberStr = [(*number)copy];
  int mStart = [self stringPositionByRegex:numberStr regex:EXTN_PATTERN];

  // If we find a potential extension, and the number preceding this is a viable
  // number, we assume it is an extension.
  if (mStart >= 0 &&
      [self isViablePhoneNumber:[numberStr substringWithRange:NSMakeRange(0, mStart)]]) {
    // The numbers are captured into groups in the regular expression.
    NSTextCheckingResult *firstMatch = [self matchFirstByRegex:numberStr regex:EXTN_PATTERN];
    NSUInteger matchedGroupsLength = [firstMatch numberOfRanges];

    for (NSUInteger i = 1; i < matchedGroupsLength; i++) {
      NSRange curRange = [firstMatch rangeAtIndex:i];

      if (curRange.location != NSNotFound && curRange.location < numberStr.length) {
        NSString *matchString = [(*number) substringWithRange:curRange];
        // We go through the capturing groups until we find one that captured
        // some digits. If none did, then we will return the empty string.
        NSString *tokenedString = [numberStr substringWithRange:NSMakeRange(0, mStart)];
        (*number) = @"";
        (*number) = [(*number) stringByAppendingString:tokenedString];

        return matchString;
      }
    }
  }

  return @"";
}

/**
 * Checks to see that the region code used is valid, or if it is not valid, that
 * the number to parse starts with a + symbol so that we can attempt to infer
 * the region from the number.
 * - param {string} numberToParse number that we are attempting to parse.
 * - param {?string} defaultRegion region that we are expecting the number to be
 *     from.
 * @return {boolean} NO if it cannot use the region provided and the region
 *     cannot be inferred.
 * @private
 */
- (BOOL)checkRegionForParsing:(NSString *)numberToParse defaultRegion:(NSString *)defaultRegion {
  // If the number is nil or empty, we can't infer the region.
  return [self isValidRegionCode:defaultRegion] ||
         (numberToParse != nil && numberToParse.length > 0 &&
          [self isStartingStringByRegex:numberToParse regex:LEADING_PLUS_CHARS_PATTERN]);
}

/**
 * Parses a string and returns it in proto buffer format. This method will throw
 * a {@link i18n.phonenumbers.Error} if the number is not considered to be a
 * possible number. Note that validation of whether the number is actually a
 * valid number for a particular region is not performed. This can be done
 * separately with {@link #isValidNumber}.
 *
 * - param {?string} numberToParse number that we are attempting to parse. This
 *     can contain formatting such as +, ( and -, as well as a phone number
 *     extension. It can also be provided in RFC3966 format.
 * - param {?string} defaultRegion region that we are expecting the number to be
 *     from. This is only used if the number being parsed is not written in
 *     international format. The country_code for the number in this case would
 *     be stored as that of the default region supplied. If the number is
 *     guaranteed to start with a '+' followed by the country calling code, then
 *     'ZZ' or nil can be supplied.
 * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
 *     with the parsed number.
 * @throws {i18n.phonenumbers.Error} if the string is not considered to be a
 *     viable phone number or if no default region was supplied and the number
 *     is not in international format (does not start with +).
 */
- (NBPhoneNumber *)parse:(NSString *)numberToParse
           defaultRegion:(NSString *)defaultRegion
                   error:(NSError **)error {
  NSError *anError = nil;
  NBPhoneNumber *phoneNumber = [self parseHelper:numberToParse
                                   defaultRegion:defaultRegion
                                    keepRawInput:NO
                                     checkRegion:YES
                                           error:&anError];

  if (anError != nil) {
    if (error != NULL) {
      (*error) = [self errorWithObject:anError.description withDomain:anError.domain];
    }
  }
  return phoneNumber;
}

/**
 * Parses a string using the phone's carrier region (when available, ZZ otherwise).
 * This uses the country the sim card in the phone is registered with.
 * For example if you have an AT&T sim card but are in Europe, this will parse the
 * number using +1 (AT&T is a US Carrier) as the default country code.
 * This also works for CDMA phones which don't have a sim card.
 */
- (NBPhoneNumber *)parseWithPhoneCarrierRegion:(NSString *)numberToParse error:(NSError **)error {
  numberToParse = NormalizeNonBreakingSpace(numberToParse);

  NSString *defaultRegion = nil;
#if TARGET_OS_IOS
  defaultRegion = [self countryCodeByCarrier];
#else
  defaultRegion = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
#endif
  if ([NB_UNKNOWN_REGION isEqualToString:defaultRegion]) {
    // get region from device as a failover (e.g. iPad)
    NSLocale *currentLocale = [NSLocale currentLocale];
    defaultRegion = [currentLocale objectForKey:NSLocaleCountryCode];
  }

  return [self parse:numberToParse defaultRegion:defaultRegion error:error];
}

#if TARGET_OS_IOS

static CTTelephonyNetworkInfo *_telephonyNetworkInfo;

- (CTTelephonyNetworkInfo *)telephonyNetworkInfo {
  // cache telephony network info;
  // CTTelephonyNetworkInfo objects are unnecessarily created for every call to
  // parseWithPhoneCarrierRegion:error: when in reality this information not change while an app
  // lives in memory real-world performance test while parsing 93 phone numbers: before change:
  // 126ms after change:    32ms using static instance prevents deallocation crashes due to ios bug

  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    _telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init];
  });

  return _telephonyNetworkInfo;
}

- (NSString *)countryCodeByCarrier {
  NSString *isoCode = [[self.telephonyNetworkInfo subscriberCellularProvider] isoCountryCode];

  // The 2nd part of the if is working around an iOS 7 bug
  // If the SIM card is missing, iOS 7 returns an empty string instead of nil
  if (isoCode.length == 0) {
    isoCode = NB_UNKNOWN_REGION;
  }

  return isoCode;
}

#endif

/**
 * Parses a string and returns it in proto buffer format. This method differs
 * from {@link #parse} in that it always populates the raw_input field of the
 * protocol buffer with numberToParse as well as the country_code_source field.
 *
 * - param {string} numberToParse number that we are attempting to parse. This
 *     can contain formatting such as +, ( and -, as well as a phone number
 *     extension.
 * - param {?string} defaultRegion region that we are expecting the number to be
 *     from. This is only used if the number being parsed is not written in
 *     international format. The country calling code for the number in this
 *     case would be stored as that of the default region supplied.
 * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
 *     with the parsed number.
 * @throws {i18n.phonenumbers.Error} if the string is not considered to be a
 *     viable phone number or if no default region was supplied.
 */
- (NBPhoneNumber *)parseAndKeepRawInput:(NSString *)numberToParse
                          defaultRegion:(NSString *)defaultRegion
                                  error:(NSError **)error {
  if ([self isValidRegionCode:defaultRegion] == NO) {
    if (numberToParse.length > 0 && [numberToParse hasPrefix:@"+"] == NO) {
      NSDictionary *userInfo = [NSDictionary
          dictionaryWithObject:[NSString stringWithFormat:@"Invalid country code:%@", numberToParse]
                        forKey:NSLocalizedDescriptionKey];
      if (error != NULL) {
        (*error) = [NSError errorWithDomain:@"INVALID_COUNTRY_CODE" code:0 userInfo:userInfo];
      }
    }
  }
  return [self parseHelper:numberToParse
             defaultRegion:defaultRegion
              keepRawInput:YES
               checkRegion:YES
                     error:error];
}

/**
 * A helper function to set the values related to leading zeros in a
 * PhoneNumber.
 *
 * - param {string} nationalNumber the number we are parsing.
 * - param {i18n.phonenumbers.PhoneNumber} phoneNumber a phone number proto
 *     buffer to fill in.
 * @private
 */
- (void)setItalianLeadingZerosForPhoneNumber:(NSString *)nationalNumber
                                 phoneNumber:(NBPhoneNumber *)phoneNumber {
  if (nationalNumber.length > 1 && [nationalNumber hasPrefix:@"0"]) {
    phoneNumber.italianLeadingZero = YES;
    NSInteger numberOfLeadingZeros = 1;
    // Note that if the national number is all "0"s, the last "0" is not counted
    // as a leading zero.
    while (numberOfLeadingZeros < nationalNumber.length - 1 &&
           [[nationalNumber substringWithRange:NSMakeRange(numberOfLeadingZeros, 1)]
               isEqualToString:@"0"]) {
      numberOfLeadingZeros++;
    }
    if (numberOfLeadingZeros != 1) {
      phoneNumber.numberOfLeadingZeros = @(numberOfLeadingZeros);
    }
  }
}

/**
 * Parses a string and returns it in proto buffer format. This method is the
 * same as the public {@link #parse} method, with the exception that it allows
 * the default region to be nil, for use by {@link #isNumberMatch}.
 *
 * - param {?string} numberToParse number that we are attempting to parse. This
 *     can contain formatting such as +, ( and -, as well as a phone number
 *     extension.
 * - param {?string} defaultRegion region that we are expecting the number to be
 *     from. This is only used if the number being parsed is not written in
 *     international format. The country calling code for the number in this
 *     case would be stored as that of the default region supplied.
 * - param {boolean} keepRawInput whether to populate the raw_input field of the
 *     phoneNumber with numberToParse.
 * - param {boolean} checkRegion should be set to NO if it is permitted for
 *     the default coregion to be nil or unknown ('ZZ').
 * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
 *     with the parsed number.
 * @throws {i18n.phonenumbers.Error}
 * @private
 */
- (NBPhoneNumber *)parseHelper:(NSString *)numberToParse
                 defaultRegion:(NSString *)defaultRegion
                  keepRawInput:(BOOL)keepRawInput
                   checkRegion:(BOOL)checkRegion
                         error:(NSError **)error {
  numberToParse = NormalizeNonBreakingSpace(numberToParse);

  if (numberToParse == nil) {
    if (error != NULL) {
      (*error) = [self errorWithObject:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", numberToParse]
                            withDomain:@"NOT_A_NUMBER"];
    }

    return nil;
  } else if (numberToParse.length > MAX_INPUT_STRING_LENGTH_) {
    if (error != NULL) {
      (*error) = [self errorWithObject:[NSString stringWithFormat:@"TOO_LONG:%@", numberToParse]
                            withDomain:@"TOO_LONG"];
    }

    return nil;
  }

  NSString *nationalNumber = @"";
  [self buildNationalNumberForParsing:numberToParse nationalNumber:&nationalNumber];

  if ([self isViablePhoneNumber:nationalNumber] == NO) {
    if (error != NULL) {
      (*error) =
          [self errorWithObject:[NSString stringWithFormat:@"NOT_A_NUMBER:%@", nationalNumber]
                     withDomain:@"NOT_A_NUMBER"];
    }

    return nil;
  }

  // Check the region supplied is valid, or that the extracted number starts
  // with some sort of + sign so the number's region can be determined.
  if (checkRegion &&
      [self checkRegionForParsing:nationalNumber defaultRegion:defaultRegion] == NO) {
    if (error != NULL) {
      (*error) = [self
          errorWithObject:[NSString stringWithFormat:@"INVALID_COUNTRY_CODE:%@", defaultRegion]
               withDomain:@"INVALID_COUNTRY_CODE"];
    }

    return nil;
  }

  NBPhoneNumber *phoneNumber = [[NBPhoneNumber alloc] init];
  if (keepRawInput) {
    phoneNumber.rawInput = [numberToParse copy];
  }

  // Attempt to parse extension first, since it doesn't require region-specific
  // data and we want to have the non-normalised number here.
  NSString *extension = [self maybeStripExtension:&nationalNumber];
  if (extension.length > 0) {
    phoneNumber.extension = [extension copy];
  }
  NBPhoneMetaData *regionMetadata = [self.helper getMetadataForRegion:defaultRegion];
  // Check to see if the number is given in international format so we know
  // whether this number is from the default region or not.
  NSString *normalizedNationalNumber = @"";
  NSNumber *countryCode = nil;
  NSString *nationalNumberStr = [nationalNumber copy];

  {
    NSError *anError = nil;
    countryCode = [self maybeExtractCountryCode:nationalNumberStr
                                       metadata:regionMetadata
                                 nationalNumber:&normalizedNationalNumber
                                   keepRawInput:keepRawInput
                                    phoneNumber:&phoneNumber
                                          error:&anError];

    if (anError != nil) {
      if ([anError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] &&
          [self stringPositionByRegex:nationalNumberStr regex:LEADING_PLUS_CHARS_PATTERN] >= 0) {
        // Strip the plus-char, and try again.
        NSError *aNestedError = nil;
        nationalNumberStr = [self replaceStringByRegex:nationalNumberStr
                                                 regex:LEADING_PLUS_CHARS_PATTERN
                                          withTemplate:@""];
        countryCode = [self maybeExtractCountryCode:nationalNumberStr
                                           metadata:regionMetadata
                                     nationalNumber:&normalizedNationalNumber
                                       keepRawInput:keepRawInput
                                        phoneNumber:&phoneNumber
                                              error:&aNestedError];
        if ([countryCode isEqualToNumber:@0]) {
          if (error != NULL)
            (*error) = [self errorWithObject:anError.description withDomain:anError.domain];

          return nil;
        }
      } else {
        if (error != NULL)
          (*error) = [self errorWithObject:anError.description withDomain:anError.domain];

        return nil;
      }
    }
  }

  if (![countryCode isEqualToNumber:@0]) {
    NSString *phoneNumberRegion = [self getRegionCodeForCountryCode:countryCode];
    if (phoneNumberRegion != defaultRegion) {
      // Metadata cannot be nil because the country calling code is valid.
      regionMetadata =
          [self getMetadataForRegionOrCallingCode:countryCode regionCode:phoneNumberRegion];
    }
  } else {
    // If no extracted country calling code, use the region supplied instead.
    // The national number is just the normalized version of the number we were
    // given to parse.
    [self normalizeSB:&nationalNumber];
    normalizedNationalNumber = [normalizedNationalNumber stringByAppendingString:nationalNumber];

    if (defaultRegion != nil) {
      countryCode = regionMetadata.countryCode;
      phoneNumber.countryCode = countryCode;
    } else if (keepRawInput) {
      [phoneNumber clearCountryCodeSource];
    }
  }

  if (normalizedNationalNumber.length < MIN_LENGTH_FOR_NSN_) {
    if (error != NULL) {
      (*error) = [self
          errorWithObject:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
               withDomain:@"TOO_SHORT_NSN"];
    }

    return nil;
  }

  if (regionMetadata != nil) {
    NSString *carrierCode = @"";
    NSString *potentialNationalNumber = [normalizedNationalNumber copy];
    [self maybeStripNationalPrefixAndCarrierCode:&potentialNationalNumber
                                        metadata:regionMetadata
                                     carrierCode:&carrierCode];
    NBEValidationResult validationResult =
        [self validateNumberLength:potentialNationalNumber metadata:regionMetadata];
    if (validationResult != NBEValidationResultTOO_SHORT &&
        validationResult != NBEValidationResultIS_POSSIBLE_LOCAL_ONLY &&
        validationResult != NBEValidationResultINVALID_LENGTH) {
      normalizedNationalNumber = potentialNationalNumber;
      if (keepRawInput) {
        phoneNumber.preferredDomesticCarrierCode = [carrierCode copy];
      }
    }
  }

  NSString *normalizedNationalNumberStr = [normalizedNationalNumber copy];

  NSUInteger lengthOfNationalNumber = normalizedNationalNumberStr.length;
  if (lengthOfNationalNumber < MIN_LENGTH_FOR_NSN_) {
    if (error != NULL) {
      (*error) = [self
          errorWithObject:[NSString stringWithFormat:@"TOO_SHORT_NSN:%@", normalizedNationalNumber]
               withDomain:@"TOO_SHORT_NSN"];
    }

    return nil;
  }

  if (lengthOfNationalNumber > MAX_LENGTH_FOR_NSN_) {
    if (error != NULL) {
      (*error) =
          [self errorWithObject:[NSString stringWithFormat:@"TOO_LONG:%@", normalizedNationalNumber]
                     withDomain:@"TOO_LONG"];
    }

    return nil;
  }

  [self setItalianLeadingZerosForPhoneNumber:normalizedNationalNumberStr phoneNumber:phoneNumber];

  phoneNumber.nationalNumber =
      [NSNumber numberWithLongLong:[normalizedNationalNumberStr longLongValue]];
  return phoneNumber;
}

/**
 * Converts numberToParse to a form that we can parse and write it to
 * nationalNumber if it is written in RFC3966; otherwise extract a possible
 * number out of it and write to nationalNumber.
 *
 * - param {?string} numberToParse number that we are attempting to parse. This
 *     can contain formatting such as +, ( and -, as well as a phone number
 *     extension.
 * - param {!goog.string.StringBuffer} nationalNumber a string buffer for storing
 *     the national significant number.
 * @private
 */
- (void)buildNationalNumberForParsing:(NSString *)numberToParse
                       nationalNumber:(NSString **)nationalNumber {
  if (nationalNumber == NULL) return;

  NSMutableString *result = [[NSMutableString alloc] init];

  int indexOfPhoneContext = [self indexOfStringByString:numberToParse target:RFC3966_PHONE_CONTEXT];
  if (indexOfPhoneContext > 0) {
    NSUInteger phoneContextStart = indexOfPhoneContext + RFC3966_PHONE_CONTEXT.length;
    // If the phone context contains a phone number prefix, we need to capture
    // it, whereas domains will be ignored.
    if ([numberToParse characterAtIndex:phoneContextStart] == '+') {
      // Additional parameters might follow the phone context. If so, we will
      // remove them here because the parameters after phone context are not
      // important for parsing the phone number.
      NSRange foundRange = [numberToParse
          rangeOfString:@";"
                options:NSLiteralSearch
                  range:NSMakeRange(phoneContextStart, numberToParse.length - phoneContextStart)];
      if (foundRange.location != NSNotFound) {
        NSRange subRange = NSMakeRange(phoneContextStart, foundRange.location - phoneContextStart);
        [result appendString:[numberToParse substringWithRange:subRange]];
      } else {
        [result appendString:[numberToParse substringFromIndex:phoneContextStart]];
      }
    }

    // Now append everything between the "tel:" prefix and the phone-context.
    // This should include the national number, an optional extension or
    // isdn-subaddress component.
    NSUInteger rfc3966Start =
        [self indexOfStringByString:numberToParse target:RFC3966_PREFIX] + RFC3966_PREFIX.length;
    NSString *subString = [numberToParse
        substringWithRange:NSMakeRange(rfc3966Start, indexOfPhoneContext - rfc3966Start)];
    [result appendString:subString];
  } else {
    // Extract a possible number from the string passed in (this strips leading
    // characters that could not be the start of a phone number.)
    [result appendString:[self extractPossibleNumber:numberToParse]];
  }

  // Delete the isdn-subaddress and everything after it if it is present.
  // Note extension won't appear at the same time with isdn-subaddress
  // according to paragraph 5.3 of the RFC3966 spec,
  NSString *nationalNumberStr = [result copy];
  int indexOfIsdn = [self indexOfStringByString:nationalNumberStr target:RFC3966_ISDN_SUBADDRESS];
  if (indexOfIsdn > 0) {
    NSRange range = NSMakeRange(0, indexOfIsdn);
    result = [[NSMutableString alloc] initWithString:[nationalNumberStr substringWithRange:range]];
  }
  // If both phone context and isdn-subaddress are absent but other
  // parameters are present, the parameters are left in nationalNumber. This
  // is because we are concerned about deleting content from a potential
  // number string when there is no strong evidence that the number is
  // actually written in RFC3966.

  *nationalNumber = [result copy];
}

/**
 * Takes two phone numbers and compares them for equality.
 *
 * <p>Returns EXACT_MATCH if the country_code, NSN, presence of a leading zero
 * for Italian numbers and any extension present are the same. Returns NSN_MATCH
 * if either or both has no region specified, and the NSNs and extensions are
 * the same. Returns SHORT_NSN_MATCH if either or both has no region specified,
 * or the region specified is the same, and one NSN could be a shorter version
 * of the other number. This includes the case where one has an extension
 * specified, and the other does not. Returns NO_MATCH otherwise. For example,
 * the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
 * +1 345 657 1234 and 345 657 are a NO_MATCH.
 *
 * - param {i18n.phonenumbers.PhoneNumber|string} firstNumberIn first number to
 *     compare. If it is a string it can contain formatting, and can have
 *     country calling code specified with + at the start.
 * - param {i18n.phonenumbers.PhoneNumber|string} secondNumberIn second number to
 *     compare. If it is a string it can contain formatting, and can have
 *     country calling code specified with + at the start.
 * @return {MatchType} NOT_A_NUMBER, NO_MATCH,
 *     SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of
 *     equality of the two numbers, described in the method definition.
 */
- (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn error:(NSError **)error {
  NBEMatchType res = 0;
  @try {
    res = [self isNumberMatch:firstNumberIn second:secondNumberIn];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (NBEMatchType)isNumberMatch:(id)firstNumberIn second:(id)secondNumberIn {
  // If the input arguements are strings parse them to a proto buffer format.
  // Else make copies of the phone numbers so that the numbers passed in are not
  // edited.
  /** @type {i18n.phonenumbers.PhoneNumber} */
  NBPhoneNumber *firstNumber = nil, *secondNumber = nil;

  if ([firstNumberIn isKindOfClass:[NSString class]]) {
    // First see if the first number has an implicit country calling code, by
    // attempting to parse it.
    NSError *anError;
    firstNumber = [self parse:firstNumberIn defaultRegion:NB_UNKNOWN_REGION error:&anError];

    if (anError != nil) {
      if ([anError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) {
        return NBEMatchTypeNOT_A_NUMBER;
      }
      // The first number has no country calling code. EXACT_MATCH is no longer
      // possible. We parse it as if the region was the same as that for the
      // second number, and if EXACT_MATCH is returned, we replace this with
      // NSN_MATCH.
      if ([secondNumberIn isKindOfClass:[NSString class]] == NO) {
        NSString *secondNumberRegion =
            [self getRegionCodeForCountryCode:((NBPhoneNumber *)secondNumberIn).countryCode];
        if (secondNumberRegion != NB_UNKNOWN_REGION) {
          NSError *aNestedError;
          firstNumber =
              [self parse:firstNumberIn defaultRegion:secondNumberRegion error:&aNestedError];

          if (aNestedError != nil) {
            return NBEMatchTypeNOT_A_NUMBER;
          }

          NBEMatchType match = [self isNumberMatch:firstNumber second:secondNumberIn];
          if (match == NBEMatchTypeEXACT_MATCH) {
            return NBEMatchTypeNSN_MATCH;
          }
          return match;
        }
      }
      // If the second number is a string or doesn't have a valid country
      // calling code, we parse the first number without country calling code.
      NSError *aNestedError;
      firstNumber = [self parseHelper:firstNumberIn
                        defaultRegion:nil
                         keepRawInput:NO
                          checkRegion:NO
                                error:&aNestedError];
      if (aNestedError != nil) {
        return NBEMatchTypeNOT_A_NUMBER;
      }
    }
  } else {
    firstNumber = [firstNumberIn copy];
  }

  if ([secondNumberIn isKindOfClass:[NSString class]]) {
    NSError *parseError;
    secondNumber = [self parse:secondNumberIn defaultRegion:NB_UNKNOWN_REGION error:&parseError];
    if (parseError != nil) {
      if ([parseError.domain isEqualToString:@"INVALID_COUNTRY_CODE"] == NO) {
        return NBEMatchTypeNOT_A_NUMBER;
      }
      return [self isNumberMatch:secondNumberIn second:firstNumber];
    } else {
      return [self isNumberMatch:firstNumberIn second:secondNumber];
    }
  } else {
    secondNumber = [secondNumberIn copy];
  }

  // First clear raw_input, country_code_source and
  // preferred_domestic_carrier_code fields and any empty-string extensions so
  // that we can use the proto-buffer equality method.
  firstNumber.rawInput = @"";
  [firstNumber clearCountryCodeSource];
  firstNumber.preferredDomesticCarrierCode = @"";

  secondNumber.rawInput = @"";
  [secondNumber clearCountryCodeSource];
  secondNumber.preferredDomesticCarrierCode = @"";

  if (firstNumber.extension != nil && firstNumber.extension.length == 0) {
    firstNumber.extension = nil;
  }

  if (secondNumber.extension != nil && secondNumber.extension.length == 0) {
    secondNumber.extension = nil;
  }

  // Early exit if both had extensions and these are different.
  if ([NBMetadataHelper hasValue:firstNumber.extension] &&
      [NBMetadataHelper hasValue:secondNumber.extension] &&
      [firstNumber.extension isEqualToString:secondNumber.extension] == NO) {
    return NBEMatchTypeNO_MATCH;
  }

  NSNumber *firstNumberCountryCode = firstNumber.countryCode;
  NSNumber *secondNumberCountryCode = secondNumber.countryCode;

  // Both had country_code specified.
  if (![firstNumberCountryCode isEqualToNumber:@0] &&
      ![secondNumberCountryCode isEqualToNumber:@0]) {
    if ([firstNumber isEqual:secondNumber]) {
      return NBEMatchTypeEXACT_MATCH;
    } else if ([firstNumberCountryCode isEqualToNumber:secondNumberCountryCode] &&
               [self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber]) {
      // A SHORT_NSN_MATCH occurs if there is a difference because of the
      // presence or absence of an 'Italian leading zero', the presence or
      // absence of an extension, or one NSN being a shorter variant of the
      // other.
      return NBEMatchTypeSHORT_NSN_MATCH;
    }
    // This is not a match.
    return NBEMatchTypeNO_MATCH;
  }
  // Checks cases where one or both country_code fields were not specified. To
  // make equality checks easier, we first set the country_code fields to be
  // equal.
  firstNumber.countryCode = @0;
  secondNumber.countryCode = @0;
  // If all else was the same, then this is an NSN_MATCH.
  if ([firstNumber isEqual:secondNumber]) {
    return NBEMatchTypeNSN_MATCH;
  }

  if ([self isNationalNumberSuffixOfTheOther:firstNumber second:secondNumber]) {
    return NBEMatchTypeSHORT_NSN_MATCH;
  }
  return NBEMatchTypeNO_MATCH;
}

/**
 * Returns NO when one national number is the suffix of the other or both are
 * the same.
 *
 * - param {i18n.phonenumbers.PhoneNumber} firstNumber the first PhoneNumber
 *     object.
 * - param {i18n.phonenumbers.PhoneNumber} secondNumber the second PhoneNumber
 *     object.
 * @return {boolean} NO if one PhoneNumber is the suffix of the other one.
 * @private
 */
- (BOOL)isNationalNumberSuffixOfTheOther:(NBPhoneNumber *)firstNumber
                                  second:(NBPhoneNumber *)secondNumber {
  NSString *firstNumberNationalNumber =
      [NSString stringWithFormat:@"%@", firstNumber.nationalNumber];
  NSString *secondNumberNationalNumber =
      [NSString stringWithFormat:@"%@", secondNumber.nationalNumber];

  // Note that endsWith returns NO if the numbers are equal.
  return [firstNumberNationalNumber hasSuffix:secondNumberNationalNumber] ||
         [secondNumberNationalNumber hasSuffix:firstNumberNationalNumber];
}

/**
 * Returns NO if the number can be dialled from outside the region, or
 * unknown. If the number can only be dialled from within the region, returns
 * NO. Does not check the number is a valid number.
 * TODO: Make this method public when we have enough metadata to make it
 * worthwhile. Currently visible for testing purposes only.
 *
 * - param {i18n.phonenumbers.PhoneNumber} number the phone-number for which we
 *     want to know whether it is diallable from outside the region.
 * @return {boolean} NO if the number can only be dialled from within the
 *     country.
 */
- (BOOL)canBeInternationallyDialled:(NBPhoneNumber *)number error:(NSError **)error {
  BOOL res = NO;
  @try {
    res = [self canBeInternationallyDialled:number];
  } @catch (NSException *exception) {
    NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:exception.reason forKey:NSLocalizedDescriptionKey];
    if (error != NULL) (*error) = [NSError errorWithDomain:exception.name code:0 userInfo:userInfo];
  }
  return res;
}

- (BOOL)canBeInternationallyDialled:(NBPhoneNumber *)number {
  NBPhoneMetaData *metadata =
      [self.helper getMetadataForRegion:[self getRegionCodeForNumber:number]];
  if (metadata == nil) {
    // Note numbers belonging to non-geographical entities (e.g. +800 numbers)
    // are always internationally diallable, and will be caught here.
    return YES;
  }
  NSString *nationalSignificantNumber = [self getNationalSignificantNumber:number];
  return [self isNumberMatchingDesc:nationalSignificantNumber
                         numberDesc:metadata.noInternationalDialling] == NO;
}

/**
 * Check whether the entire input sequence can be matched against the regular
 * expression.
 *
 * - param {!RegExp|string} regex the regular expression to match against.
 * - param {string} str the string to test.
 * @return {boolean} NO if str can be matched entirely against regex.
 * @private
 */
- (BOOL)matchesEntirely:(NSString *)regex string:(NSString *)str {
  if ([regex isEqualToString:@"NA"]) {
    return NO;
  }

  NSError *error = nil;
  NSRegularExpression *currentPattern =
      [self entireRegularExpressionWithPattern:regex options:0 error:&error];
  NSRange stringRange = NSMakeRange(0, str.length);
  NSTextCheckingResult *matchResult =
      [currentPattern firstMatchInString:str options:NSMatchingAnchored range:stringRange];

  if (matchResult != nil) {
    BOOL matchIsEntireString = NSEqualRanges(matchResult.range, stringRange);
    if (matchIsEntireString) {
      return YES;
    }
  }

  return NO;
}

@end
