import wx

import trelby.misc as misc
import trelby.spellcheck as spellcheck
import trelby.undo as undo
import trelby.util as util


class SpellCheckDlg(wx.Dialog):
    def __init__(self, parent, ctrl, sc, gScDict):
        wx.Dialog.__init__(
            self,
            parent,
            -1,
            "Spell checker",
            style=wx.DEFAULT_DIALOG_STYLE | wx.WANTS_CHARS,
        )

        self.ctrl = ctrl

        # spellcheck.SpellCheck
        self.sc = sc

        # user's global spell checker dictionary
        self.gScDict = gScDict

        # have we added any words to global dictionary
        self.changedGlobalDict = False

        vsizer = wx.BoxSizer(wx.VERTICAL)

        hsizer = wx.BoxSizer(wx.HORIZONTAL)

        hsizer.Add(
            wx.StaticText(self, -1, "Word:"), 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 10
        )
        self.replaceEntry = wx.TextCtrl(self, -1, style=wx.TE_PROCESS_ENTER)
        hsizer.Add(self.replaceEntry, 1, wx.EXPAND)

        vsizer.Add(hsizer, 1, wx.EXPAND | wx.BOTTOM, 15)

        gsizer = wx.FlexGridSizer(2, 2, 10, 10)
        gsizer.AddGrowableCol(1)

        replaceBtn = wx.Button(self, -1, "&Replace")
        gsizer.Add(replaceBtn)

        addScriptBtn = wx.Button(self, -1, "Add to &script dictionary")
        gsizer.Add(addScriptBtn, 0, wx.EXPAND)

        skipBtn = wx.Button(self, -1, "S&kip")
        gsizer.Add(skipBtn)

        addGlobalBtn = wx.Button(self, -1, "Add to &global dictionary")
        gsizer.Add(addGlobalBtn, 0, wx.EXPAND)

        vsizer.Add(gsizer, 0, wx.EXPAND, 0)

        suggestBtn = wx.Button(self, -1, "S&uggest replacement")
        vsizer.Add(suggestBtn, 0, wx.EXPAND | wx.TOP, 10)

        self.Bind(wx.EVT_TEXT_ENTER, self.OnReplace, id=self.replaceEntry.GetId())

        self.Bind(wx.EVT_BUTTON, self.OnReplace, id=replaceBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnAddScript, id=addScriptBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnAddGlobal, id=addGlobalBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnSkip, id=skipBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnSuggest, id=suggestBtn.GetId())

        self.Bind(wx.EVT_CHAR, self.OnChar)
        self.replaceEntry.Bind(wx.EVT_CHAR, self.OnChar)
        replaceBtn.Bind(wx.EVT_CHAR, self.OnChar)
        addScriptBtn.Bind(wx.EVT_CHAR, self.OnChar)
        skipBtn.Bind(wx.EVT_CHAR, self.OnChar)
        addGlobalBtn.Bind(wx.EVT_CHAR, self.OnChar)
        suggestBtn.Bind(wx.EVT_CHAR, self.OnChar)

        util.finishWindow(self, vsizer)

        self.showWord()

    def showWord(self):
        self.ctrl.sp.line = self.sc.line
        self.ctrl.sp.column = self.sc.col
        self.ctrl.sp.setMark(self.sc.line, self.sc.col + len(self.sc.word) - 1)

        self.replaceEntry.SetValue(self.sc.word)

        self.ctrl.makeLineVisible(self.sc.line)
        self.ctrl.updateScreen()

    def gotoNext(self, incCol=True):
        if incCol:
            self.sc.col += len(self.sc.word)

        if not self.sc.findNext():
            wx.MessageBox("No more incorrect words found.", "Results", wx.OK, self)

            self.EndModal(wx.ID_OK)

            return

        self.showWord()

    def OnChar(self, event):
        kc = event.GetKeyCode()

        if kc == wx.WXK_ESCAPE:
            self.EndModal(wx.ID_OK)

            return

        event.Skip()

    def OnReplace(self, event):
        if not self.sc.word:
            return

        sp = self.ctrl.sp
        u = undo.SinglePara(sp, undo.CMD_MISC, self.sc.line)

        word = util.toInputStr(misc.fromGUI(self.replaceEntry.GetValue()))
        ls = sp.lines

        sp.gotoPos(self.sc.line, self.sc.col)

        ls[self.sc.line].text = util.replace(
            ls[self.sc.line].text, word, self.sc.col, len(self.sc.word)
        )

        sp.rewrapPara(sp.getParaFirstIndexFromLine(self.sc.line))

        # rewrapping a paragraph can have moved the cursor, so get the new
        # location of it, and then advance past the just-changed word
        self.sc.line = sp.line
        self.sc.col = sp.column + len(word)

        sp.clearMark()
        sp.markChanged()

        u.setAfter(sp)
        sp.addUndo(u)

        self.gotoNext(False)

    def OnSkip(self, event=None, autoFind=False):
        if not self.sc.word:
            return

        self.gotoNext()

    def OnAddScript(self, event):
        if not self.sc.word:
            return

        self.ctrl.sp.scDict.add(self.sc.word)
        self.ctrl.sp.markChanged()
        self.gotoNext()

    def OnAddGlobal(self, event):
        if not self.sc.word:
            return

        self.gScDict.add(self.sc.word)
        self.changedGlobalDict = True

        self.gotoNext()

    def OnSuggest(self, event):
        if not self.sc.word:
            return

        isAllCaps = self.sc.word == util.upper(self.sc.word)
        isCapitalized = self.sc.word[:1] == util.upper(self.sc.word[:1])

        word = util.lower(self.sc.word)

        wl = len(word)
        wstart = word[:2]
        d = 500
        fifo = util.FIFO(5)
        wx.BeginBusyCursor()

        for w in spellcheck.prefixDict[util.getWordPrefix(word)]:
            if w.startswith(wstart):
                d = self.tryWord(word, wl, w, d, fifo)

        for w in self.gScDict.words.keys():
            if w.startswith(wstart):
                d = self.tryWord(word, wl, w, d, fifo)

        for w in self.ctrl.sp.scDict.words.keys():
            if w.startswith(wstart):
                d = self.tryWord(word, wl, w, d, fifo)

        items = fifo.get()

        wx.EndBusyCursor()

        if len(items) == 0:
            wx.MessageBox("No similar words found.", "Results", wx.OK, self)

            return

        dlg = wx.SingleChoiceDialog(self, "Most similar words:", "Suggestions", items)

        if dlg.ShowModal() == wx.ID_OK:
            sel = dlg.GetSelection()

            newWord = items[sel]

            if isAllCaps:
                newWord = util.upper(newWord)
            elif isCapitalized:
                newWord = util.capitalize(newWord)

            self.replaceEntry.SetValue(newWord)

        dlg.Destroy()

    # if w2 is closer to w1 in Levenshtein distance than d, add it to
    # fifo. return min(d, new_distance).
    def tryWord(self, w1, w1len, w2, d, fifo):
        if abs(w1len - len(w2)) > 3:
            return d

        d2 = spellcheck.lev(w1, w2)

        if d2 <= d:
            fifo.add(w2)

            return d2

        return d
