# coding= utf-8

# This script implements the Double Metaphone algorythm (c) 1998, 1999 by
# Lawrence Philips. It was translated to Python from the C source written by
# Kevin Atkinson (http://aspell.net/metaphone/) By Andrew Collins - January 12,
# 2007 who claims no rights to this work.
# http://atomboy.isa-geek.com:8080/plone/Members/acoil/programing/double-metaphone

import re


vowels = frozenset("AEIOUY")
slavo_germ_exp = re.compile("W|K|CZ|WITZ")
silent_starts = re.compile("GN|KN|PN|WR|PS")


def double_metaphone(text):
    text = text.upper()
    slavo_germanic = bool(slavo_germ_exp.search(text))

    length = len(text)
    text = "--" + text + "     "
    first = pos = 2
    last = first + length - 1
    primary = secondary = ""

    if silent_starts.match(text, pos):
        pos += 1

    while pos < length + 2:
        ch = text[pos]

        if ch in vowels:
            # all init vowels now map to 'A'
            if pos != first:
                next = (None, 1)
            else:
                next = ("A", 1)
        elif ch == "B":
            #"-mb", e.g", "dumb", already skipped over... see 'M' below
            if text[pos + 1] == "B":
                next = ("P", 2)
            else:
                next = ("P", 1)
        elif ch == "C":
            # various germanic
            if (pos > (first + 1) and text[pos - 2] not in vowels and text[pos - 1:pos + 2] == 'ACH' and \
               (text[pos + 2] not in ['I', 'E'] or text[pos - 2:pos + 4] in ['BACHER', 'MACHER'])):
                next = ('K', 2)
            # special case 'CAESAR'
            elif pos == first and text[first:first + 6] == 'CAESAR':
                next = ('S', 2)
            elif text[pos:pos + 4] == 'CHIA':  # italian 'chianti'
                next = ('K', 2)
            elif text[pos:pos + 2] == 'CH':
                # find 'michael'
                if pos > first and text[pos:pos + 4] == 'CHAE':
                    next = ('K', 'X', 2)
                elif pos == first and (text[pos + 1:pos + 6] in ['HARAC', 'HARIS'] or \
                   text[pos + 1:pos + 4] in ["HOR", "HYM", "HIA", "HEM"]) and text[first:first + 5] != 'CHORE':
                    next = ('K', 2)
                # germanic, greek, or otherwise 'ch' for 'kh' sound
                elif text[first:first + 4] in ['VAN ', 'VON '] or text[first:first + 3] == 'SCH' \
                   or text[pos - 2:pos + 4] in ["ORCHES", "ARCHIT", "ORCHID"] \
                   or text[pos + 2] in ['T', 'S'] \
                   or ((text[pos - 1] in ["A", "O", "U", "E"] or pos == first) \
                   and text[pos + 2] in ["L", "R", "N", "M", "B", "H", "F", "V", "W", " "]):
                    next = ('K', 1)
                else:
                    if pos > first:
                        if text[first:first + 2] == 'MC':
                            next = ('K', 2)
                        else:
                            next = ('X', 'K', 2)
                    else:
                        next = ('X', 2)
            # e.g, 'czerny'
            elif text[pos:pos + 2] == 'CZ' and text[pos - 2:pos + 2] != 'WICZ':
                next = ('S', 'X', 2)
            # e.g., 'focaccia'
            elif text[pos + 1:pos + 4] == 'CIA':
                next = ('X', 3)
            # double 'C', but not if e.g. 'McClellan'
            elif text[pos:pos + 2] == 'CC' and not (pos == (first + 1) and text[first] == 'M'):
                # 'bellocchio' but not 'bacchus'
                if text[pos + 2] in ["I", "E", "H"] and text[pos + 2:pos + 4] != 'HU':
                    # 'accident', 'accede' 'succeed'
                    if (pos == (first + 1) and text[first] == 'A') or \
                       text[pos - 1:pos + 4] in ['UCCEE', 'UCCES']:
                        next = ('KS', 3)
                    # 'bacci', 'bertucci', other italian
                    else:
                        next = ('X', 3)
                else:
                    next = ('K', 2)
            elif text[pos:pos + 2] in ["CK", "CG", "CQ"]:
                next = ('K', 'K', 2)
            elif text[pos:pos + 2] in ["CI", "CE", "CY"]:
                # italian vs. english
                if text[pos:pos + 3] in ["CIO", "CIE", "CIA"]:
                    next = ('S', 'X', 2)
                else:
                    next = ('S', 2)
            else:
                # name sent in 'mac caffrey', 'mac gregor
                if text[pos + 1:pos + 3] in [" C", " Q", " G"]:
                    next = ('K', 3)
                else:
                    if text[pos + 1] in ["C", "K", "Q"] and text[pos + 1:pos + 3] not in ["CE", "CI"]:
                        next = ('K', 2)
                    else:  # default for 'C'
                        next = ('K', 1)
        elif ch == u'Ç':
            next = ('S', 1)
        elif ch == 'D':
            if text[pos:pos + 2] == 'DG':
                if text[pos + 2] in ['I', 'E', 'Y']:  # e.g. 'edge'
                    next = ('J', 3)
                else:
                    next = ('TK', 2)
            elif text[pos:pos + 2] in ['DT', 'DD']:
                next = ('T', 2)
            else:
                next = ('T', 1)
        elif ch == 'F':
            if text[pos + 1] == 'F':
                next = ('F', 2)
            else:
                next = ('F', 1)
        elif ch == 'G':
            if text[pos + 1] == 'H':
                if pos > first and text[pos - 1] not in vowels:
                    next = ('K', 2)
                elif pos < (first + 3):
                    if pos == first:  # 'ghislane', ghiradelli
                        if text[pos + 2] == 'I':
                            next = ('J', 2)
                        else:
                            next = ('K', 2)
                # Parker's rule (with some further refinements) - e.g., 'hugh'
                elif (pos > (first + 1) and text[pos - 2] in ['B', 'H', 'D']) \
                   or (pos > (first + 2) and text[pos - 3] in ['B', 'H', 'D']) \
                   or (pos > (first + 3) and text[pos - 4] in ['B', 'H']):
                    next = (None, 2)
                else:
                    # e.g., 'laugh', 'McLaughlin', 'cough', 'gough', 'rough', 'tough'
                    if pos > (first + 2) and text[pos - 1] == 'U' \
                       and text[pos - 3] in ["C", "G", "L", "R", "T"]:
                        next = ('F', 2)
                    else:
                        if pos > first and text[pos - 1] != 'I':
                            next = ('K', 2)
            elif text[pos + 1] == 'N':
                if pos == (first + 1) and text[first] in vowels and not slavo_germanic:
                    next = ('KN', 'N', 2)
                else:
                    # not e.g. 'cagney'
                    if text[pos + 2:pos + 4] != 'EY' and text[pos + 1] != 'Y' and not slavo_germanic:
                        next = ('N', 'KN', 2)
                    else:
                        next = ('KN', 2)
            # 'tagliaro'
            elif text[pos + 1:pos + 3] == 'LI' and not slavo_germanic:
                next = ('KL', 'L', 2)
            # -ges-,-gep-,-gel-, -gie- at beginning
            elif pos == first and (text[pos + 1] == 'Y' \
               or text[pos + 1:pos + 3] in ["ES", "EP", "EB", "EL", "EY", "IB", "IL", "IN", "IE", "EI", "ER"]):
                next = ('K', 'J', 2)
            # -ger-,  -gy-
            elif (text[pos + 1:pos + 2] == 'ER' or text[pos + 1] == 'Y') \
               and text[first:first + 6] not in ["DANGER", "RANGER", "MANGER"] \
               and text[pos - 1] not in ['E', 'I'] and text[pos - 1:pos + 2] not in ['RGY', 'OGY']:
                next = ('K', 'J', 2)
            # italian e.g, 'biaggi'
            elif text[pos + 1] in ['E', 'I', 'Y'] or text[pos - 1:pos + 3] in ["AGGI", "OGGI"]:
                # obvious germanic
                if text[first:first + 4] in ['VON ', 'VAN '] or text[first:first + 3] == 'SCH' \
                   or text[pos + 1:pos + 3] == 'ET':
                    next = ('K', 2)
                else:
                    # always soft if french ending
                    if text[pos + 1:pos + 5] == 'IER ':
                        next = ('J', 2)
                    else:
                        next = ('J', 'K', 2)
            elif text[pos + 1] == 'G':
                next = ('K', 2)
            else:
                next = ('K', 1)
        elif ch == 'H':
            # only keep if first & before vowel or btw. 2 vowels
            if (pos == first or text[pos - 1] in vowels) and text[pos + 1] in vowels:
                next = ('H', 2)
            else:  # (also takes care of 'HH')
                next = (None, 1)
        elif ch == 'J':
            # obvious spanish, 'jose', 'san jacinto'
            if text[pos:pos + 4] == 'JOSE' or text[first:first + 4] == 'SAN ':
                if (pos == first and text[pos + 4] == ' ') or text[first:first + 4] == 'SAN ':
                    next = ('H',)
                else:
                    next = ('J', 'H')
            elif pos == first and text[pos:pos + 4] != 'JOSE':
                next = ('J', 'A')  # Yankelovich/Jankelowicz
            else:
                # spanish pron. of e.g. 'bajador'
                if text[pos - 1] in vowels and not slavo_germanic \
                   and text[pos + 1] in ['A', 'O']:
                    next = ('J', 'H')
                else:
                    if pos == last:
                        next = ('J', ' ')
                    else:
                        if text[pos + 1] not in ["L", "T", "K", "S", "N", "M", "B", "Z"] \
                           and text[pos - 1] not in ["S", "K", "L"]:
                            next = ('J',)
                        else:
                            next = (None,)
            if text[pos + 1] == 'J':
                next = next + (2,)
            else:
                next = next + (1,)
        elif ch == 'K':
            if text[pos + 1] == 'K':
                next = ('K', 2)
            else:
                next = ('K', 1)
        elif ch == 'L':
            if text[pos + 1] == 'L':
                # spanish e.g. 'cabrillo', 'gallegos'
                if (pos == (last - 2) and text[pos - 1:pos + 3] in ["ILLO", "ILLA", "ALLE"]) \
                   or ((text[last - 1:last + 1] in ["AS", "OS"] or text[last] in ["A", "O"]) \
                   and text[pos - 1:pos + 3] == 'ALLE'):
                    next = ('L', '', 2)
                else:
                    next = ('L', 2)
            else:
                next = ('L', 1)
        elif ch == 'M':
            if text[pos + 1:pos + 4] == 'UMB' \
               and (pos + 1 == last or text[pos + 2:pos + 4] == 'ER') \
               or text[pos + 1] == 'M':
                next = ('M', 2)
            else:
                next = ('M', 1)
        elif ch == 'N':
            if text[pos + 1] == 'N':
                next = ('N', 2)
            else:
                next = ('N', 1)
        elif ch == u'Ñ':
            next = ('N', 1)
        elif ch == 'P':
            if text[pos + 1] == 'H':
                next = ('F', 2)
            elif text[pos + 1] in ['P', 'B']:  # also account for "campbell", "raspberry"
                next = ('P', 2)
            else:
                next = ('P', 1)
        elif ch == 'Q':
            if text[pos + 1] == 'Q':
                next = ('K', 2)
            else:
                next = ('K', 1)
        elif ch == 'R':
            # french e.g. 'rogier', but exclude 'hochmeier'
            if pos == last and not slavo_germanic \
               and text[pos - 2:pos] == 'IE' and text[pos - 4:pos - 2] not in ['ME', 'MA']:
                next = ('', 'R')
            else:
                next = ('R',)
            if text[pos + 1] == 'R':
                next = next + (2,)
            else:
                next = next + (1,)
        elif ch == 'S':
            # special cases 'island', 'isle', 'carlisle', 'carlysle'
            if text[pos - 1:pos + 2] in ['ISL', 'YSL']:
                next = (None, 1)
            # special case 'sugar-'
            elif pos == first and text[first:first + 5] == 'SUGAR':
                next = ('X', 'S', 1)
            elif text[pos:pos + 2] == 'SH':
                # germanic
                if text[pos + 1:pos + 5] in ["HEIM", "HOEK", "HOLM", "HOLZ"]:
                    next = ('S', 2)
                else:
                    next = ('X', 2)
            # italian & armenian
            elif text[pos:pos + 3] in ["SIO", "SIA"] or text[pos:pos + 4] == 'SIAN':
                if not slavo_germanic:
                    next = ('S', 'X', 3)
                else:
                    next = ('S', 3)
            # german & anglicisations, e.g. 'smith' match 'schmidt', 'snider' match 'schneider'
            # also, -sz- in slavic language altho in hungarian it is pronounced 's'
            elif (pos == first and text[pos + 1] in ["M", "N", "L", "W"]) or text[pos + 1] == 'Z':
                next = ('S', 'X')
                if text[pos + 1] == 'Z':
                    next = next + (2,)
                else:
                    next = next + (1,)
            elif text[pos:pos + 2] == 'SC':
                # Schlesinger's rule
                if text[pos + 2] == 'H':
                    # dutch origin, e.g. 'school', 'schooner'
                    if text[pos + 3:pos + 5] in ["OO", "ER", "EN", "UY", "ED", "EM"]:
                        # 'schermerhorn', 'schenker'
                        if text[pos + 3:pos + 5] in ['ER', 'EN']:
                            next = ('X', 'SK', 3)
                        else:
                            next = ('SK', 3)
                    else:
                        if pos == first and text[first + 3] not in vowels and text[first + 3] != 'W':
                            next = ('X', 'S', 3)
                        else:
                            next = ('X', 3)
                elif text[pos + 2] in ['I', 'E', 'Y']:
                    next = ('S', 3)
                else:
                    next = ('SK', 3)
            # french e.g. 'resnais', 'artois'
            elif pos == last and text[pos - 2:pos] in ['AI', 'OI']:
                next = ('', 'S', 1)
            else:
                next = ('S',)
                if text[pos + 1] in ['S', 'Z']:
                    next = next + (2,)
                else:
                    next = next + (1,)
        elif ch == 'T':
            if text[pos:pos + 4] == 'TION':
                next = ('X', 3)
            elif text[pos:pos + 3] in ['TIA', 'TCH']:
                next = ('X', 3)
            elif text[pos:pos + 2] == 'TH' or text[pos:pos + 3] == 'TTH':
                # special case 'thomas', 'thames' or germanic
                if text[pos + 2:pos + 4] in ['OM', 'AM'] or text[first:first + 4] in ['VON ', 'VAN '] \
                   or text[first:first + 3] == 'SCH':
                    next = ('T', 2)
                else:
                    next = ('0', 'T', 2)
            elif text[pos + 1] in ['T', 'D']:
                next = ('T', 2)
            else:
                next = ('T', 1)
        elif ch == 'V':
            if text[pos + 1] == 'V':
                next = ('F', 2)
            else:
                next = ('F', 1)
        elif ch == 'W':
            # can also be in middle of word
            if text[pos:pos + 2] == 'WR':
                next = ('R', 2)
            elif pos == first and (text[pos + 1] in vowels or text[pos:pos + 2] == 'WH'):
                # Wasserman should match Vasserman
                if text[pos + 1] in vowels:
                    next = ('A', 'F', 1)
                else:
                    next = ('A', 1)
            # Arnow should match Arnoff
            elif (pos == last and text[pos - 1] in vowels) \
               or text[pos - 1:pos + 5] in ["EWSKI", "EWSKY", "OWSKI", "OWSKY"] \
               or text[first:first + 3] == 'SCH':
                next = ('', 'F', 1)
            # polish e.g. 'filipowicz'
            elif text[pos:pos + 4] in ["WICZ", "WITZ"]:
                next = ('TS', 'FX', 4)
            else:  # default is to skip it
                next = (None, 1)
        elif ch == 'X':
            # french e.g. breaux
            next = (None,)
            if not(pos == last and (text[pos - 3:pos] in ["IAU", "EAU"] \
               or text[pos - 2:pos] in ['AU', 'OU'])):
                next = ('KS',)
            if text[pos + 1] in ['C', 'X']:
                next = next + (2,)
            else:
                next = next + (1,)
        elif ch == 'Z':
            # chinese pinyin e.g. 'zhao'
            if text[pos + 1] == 'H':
                next = ('J',)
            elif text[pos + 1:pos + 3] in ["ZO", "ZI", "ZA"] \
               or (slavo_germanic and pos > first and text[pos - 1] != 'T'):
                next = ('S', 'TS')
            else:
                next = ('S',)
            if text[pos + 1] == 'Z':
                next = next + (2,)
            else:
                next = next + (1,)
        else:
            next = (None, 1)

        if len(next) == 2:
            if next[0]:
                primary += next[0]
                secondary += next[0]
            pos += next[1]
        elif len(next) == 3:
            if next[0]:
                primary += next[0]
            if next[1]:
                secondary += next[1]
            pos += next[2]

    if primary == secondary:
        return (primary, None)
    else:
        return (primary, secondary)

