from collections import defaultdict
from typing import Dict, List, Tuple

from sastadev.deregularise import correctinflection
from sastadev.metadata import bpl_word, defaultpenalty
from sastadev.sastatoken import Token
from sastadev.sastatypes import ReplacementMode, SynTree, TokenTreePredicate
from sastadev.treebankfunctions import find1

BasicExpansion = Tuple[str, List[str], str, str, str, int]
BasicReplacement = Tuple[str, str, str, str, str, int]
KnownReplacement = Tuple[str, str, str, str, str, ReplacementMode]

dp = defaultpenalty
dp6 = dp + 6
dp3 = dp + 3
dp2 = dp + 2
pron = 'Pronunciation'
orth = 'Orthography'
infpron = 'Informal Pronunciation'
initdev = 'Initial Devoicing'
codared = 'Coda Reduction'
apomiss = 'Missing Apostrophe'
addschwa = 'Schwa addition'
onsetred = 'Onset Reduction'
d_er = 'd-onset on er'
spellerr = 'Spelling Error'
varpron = 'Alternative Pronunciation'
pronerr = 'Wrong Pronunciation'
contract = 'Contraction'
t_ie = 't-Insertion with ie'
fndrop = 'Final n drop'
zdev = 'Devoicing of /z/'
wrongpron = 'Wrong Prunciation'
phonrepl = '/{wrong}/ instead of /{correct}/'
wronginfl = 'Incorrect inflection'
morph = 'Morphology'
overgen = 'Overgeneralisation'
typo = 'Typo'
typorepl = '{wrong} instead of {correct}'
redpron = 'reduced pronunciation'
emphasis = 'emphasis'
prefixdrop = 'prefix drop'
voweldup = 'vowel duplication'
erdrop = 'er drop'
closedvow = 'closed vowel'
vzdevoicing = 'v/z devoicing'

#: The constant *Rvzlist* contains a list of adpositions that can combine with *er* into
#: a so-called pronominal adverb. Example: *van*, since *er* + * van* = *ervan*.
#:
Rvzlist = ['aan', 'achter', 'achteraan', 'achterin', 'achterop', 'af', 'beneden', 'benevens', 'bij', 'binnen',
           'binnenuit', 'boven', 'bovenaan', 'bovenin', 'bovenop', 'buiten', 'dichtbij', 'door', 'doorheen', 'heen',
           'in', 'langs', 'langsheen', 'langszij', 'mede', 'mee', 'middendoor', 'na', 'naar', 'naartoe', 'naast',
           'nabij', 'neer', 'om', 'omheen', 'onder', 'onderaan', 'op', 'over', 'overheen', 'rond', 'rondom', 'tegen',
           'tegenaan', 'tegenin', 'tegenover', 'toe', 'tussen', 'tussenin', 'tusssen', 'uit', 'van', 'vanaf',
           'vandaan', 'vandoor', 'vanop', 'vanuit', 'vanwege', 'vlakbij', 'voor', 'vooraan', 'voorbij', 'zonder']

#: The constant *ervzvariants* contains a list of 6-tuples (type *BasicReplacement*)
#: for variants of *er* (*der*, *d'r*) plus an adposition. The list is generated by using
#: the constant *Rvzlist*
#:
#: .. autodata:: basicreplacements::Rvzlist
#:        :no-value:
#:
ervzvariants: List[BasicReplacement] = \
    [('der' + vz, 'er' + vz, pron, varpron, d_er, dp) for vz in Rvzlist] + \
    [("d'r" + vz, 'er' + vz, pron, varpron, d_er, dp) for vz in Rvzlist]

#: The constant *innereplacements* contains replacements for short closed syllable prepositions followed by *e*
#: (and with final consonant doubling), e.g. *inne* for the preposition *in*. They can be replaced by *in* or by *erin*.
#: The preferred replacement for *inne* and *innu* is by an expansion into preposition + definite article, so
#: replacement by *in* has a higher penalty than such an expansion, and replacement by *erin* has an even higher penalty.
#:
innereplacements = [('inne', 'erin', pron, wrongpron, erdrop, dp6),
                    ('inne', 'in', pron, wrongpron, emphasis, dp3),
                    ('omme', 'erom', pron, wrongpron, erdrop, dp6),
                    ('omme', 'om', pron, wrongpron, emphasis, dp3),
                    ('oppe', 'erop', pron, wrongpron, erdrop, dp6),
                    ('oppe', 'op', pron, wrongpron, emphasis, dp3),
                    ('vanne', 'ervan', pron, wrongpron, erdrop, dp6),
                    ('vanne', 'van', pron, wrongpron, emphasis, dp3),
                    ]
