_s2_options = {
    'a': ((['a', 't', 'i', 'o', 'n', 'a', 'l'], ['a', 't', 'e']),
          (['t', 'i', 'o', 'n', 'a', 'l'], ['t', 'i', 'o', 'n'])),
    'c': ((['e', 'n', 'c', 'i'], ['e', 'n', 'c', 'e']),
          (['a', 'n', 'c', 'i'], ['a', 'n', 'c', 'e']),),
    'e': ((['i', 'z', 'e', 'r'], ['i', 'z', 'e']),),
    'l': ((['b', 'l', 'i'], ['b', 'l', 'e']),
          (['a', 'l', 'l', 'i'], ['a', 'l']),
          (['e', 'n', 't', 'l', 'i'], ['e', 'n', 't']),
          (['e', 'l', 'i'], ['e']),
          (['o', 'u', 's', 'l', 'i'], ['o', 'u', 's']),),
    'o': ((['i', 'z', 'a', 't', 'i', 'o', 'n'], ['i', 'z', 'e']),
          (['a', 't', 'i', 'o', 'n'], ['a', 't', 'e']),
          (['a', 't', 'o', 'r'], ['a', 't', 'e']),),
    's': ((['a', 'l', 'i', 's', 'm'], ['a', 'l']),
          (['i', 'v', 'e', 'n', 'e', 's', 's'], ['i', 'v', 'e']),
          (['f', 'u', 'l', 'n', 'e', 's', 's'], ['f', 'u', 'l']),
          (['o', 'u', 's', 'n', 'e', 's', 's'], ['o', 'u', 's']),),
    't': ((['a', 'l', 'i', 't', 'i'], ['a', 'l']),
          (['i', 'v', 'i', 't', 'i'], ['i', 'v', 'e']),
          (['b', 'i', 'l', 'i', 't', 'i'], ['b', 'l', 'e']),),
    'g': ((['l', 'o', 'g', 'i'], ['l', 'o', 'g']),),
}


_s3_options = {
    'e': ((['i', 'c', 'a', 't', 'e'], ['i', 'c']),
          (['a', 't', 'i', 'v', 'e'], []),
          (['a', 'l', 'i', 'z', 'e'], ['a', 'l']),),
    'i': ((['i', 'c', 'i', 't', 'i'], ['i', 'c']),),
    'l': ((['i', 'c', 'a', 'l'], ['i', 'c']),
          (['f', 'u', 'l'], []),),
    's': ((['n', 'e', 's', 's'], []),),
}

_s4_endings = {
    'a': (['a', 'l'],),
    'c': (['a', 'n', 'c', 'e'], ['e', 'n', 'c', 'e']),
    'e': (['e', 'r'],),
    'i': (['i', 'c'],),
    'l': (['a', 'b', 'l', 'e'], ['i', 'b', 'l', 'e']),
    'n': (['a', 'n', 't'], ['e', 'm', 'e', 'n', 't'], ['m', 'e', 'n', 't'],
          ['e', 'n', 't']),
    # handle 'o' separately
    's': (['i', 's', 'm'],),
    't': (['a', 't', 'e'], ['i', 't', 'i']),
    'u': (['o', 'u', 's'],),
    'v': (['i', 'v', 'e'],),
    'z': (['i', 'z', 'e'],),
}


class Stemmer(object):
    def __init__(self, b):
        self.b = list(b)
        self.k = len(b)-1
        self.j = 0

    def cons(self, i):
        """ True iff b[i] is a consonant """
        if self.b[i] in 'aeiou':
            return False
        elif self.b[i] == 'y':
            return True if i == 0 else not self.cons(i-1)
        return True

    def m(self):
        n = i = 0
        while True:
            if i > self.j:
                return n
            if not self.cons(i):
                break
            i += 1
        i += 1
        while True:
            while True:
                if i > self.j:
                    return n
                if self.cons(i):
                    break
                i += 1

            i += 1
            n += 1

            while True:
                if i > self.j:
                    return n
                if not self.cons(i):
                    break
                i += 1
            i += 1

    def vowel_in_stem(self):
        """ True iff 0...j contains vowel """
        for i in range(0, self.j+1):
            if not self.cons(i):
                return True
        return False

    def doublec(self, j):
        """ True iff j, j-1 contains double consonant """
        if j < 1 or self.b[j] != self.b[j-1]:
            return False
        return self.cons(j)

    def cvc(self, i):
        """ True iff i-2,i-1,i is consonent-vowel consonant
        and if second c isn't w,x, or y.
        used to restore e at end of short words like cave, love, hope, crime
        """
        if (i < 2 or not self.cons(i) or self.cons(i-1) or not self.cons(i-2) or
                self.b[i] in 'wxy'):
            return False
        return True

    def ends(self, s):
        length = len(s)
        """ True iff 0...k ends with string s """
        res = (self.b[self.k-length+1:self.k+1] == s)
        if res:
            self.j = self.k - length
        return res

    def setto(self, s):
        """ set j+1...k to string s, readjusting k """
        length = len(s)
        self.b[self.j+1:self.j+1+length] = s
        self.k = self.j + length

    def r(self, s):
        if self.m() > 0:
            self.setto(s)

    def step1ab(self):
        if self.b[self.k] == 's':
            if self.ends(['s', 's', 'e', 's']):
                self.k -= 2
            elif self.ends(['i', 'e', 's']):
                self.setto(['i'])
            elif self.b[self.k-1] != 's':
                self.k -= 1
        if self.ends(['e', 'e', 'd']):
            if self.m() > 0:
                self.k -= 1
        elif ((self.ends(['e', 'd']) or self.ends(['i', 'n', 'g'])) and
              self.vowel_in_stem()):
            self.k = self.j
            if self.ends(['a', 't']):
                self.setto(['a', 't', 'e'])
            elif self.ends(['b', 'l']):
                self.setto(['b', 'l', 'e'])
            elif self.ends(['i', 'z']):
                self.setto(['i', 'z', 'e'])
            elif self.doublec(self.k):
                self.k -= 1
                if self.b[self.k] in 'lsz':
                    self.k += 1
            elif self.m() == 1 and self.cvc(self.k):
                self.setto(['e'])

    def step1c(self):
        """ turn terminal y into i if there's a vowel in stem """
        if self.ends(['y']) and self.vowel_in_stem():
            self.b[self.k] = 'i'

    def step2and3(self):
        for end, repl in _s2_options.get(self.b[self.k-1], []):
            if self.ends(end):
                self.r(repl)
                break

        for end, repl in _s3_options.get(self.b[self.k], []):
            if self.ends(end):
                self.r(repl)
                break

    def step4(self):
        ch = self.b[self.k-1]

        if ch == 'o':
            if not ((self.ends(['i', 'o', 'n']) and self.b[self.j] in 'st') or
                    self.ends(['o', 'u'])):
                return
        else:
            endings = _s4_endings.get(ch, [])
            for end in endings:
                if self.ends(end):
                    break
            else:
                return

        if self.m() > 1:
            self.k = self.j

    def step5(self):
        self.j = self.k
        if self.b[self.k] == 'e':
            a = self.m()
            if a > 1 or a == 1 and not self.cvc(self.k-1):
                self.k -= 1
        if self.b[self.k] == 'l' and self.doublec(self.k) and self.m() > 1:
            self.k -= 1

    def result(self):
        return ''.join(self.b[:self.k+1])

    def stem(self):
        if self.k > 1:
            self.step1ab()
            self.step1c()
            self.step2and3()
            self.step4()
            self.step5()
        return self.result()