if __name__ == '__main__':
    names = {'maurice': ('MRS', None), 'aubrey': ('APR', None), 'cambrillo': ('KMPRL', 'KMPR'),
        'heidi': ('HT', None), 'katherine': ('K0RN', 'KTRN'), 'Thumbail': ('0MPL', 'TMPL'),
        'catherine': ('K0RN', 'KTRN'), 'richard': ('RXRT', 'RKRT'), 'bob': ('PP', None),\
        'eric': ('ARK', None), 'geoff': ('JF', 'KF'), 'Through': ('0R', 'TR'), 'Schwein': ('XN', 'XFN'),
        'dave': ('TF', None), 'ray': ('R', None), 'steven': ('STFN', None), 'bryce': ('PRS', None),
        'randy': ('RNT', None), 'bryan': ('PRN', None), 'Rapelje': ('RPL', None),
        'brian': ('PRN', None), 'otto': ('AT', None), 'auto': ('AT', None), 'Dallas': ('TLS', None),
        'maisey': ('MS', None), 'zhang': ('JNK', None), 'Chile': ('XL', None),
        'Jose': ('HS', None), 'Arnow': ('ARN', 'ARNF'), 'solilijs': ('SLLS', None),
        'Parachute': ('PRKT', None), 'Nowhere': ('NR', None), 'Tux': ('TKS', None)}
    for name in names.keys():
        assert (double_metaphone(name) == names[name]), 'For "%s" function returned %s. Should be %s.' % (name, double_metaphone(name), names[name])