#: The constant *innureplacements* is derived from the constant *innereplacements* by replacing the final *e* of the
#: first word in each tuple by an *u*.
#:
innureplacements = [(w1[:-1] + 'u', w2, c, n, v, p) for (w1, w2, c, n, v, p) in innereplacements]

#: The constant *basicreplacementlist* contains 6-tuples of strings with:
#:
#: * frequent deviant spelling of a word, usually to indicate how the word was (incorrectly) pronounced. together with (e.g. "as", "effe")
#: * the correct spelling of the word ("als", "even")
#: * linguistic level (e.g Pronunciation, Orthography)
#: * characterisation of the deviancy, e.g. "Informal Pronunciation", "Wrong Pronunciation"
#: * details about the deviation, e.g. "Coda Reduction", "Initial Devoicing"
#: * penalty for the replacement
#:
#: It also includes variants of R-pronouns + adposition, e.g. *dervan* or *d'rvan*
#: instead of *ervan* for all adpositions that allow an R-pronoun. It derives these by
#: using the constant *ervzvariants*
#:
#: .. autodata:: basicreplacements::ervzvariants
#:       :no-value:
#:
#: It obtains replacements for short prepositions with 'e' or 'u' attached (e.g. *inne*, *innu* for *in*) from the constants
#: *innereplacements* and *innureplacements*.
#:
#: .. autodata:: basicreplacements::innereplacements
#:      :no-value:
#:
#: .. autodata:: basicreplacements::innureplacements
#:      :no-value:
#:
basicreplacementlist: List[BasicReplacement] = [('as', 'als', pron, infpron, codared, dp),
                                                ('isse', 'is', pron, infpron, addschwa, dp),
                                                ('ooke', 'ook', pron, infpron, addschwa, dp),
                                                ('t', "'t", orth, spellerr, apomiss, dp),
                                                ('effjes', 'eventjes', pron, infpron, varpron, dp),
                                                ('effetjes', 'eventjes', pron, infpron, varpron, dp),
                                                ('effe', 'even', pron, infpron, varpron, dp),
                                                ('set', 'zet', pron, infpron, initdev, dp),
                                                ('fan', 'van', pron, infpron, initdev, dp),
                                                ('hie', 'hier', pron, pronerr, codared, dp),
                                                ('eers', 'eerst', pron, pronerr, codared, dp),
                                                ('era', 'eraf', pron, pronerr, codared, dp),
                                                ('il', 'wil', pron, pronerr, onsetred, dp),
                                                ('tee', 'twee', pron, pronerr, onsetred, dp),
                                                ('nie', 'niet', pron, infpron, codared, dp),
                                                ('s', 'is', orth, spellerr, apomiss, dp),
                                                ('ooke', 'ook', pron, infpron, addschwa, dp),
                                                ('it', 'dit', pron, pronerr, onsetred, dp),
                                                ('da', 'dat', pron, infpron, codared, dp),
                                                ('si', 'zit', pron, infpron, codared, dp),  # and zdev
                                                ('ieduleen', 'iedereen', pron, wrongpron, phonrepl.format(wrong='l', correct='r'), dp),
                                                ('allemaaw', 'allemaal', pron, wrongpron, phonrepl.format(wrong='w', correct='l'), dp),
                                                ('amaal', 'allemaal', pron, infpron, varpron, dp),
                                                ('wiw', 'wil', pron, wrongpron, phonrepl.format(wrong='w', correct='l'), dp),
                                                ('annug', 'ander', pron, wrongpron, phonrepl.format(wrong='nug', correct='der'), dp),
                                                ('nohug', 'nodig', pron, wrongpron, phonrepl.format(wrong='hu', correct='di'), dp),
                                                ('magge', 'mogen', morph, wronginfl, '{} & {}'.format(overgen, infpron), dp),
                                                ('maggen', 'mogen', morph, wronginfl, overgen, dp),
                                                ('aleen', 'alleen', orth, typo, typorepl.format(wrong='alleen', correct='alleen'), dp),
                                                ('heef', 'heeft', pron, infpron, codared, dp),
                                                ('saan', 'staan', pron, wrongpron, onsetred, dp),
                                                ('saan', 'gaan', pron, wrongpron, wrongpron, dp + 2),
                                                ('jerke', 'werken', pron, wrongpron, wrongpron, dp),
                                                ('taan', 'staan', pron, wrongpron, onsetred, dp),
                                                ("a'maal", 'allemaal', pron, infpron, redpron, dp),
                                                ('taan', 'staan', pron, wrongpron, onsetred, dp),
                                                ('beurt', 'gebeurt', pron, wrongpron, prefixdrop, dp),
                                                ('dahaar', 'daar', pron, emphasis, voweldup, dp),
                                                ('desu', 'deze', pron, infpron, vzdevoicing, dp),
                                                ('tan', 'dan', pron, infpron, initdev, dp),
                                                ('tat', 'dat', pron, infpron, initdev, dp),
                                                ('tit', 'dit', pron, infpron, initdev, dp),
                                                ('lape', 'slapen', pron, infpron, f'{onsetred}+{fndrop}', dp),
                                                ('vas', 'vast', pron, infpron, codared, dp),
                                                ('datte', 'dat', pron, infpron, emphasis, dp),
                                                ('omdatte', 'dat', pron, infpron, emphasis, dp),
                                                ('cirtus', 'circus', pron, wrongpron, typorepl.format(wrong='t', correct='c'), dp)

                                                ] + \
    ervzvariants + \
    innereplacements + \
    innureplacements


