docs: def parse_name(parts, main_names, common_names, debug=0):
    """ 
      Parse all name(s) from a Backpage ad.
        parts -> The backpage ad's posting_body, separated into substrings
        main_names -> "Regular" names (jessica, gabriel, etc.) that can be trusted as names simply by its existence
        common_names -> Names such as "pleasure" or "sexy" that should only be parsed if surrounded by an "intro"
      main_names and common_names can both be either sets or dictionaries. Current version uses dictionaries.
    """
  
    lowercase_parts = [re.sub(r'(in|out)call', '', p.lower()) for p in parts]
  
    start = time.time()
  
    # Intros to common names
    intros = {
      'pre': ['my name is', 'i am', 'call me', 'call', 'text', 'my names', 'my name', 'known as', 'go by', 'Intro',
              'ask for', 'call for', 'ask', 'this is', 'one and only', 'prevphonespothti', 'called'],
      'post': ['is back', 'is here', 'in town', 'prevphonespothti', 'is ready', 'is available']
    }
    spanish_intros = ['hola soy', 'me llamo', 'mi nombre es', 'yo soy', 'pregunta por', 'encantada de conocerlo', 'hola papi']
    intros['pre'].extend(spanish_intros)
  
    # Regex intros to common names
    rgx_intros = {
      'pre': [r'\b(?:it\'s)\b', r'\b(?:it s)\b', r'\b(?:its)\b', r'\b(?:soy)\b', r'\b(?:es)\b', r'\b(?:hola)\b', r'\b(?:y?o?ur girl)\b', r'\b(?:i\'m)\b',
              r'\b(?:im)\b', r'\b(?:y?o?ur favorite girl)\b', r'\b(?:y?o?ur most favorite girl)\b', r'\bmy ?fr(?:i|e)(?:i|e)nd\b', r'\bm(?:s|z)\.',
              r'\bmi(?:s{1,2}|z{1,2})'],
      'post': [r'\b(?:here)\b', r'\b(?:(?:i|\')s (?:the|my) name)\b']
    }
  
    # These words shouldn't follow common name matched from an intro
    false_positives = set(['complexion', 'skin', 'hair', 'locks', 'eyes', 'st', 'ave', 'street', 'avenue', 'blvd', 'boulevard',
                           'highway', 'circle', 'hwy', 'road', 'rd'])
    vowels_with_y = set(list('aeiouy'))
  
    uniques = set([])
  
    for p in lowercase_parts:
      part = p.lower()
      part = re.sub(r"(^| )i ?'? ?m ", " Intro ", part).strip()
      part = part.replace('<br>', ' ').replace('&amp;', ' and ')
      part = re.sub(r'\.+', ' ', part)
      part = re.sub(r'x+', 'x', part)
      part = re.sub(r'y+', 'y', part)
      # Retain 'part' to be used for separating comma-separated names
      part = re.sub(r',+', ' ', part)
      part = re.sub(r' +', ' ', part)
  
      builder = []
      for pt in part.split():
        if len(pt) > 2:
          lastc = pt[len(pt)-1]
          # Convert names that have repeated last letters and the last letters aren't "E" and aren't two consonants following a vowel
          if lastc == pt[len(pt)-2] and not (lastc == 'e' or (pt[len(pt)-3] in vowels_with_y and lastc not in vowels_with_y)):
            builder.append(pt[:len(pt)-1])
          else:
            builder.append(pt)
        else:
          builder.append(pt)
      part = ' '.join(builder)
  
      # Check if the part is entirely just a common word
      ageless_title = re.sub(r' - \d\d', '', part.lower())
      ageless_title = re.sub(r'\W+', '', ageless_title)
      if ageless_title in common_names or ageless_title in main_names:
        uniques.add(ageless_title)
        continue;
  
      # Find common names that come immediately before or after a "both-side intro"
      for k in intros:
        for intro in intros[k]:
          if intro in part:
            pts = part.split(intro)
            for i in range(1, len(pts)):
              if k == 'post':
                # Check left side of intro
                ptl = re.sub(r'\W', ' ', pts[i-1])
                ptl = re.sub(r' +', ' ', ptl)
                tokenized = ptl.split()
                if tokenized and tokenized[len(tokenized)-1] and tokenized[len(tokenized)-1] in common_names:
                  uniques.add(tokenized[len(tokenized)-1])
                  break
              else:
                # Check right side of intro
                ptr = re.sub(r'\W', ' ', pts[i])
                ptr = re.sub(r' +', ' ', ptr)
                tokenized = ptr.split()
                if tokenized and tokenized[0] in common_names:
                  if not (len(tokenized) > 1 and tokenized[1] in false_positives or (len(tokenized) > 2 and tokenized[2] in false_positives)):
                    # Next 2 words are not false positives
                    uniques.add(tokenized[0])
                    break
  
      # Check intros that include regexes
      for k in rgx_intros:
        for intro in rgx_intros[k]:
          matches = list(re.findall(intro, part))
          for match in matches:
            pts = part.split(match)
            for i in range(1, len(pts)):
              if k == 'post':
                # Check left side of intro
                ptl = re.sub(r'\W', ' ', pts[i-1])
                ptl = re.sub(r' +', ' ', ptl)
                tokenized = ptl.split()
                if tokenized and tokenized[len(tokenized)-1] and tokenized[len(tokenized)-1] in common_names:
                  uniques.add(tokenized[len(tokenized)-1])
                  break
              else:
                # Check right side of intro
                ptr = re.sub(r'\W', ' ', pts[i])
                ptr = re.sub(r' +', ' ', ptr)
                tokenized = ptr.split()
                if tokenized and tokenized[0] in common_names:
                  if not (len(tokenized) > 1 and tokenized[1] in false_positives or (len(tokenized) > 2 and tokenized[2] in false_positives)):
                    # Next 2 words are not false positives
                    uniques.add(tokenized[0])
                    break
  
      # Find regular names
      tokens = list(re.split(r'\W+', part))
      for i in range(len(tokens)):
        if not tokens[i]:
          continue
        curr = tokens[i]
        # Check if current token has an 's' at the end (ex: "brittanys beautiful body")
        if curr not in main_names and curr[len(curr)-1] == 's' and curr[:-1] in main_names:
          curr = curr[:-1]
        if curr in main_names:
          # Check if name is a two-part name
          if i > 0 and (''.join([tokens[i-1], curr]) in main_names or ''.join([tokens[i-1], curr]) in common_names):
            # Prev token was a prefix to current
            uniques.add(' '.join([tokens[i-1], curr]))
            uniques.discard(tokens[i-1])
          elif (i < len(tokens)-1 and tokens[i+1] and (''.join([tokens[i], tokens[i+1]]) in main_names or
                ''.join([tokens[i], tokens[i+1]]) in common_names)):
            # Current token has a suffix
            uniques.add(' '.join([tokens[i], tokens[i+1]]))
          elif (i < len(tokens)-1 and tokens[i+1] and tokens[i+1][len(tokens[i+1])-1] == 's' and (''.join([tokens[i], tokens[i+1][:-1]]) in main_names or
                ''.join([tokens[i], tokens[i+1][:-1]]) in common_names)):
            # Current token has a suffix with plural ending ('s')
            uniques.add(' '.join([tokens[i], tokens[i+1][:-1]]))
          else:
            # Only single-word name
            uniques.add(curr)
  
      # Find common words that are part of "pairing" phrases, paired with names that we found already
      pairings = set(['and', 'plus', 'with'])
      for i in range(len(tokens)):
        if tokens[i] not in uniques and tokens[i] in common_names:
          if i > 1 and tokens[i-2] in uniques and tokens[i-1] in pairings:
            # ex: "jessica and diamond"
            uniques.add(tokens[i])
          elif i < len(tokens)-2 and tokens[i+2] in uniques and tokens[i+1] in pairings:
            # ex: "diamond and jessica"
            uniques.add(tokens[i])
  
    # Odd cases
    if ('mary' in uniques or 'jane' in uniques or 'mary jane' in uniques) and re.search(r'mary\W+jane', part):
      uniques.discard('jane')
      uniques.discard('mary')
      uniques.discard('mary jane')
    if 'crystal' in uniques and re.search(r'crystal ?(blue|spa|massage|parlor|city|stone)', part):
      uniques.discard('crystal')
        
    # Remove names that are substrings of larger names
    names_final = set([])
    if isinstance(main_names, set):
      # Name datasets are raw sets of names
      for match in uniques:
        if not any(match in name for name in [v for v in uniques if v != match]) and match:
          names_final.add(match.strip())
    else:
      # Name datasets are misspelled names mapped to properly spelled names
      for match in uniques:
        nosp_match = match.replace(' ', '')
        if not any(nosp_match in name for name in [v for v in uniques if v != nosp_match]) and nosp_match:
          # add the parsed name, not the converted one (ex: don't change "mickey" to "mikey")
          names_final.add(nosp_match)
  
    if debug == 1:
      print 'parse_name time taken: {} seconds'.format(time.time() - start)
    return list(names_final)