"""Spelling Corrector in Python 3; see http://norvig.com/spell-correct.html

Copyright (c) 2007-2016 Peter Norvig
MIT license: www.opensource.org/licenses/mit-license.php
"""

import re

from collections import Counter
from pathlib import Path

def words(text):
    return re.findall(r"\w+", text.lower())


WORDS = Counter(
    words(
        (Path(__file__).parent / 'en.txt').read_text()
    )
)


def correction(text, min_word_length=4):
    """
    Spell correction based on Most probable spelling correction for word.
    :param text: str
    :param min_word_length: word length
    :return: str
    """
    return " ".join(
        (
            i
            if len(i) < min_word_length or WORDS[i]
            else max(candidates(i), key=probability)
        )
        for i in text.split()
    )


def probability(word, n=sum(WORDS.values())):
    """
    Probability of `word`.
    :param word:
    :param n:
    :return: float
    """
    return WORDS[word] / n


def candidates(word):
    """
    Generate possible spelling corrections for word.
    :param word: str
    :return: set of known words
    """
    return known([word]) or known(edits1(word)) or known(edits2(word)) or [word]


def known(words):
    """
    The subset of `words` that appear in the dictionary of WORDS.
    :param words: list of str
    :return: unique set of words
    """
    return set(w for w in words if w in WORDS)


def edits1(word):
    """
    All edits that are one edit away from `word`.
    :param word: String
    :return: set of words
    """
    letters = "abcdefghijklmnopqrstuvwxyz"
    splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
    deletes = [L + R[1:] for L, R in splits if R]
    transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
    replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
    inserts = [L + c + R for L, R in splits for c in letters]
    return set(deletes + transposes + replaces + inserts)


def edits2(word):
    """
    All edits that are two edits away from `word`.
    :param word: string
    :return: string generator
    """
    return (e2 for e1 in edits1(word) for e2 in edits1(e1))