#: The dictionary *basicreplacements* maps a word with deviant orthography to a list of
#: 5-tuples containing the correct orthography, 3 strings to encode metadata, and an integer for the associated penalty. The
#: dictionary is derived from the constant *basicreplacementlist*
#:
#: .. autodata:: basicreplacements::basicreplacementlist
#:       :no-value:
#:
basicreplacements: Dict[str, List[Tuple[List[str], str, str, str, int]]] = defaultdict(list)
for w1, w2, c, n, v, p in basicreplacementlist:
    basicreplacements[w1].append((w2, c, n, v, p))

#closedsyllshortpreps = ['in', 'met',  'om',  'op',  'van' ]  # langs removed
#: The constant *closedsyllshortexpansion* contains expansions for short closed syllable prepositions followed by *e*
#: (and with final consonant doubling), e.g. *inne* for the preposition *in*. They can be expanded to preposition + *de*
#: (e.g. *in de*) or preposition + *het* (e.g. *in het*), with a preference for the former, indicated by a lower
#:penalty than the second alternative expansion.
#:
closesyllshortprepexpansions = [('inne', ['in', 'de'], pron, infpron, contract, dp),
                                ('inne', ['in', 'het'], pron, infpron, contract, dp2),  # het-cases temporarily put off, getexpansions contains a bugf
                                ('mette', ['met', 'de'], pron, infpron, contract, dp),
                                ('mette', ['met', 'het'], pron, infpron, contract, dp2),
                                ('omme', ['om', 'de'], pron, infpron, contract, dp),
                                ('omme', ['om', 'het'], pron, infpron, contract, dp2),
                                ('oppe', ['op', 'de'], pron, infpron, contract, dp),
                                ('oppe', ['op', 'het'], pron, infpron, contract, dp2),
                                ('vanne', ['van', 'de'], pron, infpron, contract, dp),
                                ('vanne', ['van', 'het'], pron, infpron, contract, dp2),
                                ]

#: The constant *innuclosedsyllshortexpansion* contains expansions for short closed syllable prepositions followed by *u*
#: (and with final consonant doubling), e.g. *innu* for the preposition *in*. It is derived from the constant
#: *closedsyllshortprepexpansions* by replacing the final *e* of the first word of the tuple by *u*.
#:
innuclosedsyllshortprepexpansions = [(w1[:-1] + 'u', w2, c, n, v, p)
                                     for (w1, w2, c, n, v, p) in closesyllshortprepexpansions]

#opensyllshortpreps = ['aan',  'door',  'uit',   'voor', ]

#: The constant *basicexpansionlist* is the concatenation of a number of lists of type *BasicExpansion*.
#:
#: * A list of  6-tuples  with:
#:
#:   * frequent contracted spelling of a word sequence, usually to indicate how the word was actually (possibly incorrectly) pronounced, (e.g. "innu", "das")
#:   * the correct expansion of the word as a list of strings (["in", "de"], ["dat", "is"])
#:   * linguistic level (e.g "Pronunciation", "Orthography")
#:   * characterisation of the deviancy, e.g. "Informal Pronunciation"
#:   * details about the deviancy, e.g. "Contraction"
#:   * penalty for this modification
#:
#: * the constant *closesyllshortprepexpansions*:
#:   * .. autodata:: basicreplacements::closesyllshortprepexpansions
#:
#: * the constant *innuclosesyllshortprepexpansions*:
#:   * .. autodata:: basicreplacements::innuclosesyllshortprepexpansions
#:
basicexpansionlist: List[BasicExpansion] = \
    [('dis', ['dit', 'is'], pron, infpron, contract, dp),
     ('das', ['dat', 'is'], pron, infpron, contract, dp),
     ('tis', ['dit', 'is'], pron, infpron, contract, dp),
     ('waas', ['waar', 'is'], pron, infpron, contract, dp),
     ('is-t-ie', ['is', 'ie'], pron, infpron, t_ie, dp),
     ('als-t-ie', ['als', 'ie'], pron, infpron, t_ie, dp),
     ('of-t-ie', ['of', 'ie'], pron, infpron, t_ie, dp),
     ('as-t-ie', ['als', 'ie'], pron, infpron, t_ie, dp),
     ("dit's", ["dit", "is"], pron, infpron, contract, dp),
     ("dat's", ["dat", "is"], pron, infpron, contract, dp),
     ("datte", ['dat', 'ie'], pron, infpron, contract, dp + 2),
     ("omdatte", ['omdat', 'ie'], pron, infpron, contract, dp + 2)
     ]
# + closesyllshortprepexpansions # put off does not lead to improvement
# + innuclosedsyllshortprepexpansions # put off does not lead to improvement


#: The dictionary *basicexpansions* maps a contracted word form to a list of 4-tuples
#: containing its expansion as a list of words, plus 3 strings to encode metadata. The
#: dictionary is derived from the constant *basicexpansionlist*:
#:
#: .. autodata:: basicreplacements::basicexpansionlist
#:       :no-value:
#:
basicexpansions: Dict[str, List[Tuple[List[str], str, str, str, int]]] = defaultdict(list)
for (w1, w2, c, n, v, p) in basicexpansionlist:
    basicexpansions[w1].append((w2, c, n, v, p))

knownreplacements: List[KnownReplacement] = [
    ('ze', "z'n", pron, infpron, fndrop, bpl_word),
    ('desu', 'deze', pron, infpron, zdev, bpl_word),
    ('mij', 'mijn', pron, infpron, fndrop, bpl_word),

]

knownreplacementsdict: Dict[Tuple[str, str], KnownReplacement] = {(repl[0], repl[1]): repl for repl in knownreplacements}


def getmeta4CHATreplacements(wrongword: str, correctword: str) -> KnownReplacement:
    if (wrongword, correctword) in knownreplacementsdict:
        result = knownreplacementsdict[(wrongword, correctword)]
    else:
        nwms = correctinflection(wrongword)
        if nwms != []:
            for nw, metavalue in nwms:  # hier wordt overschreven als er meerdere resultaten zouden zijn
                if nw == correctword:
                    result = (wrongword, correctword, 'Morphology', 'InflectionError', metavalue, bpl_word)
        else:
            result = (wrongword, correctword, 'Lexicology', 'Lexical Error', 'Paraphasy',
                      bpl_word)
    return result


#: dttp = default token tree predicate
def dtp(token, tree): return True


def welnietttp(token: Token, stree: SynTree) -> bool:
    """
    The function *welniettp* checks whether *token* has been analysed as a verb in *stree*
    :param token: input token
    :param stree: input syntactic structure
    :return: True if *token* has been analysed as a verb in *stree*, False otherwise.
    """
    beginval = str(token.pos)
    wordnode = find1(stree, f'.//node[@pt="ww" and @begin="{beginval}"]')
    result = wordnode is not None
    return result


# keer removed
#: The constant *disambiguation_replacements* contains a list of tuples. Each tuple
#: contains  the list of words each of which should be replaced to avoid
#: ambiguity, and their replacement.
#:
#: Currently,
#:
#: * singular neuter nouns that can also be a verb form (e.g. *werk*) are replaced by the word *gas*, which only is a noun;
#: * singular uter nouns that can also be a verb form (e.g. *puzzel*) are replaced by the word *teil*, which is only a noun;
#: * plural nouns that can also be a verb (e.g. *planten*) are replaced by the word *teilen*, which is only a noun;
#: * adjectives without an *-e* ending that can also be a verb (e.g. *dicht*) are replaced by the word *mooi*, which is only an adjective;
#: * adjectives with an *-e* ending that can also be a verb (*e.g. witte*) are replaced by the word *mooie*, which is only an adjective.
#: * the words *wel* and *niet* are replaced by a nonambiguous adverb (*ietsjes*) if they are parsed as a verb in the original parse
#:
#: **Remark** Currently no distinction is made between singular *count* and singular *mass* nouns: they are both replaced by the same word. This may have to be adapted.
disambiguation_replacements: List[Tuple[TokenTreePredicate, List[str], str]] = \
    [(dtp, ['huis', 'water', 'paard', 'werk', 'stuur', 'feest', 'snoep', 'geluid',
                                      'kwartet', 'kruis'], 'gas'),
     (dtp, ['toren', 'fiets', 'puzzel', 'boom', 'vis', 'melk', 'zon', 'pot', 'klok',
            'school', 'boer', 'lepel', 'jas', 'tuin', 'fles', 'lucht', 'emmer', 'maan', 'kachel',
            'kwak', 'verf', 'hop', 'kam', 'spiegel', 'klap', 'stal', 'lijm', 'lift', 'kat',
            'wagen', 'schep', 'kus', 'wind', 'borstel', 'duim', 'strik', 'klik', 'pleister',
            'stok', 'knoop', 'rits', 'knikker', 'put', 'boor', 'plons', 'beurt', 'toeter', 'poot',
            'punt', 'post', 'gom', 'tap', 'kraanwagen', 'drup', 'wieg', 'kriebel', 'pit', 'zaag',
            'slof', 'deuk', 'hark', 'jeuk', 'stift', 'aard', 'hamster', 'kiek', 'haak', 'schroef',
            'tape', 'vorm', 'klem', 'mot', 'druppel'], 'teil'),
     (dtp, ['bomen', 'kussen', 'kaarten', 'beesten', 'weken', 'huizen', 'apen', 'poten',
            'wieken', 'paarden', 'stoelen', 'ramen', 'strepen', 'planten', 'groeten',
            'flessen', 'boeren', 'punten', 'tranen'], 'teilen'),
     (dtp, ['snel', 'wit', 'kort', 'dicht'], 'mooi'),
     (dtp, ['witte'], 'mooie'),
     (welnietttp, ['wel', 'niet'], 'ietsjes')  # find a different adverb that does not get inside constituents (ietsjes?)
     ]


def getdisambiguationdict() -> Dict[str, Tuple[TokenTreePredicate, str]]:
    '''
    :return: a dictionary with words as key and a tuple of a condition and a replacement as values

    The function *getdisambiguationdict* creates a dictionary with word:(cond, replacement)
    items. It selects its content from the constant *disambiguation_replacements*:

    .. autodata:: basicreplacements::disambiguation_replacements
         :no-value:
    '''
    disambiguationdict = {}
    for cond, ws, repl in disambiguation_replacements:
        for w in ws:
            disambiguationdict[w] = cond, repl
    return disambiguationdict
