# -*- coding: utf-8 -*-
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html

import collections
import re

from aqt.qt import *
from anki.consts import *
import aqt
from anki.sound import playFromText, clearAudioQueue
from aqt.utils import saveGeom, restoreGeom, mungeQA,\
    showInfo, askUser, getOnlyText, \
     showWarning, openHelp, downArrow
from anki.utils import isMac, isWin, joinFields
from aqt.webview import AnkiWebView
import json
from anki.hooks import runFilter


class CardLayout(QDialog):

    def __init__(self, mw, note, ord=0, parent=None, addMode=False):
        QDialog.__init__(self, parent or mw, Qt.Window)
        mw.setupDialogGC(self)
        self.mw = aqt.mw
        self.parent = parent or mw
        self.note = note
        self.ord = ord
        self.col = self.mw.col
        self.mm = self.mw.col.models
        self.model = note.model()
        self.mw.checkpoint(_("Card Types"))
        self.addMode = addMode
        if addMode:
            # save it to DB temporarily
            self.emptyFields = []
            for name, val in list(note.items()):
                if val.strip():
                    continue
                self.emptyFields.append(name)
                note[name] = "(%s)" % name
            note.flush()
        self.setupTopArea()
        self.setupMainArea()
        self.setupButtons()
        self.setupShortcuts()
        self.setWindowTitle(_("Card Types for %s") % self.model['name'])
        v1 = QVBoxLayout()
        v1.addWidget(self.topArea)
        v1.addWidget(self.mainArea)
        v1.addLayout(self.buttons)
        v1.setContentsMargins(12,12,12,12)
        self.setLayout(v1)
        self.redraw()
        restoreGeom(self, "CardLayout")
        self.setWindowModality(Qt.ApplicationModal)
        self.show()
        # take the focus away from the first input area when starting up,
        # as users tend to accidentally type into the template
        self.setFocus()

    def redraw(self):
        self.cards = self.col.previewCards(self.note, 2)
        idx = self.ord
        if idx >= len(self.cards):
            self.ord = len(self.cards) - 1

        self.redrawing = True
        self.updateTopArea()
        self.updateMainArea()
        self.redrawing = False
        self.onCardSelected(self.ord)

    def setupShortcuts(self):
        for i in range(1,9):
            QShortcut(QKeySequence("Ctrl+%d" % i), self, activated=lambda i=i: self.selectCard(i))

    def selectCard(self, n):
        self.ord = n-1
        self.redraw()

    def setupTopArea(self):
        self.topArea = QWidget()
        self.topAreaForm = aqt.forms.clayout_top.Ui_Form()
        self.topAreaForm.setupUi(self.topArea)
        self.topAreaForm.templateOptions.setText(_("Options") + " "+downArrow())
        self.topAreaForm.templateOptions.clicked.connect(self.onMore)
        self.topAreaForm.templatesBox.currentIndexChanged.connect(self.onCardSelected)

    def updateTopArea(self):
        cnt = self.mw.col.models.useCount(self.model)
        self.topAreaForm.changesLabel.setText(ngettext(
            "Changes below will affect the %(cnt)d note that uses this card type.",
            "Changes below will affect the %(cnt)d notes that use this card type.",
            cnt) % dict(cnt=cnt))
        self.updateCardNames()

    def updateCardNames(self):
        self.redrawing = True
        combo = self.topAreaForm.templatesBox
        combo.clear()
        combo.addItems(self._summarizedName(t) for t in self.model['tmpls'])
        combo.setCurrentIndex(self.ord)
        combo.setEnabled(not self._isCloze())
        self.redrawing = False

    def _summarizedName(self, tmpl):
        return "{}: {} -> {}".format(
            tmpl['name'],
            self._fieldsOnTemplate(tmpl['qfmt']),
            self._fieldsOnTemplate(tmpl['afmt']))

    def _fieldsOnTemplate(self, fmt):
        matches = re.findall("{{[^#/}]+?}}", fmt)
        charsAllowed = 30
        result = collections.OrderedDict()
        for m in matches:
            # strip off mustache
            m = re.sub(r"[{}]", "", m)
            # strip off modifiers
            m = m.split(":")[-1]
            # don't show 'FrontSide'
            if m == "FrontSide":
                continue

            if m not in result:
                result[m] = True
                charsAllowed -= len(m)
                if charsAllowed <= 0:
                    break

        str = "+".join(result.keys())
        if charsAllowed <= 0:
            str += "+..."
        return str

    def _isCloze(self):
        return self.model['type'] == MODEL_CLOZE

    def setupMainArea(self):
        w = self.mainArea = QWidget()
        l = QHBoxLayout()
        l.setContentsMargins(0,0,0,0)
        l.setSpacing(3)
        left = QWidget()
        # template area
        tform = self.tform = aqt.forms.template.Ui_Form()
        tform.setupUi(left)
        tform.label1.setText(" →")
        tform.label2.setText(" →")
        tform.labelc1.setText(" ↗")
        tform.labelc2.setText(" ↘")
        if self.style().objectName() == "gtk+":
            # gtk+ requires margins in inner layout
            tform.tlayout1.setContentsMargins(0, 11, 0, 0)
            tform.tlayout2.setContentsMargins(0, 11, 0, 0)
            tform.tlayout3.setContentsMargins(0, 11, 0, 0)
        tform.groupBox_3.setTitle(_(
            "Styling (shared between cards)"))
        tform.front.textChanged.connect(self.saveCard)
        tform.css.textChanged.connect(self.saveCard)
        tform.back.textChanged.connect(self.saveCard)
        l.addWidget(left, 5)
        # preview area
        right = QWidget()
        pform = self.pform = aqt.forms.preview.Ui_Form()
        pform.setupUi(right)
        if self.style().objectName() == "gtk+":
            # gtk+ requires margins in inner layout
            pform.frontPrevBox.setContentsMargins(0, 11, 0, 0)
            pform.backPrevBox.setContentsMargins(0, 11, 0, 0)

        self.setupWebviews()

        l.addWidget(right, 5)
        w.setLayout(l)

    def setupWebviews(self):
        pform = self.pform
        pform.frontWeb = AnkiWebView()
        pform.frontPrevBox.addWidget(pform.frontWeb)
        pform.backWeb = AnkiWebView()
        pform.backPrevBox.addWidget(pform.backWeb)
        jsinc = ["jquery.js","browsersel.js",
                 "mathjax/conf.js", "mathjax/MathJax.js",
                 "reviewer.js"]
        pform.frontWeb.stdHtml(self.mw.reviewer.revHtml(),
                               css=["reviewer.css"],
                               js=jsinc)
        pform.backWeb.stdHtml(self.mw.reviewer.revHtml(),
                              css=["reviewer.css"],
                               js=jsinc)

    def updateMainArea(self):
        if self._isCloze():
            cnt = len(self.mm.availOrds(
                self.model, joinFields(self.note.fields)))
            for g in self.pform.groupBox, self.pform.groupBox_2:
                g.setTitle(g.title() + _(" (1 of %d)") % max(cnt, 1))

    def onRemove(self):
        if len(self.model['tmpls']) < 2:
            return showInfo(_("At least one card type is required."))
        idx = self.ord
        cards = self.mm.tmplUseCount(self.model, idx)
        cards = ngettext("%d card", "%d cards", cards) % cards
        msg = (_("Delete the '%(a)s' card type, and its %(b)s?") %
            dict(a=self.model['tmpls'][idx]['name'], b=cards))
        if not askUser(msg):
            return
        if not self.mm.remTemplate(self.model, self.cards[idx].template()):
            return showWarning(_("""\
Removing this card type would cause one or more notes to be deleted. \
Please create a new card type first."""))
        self.redraw()

    # Buttons
    ##########################################################################

    def setupButtons(self):
        l = self.buttons = QHBoxLayout()
        help = QPushButton(_("Help"))
        help.setAutoDefault(False)
        l.addWidget(help)
        help.clicked.connect(self.onHelp)
        l.addStretch()
        addField = QPushButton(_("Add Field"))
        addField.setAutoDefault(False)
        l.addWidget(addField)
        addField.clicked.connect(self.onAddField)
        if not self._isCloze():
            flip = QPushButton(_("Flip"))
            flip.setAutoDefault(False)
            l.addWidget(flip)
            flip.clicked.connect(self.onFlip)
        l.addStretch()
        close = QPushButton(_("Close"))
        close.setAutoDefault(False)
        l.addWidget(close)
        close.clicked.connect(self.accept)

    # Cards
    ##########################################################################

    def onCardSelected(self, idx):
        if self.redrawing:
            return
        self.card = self.cards[idx]
        self.ord = idx
        self.playedAudio = {}
        self.readCard()
        self.renderPreview()

    def readCard(self):
        t = self.card.template()
        self.redrawing = True
        self.tform.front.setPlainText(t['qfmt'])
        self.tform.css.setPlainText(self.model['css'])
        self.tform.back.setPlainText(t['afmt'])
        self.tform.front.setAcceptRichText(False)
        self.tform.css.setAcceptRichText(False)
        self.tform.back.setAcceptRichText(False)
        self.tform.front.setTabStopWidth(30)
        self.tform.css.setTabStopWidth(30)
        self.tform.back.setTabStopWidth(30)
        self.redrawing = False

    def saveCard(self):
        if self.redrawing:
            return
        text = self.tform.front.toPlainText()
        self.card.template()['qfmt'] = text
        text = self.tform.css.toPlainText()
        self.card.model()['css'] = text
        text = self.tform.back.toPlainText()
        self.card.template()['afmt'] = text
        self.renderPreview()

    # Preview
    ##########################################################################

    _previewTimer = None

    def renderPreview(self):
        # schedule a preview when timing stops
        self.cancelPreviewTimer()
        self._previewTimer = self.mw.progress.timer(500, self._renderPreview, False)

    def cancelPreviewTimer(self):
        if self._previewTimer:
            self._previewTimer.stop()
            self._previewTimer = None

    def _renderPreview(self):
        self.cancelPreviewTimer()

        c = self.card
        ti = self.maybeTextInput
        bodyclass="card card%d" % (c.ord+1)

        q = ti(mungeQA(self.mw.col, c.q(reload=True)))
        q = runFilter("prepareQA", q, c, "clayoutQuestion")

        a = ti(mungeQA(self.mw.col, c.a()), type='a')
        a = runFilter("prepareQA", a, c, "clayoutAnswer")

        # use _showAnswer to avoid the longer delay
        self.pform.frontWeb.eval("_showAnswer(%s,'%s');" % (json.dumps(q), bodyclass))
        self.pform.backWeb.eval("_showAnswer(%s, '%s');" % (json.dumps(a), bodyclass))

        clearAudioQueue()
        if c.id not in self.playedAudio:
            playFromText(c.q())
            playFromText(c.a())
            self.playedAudio[c.id] = True

        self.updateCardNames()

    def maybeTextInput(self, txt, type='q'):
        if "[[type:" not in txt:
            return txt
        origLen = len(txt)
        txt = txt.replace("<hr id=answer>", "")
        hadHR = origLen != len(txt)
        def answerRepl(match):
            res = self.mw.reviewer.correct("exomple", "an example")
            if hadHR:
                res = "<hr id=answer>" + res
            return res
        if type == 'q':
            repl = "<input id='typeans' type=text value='exomple'>"
            repl = "<center>%s</center>" % repl
        else:
            repl = answerRepl
        return re.sub("\[\[type:.+?\]\]", repl, txt)

    # Card operations
    ######################################################################

    def onRename(self):
        name = getOnlyText(_("New name:"),
                           default=self.card.template()['name'])
        if not name:
            return
        if name in [c.template()['name'] for c in self.cards
                    if c.template()['ord'] != self.ord]:
            return showWarning(_("That name is already used."))
        self.card.template()['name'] = name
        self.redraw()

    def onReorder(self):
        n = len(self.cards)
        cur = self.card.template()['ord']+1
        pos = getOnlyText(
            _("Enter new card position (1...%s):") % n,
            default=str(cur))
        if not pos:
            return
        try:
            pos = int(pos)
        except ValueError:
            return
        if pos < 1 or pos > n:
            return
        if pos == cur:
            return
        pos -= 1
        self.mm.moveTemplate(self.model, self.card.template(), pos)
        self.ord = pos
        self.redraw()

    def _newCardName(self):
        n = len(self.cards) + 1
        while 1:
            name = _("Card %d") % n
            if name not in [c.template()['name'] for c in self.cards]:
                break
            n += 1
        return name

    def onAddCard(self):
        cnt = self.mw.col.models.useCount(self.model)
        txt = ngettext("This will create %d card. Proceed?",
                       "This will create %d cards. Proceed?", cnt) % cnt
        if not askUser(txt):
            return
        name = self._newCardName()
        t = self.mm.newTemplate(name)
        old = self.card.template()
        t['qfmt'] = old['qfmt']
        t['afmt'] = old['afmt']
        self.mm.addTemplate(self.model, t)
        self.ord = len(self.cards)
        self.redraw()

    def onFlip(self):
        old = self.card.template()
        self._flipQA(old, old)
        self.redraw()

    def _flipQA(self, src, dst):
        m = re.match("(?s)(.+)<hr id=answer>(.+)", src['afmt'])
        if not m:
            showInfo(_("""\
Anki couldn't find the line between the question and answer. Please \
adjust the template manually to switch the question and answer."""))
            return
        dst['afmt'] = "{{FrontSide}}\n\n<hr id=answer>\n\n%s" % src['qfmt']
        dst['qfmt'] = m.group(2).strip()
        return True

    def onMore(self):
        m = QMenu(self)

        if not self._isCloze():
            a = m.addAction(_("Add Card Type..."))
            a.triggered.connect(self.onAddCard)

            a = m.addAction(_("Remove Card Type..."))
            a.triggered.connect(self.onRemove)

            a = m.addAction(_("Rename Card Type..."))
            a.triggered.connect(self.onRename)

            a = m.addAction(_("Reposition Card Type..."))
            a.triggered.connect(self.onReorder)

            m.addSeparator()

            t = self.card.template()
            if t['did']:
                s = _(" (on)")
            else:
                s = _(" (off)")
            a = m.addAction(_("Deck Override...") + s)
            a.triggered.connect(self.onTargetDeck)

        a = m.addAction(_("Browser Appearance..."))
        a.triggered.connect(self.onBrowserDisplay)

        m.exec_(self.topAreaForm.templateOptions.mapToGlobal(QPoint(0,0)))

    def onBrowserDisplay(self):
        d = QDialog()
        f = aqt.forms.browserdisp.Ui_Dialog()
        f.setupUi(d)
        t = self.card.template()
        f.qfmt.setText(t.get('bqfmt', ""))
        f.afmt.setText(t.get('bafmt', ""))
        if t.get("bfont"):
            f.overrideFont.setChecked(True)
        f.font.setCurrentFont(QFont(t.get('bfont', "Arial")))
        f.fontSize.setValue(t.get('bsize', 12))
        f.buttonBox.accepted.connect(lambda: self.onBrowserDisplayOk(f))
        d.exec_()

    def onBrowserDisplayOk(self, f):
        t = self.card.template()
        t['bqfmt'] = f.qfmt.text().strip()
        t['bafmt'] = f.afmt.text().strip()
        if f.overrideFont.isChecked():
            t['bfont'] = f.font.currentFont().family()
            t['bsize'] = f.fontSize.value()
        else:
            del t['bfont']
            del t['bsize']

    def onTargetDeck(self):
        from aqt.tagedit import TagEdit
        t = self.card.template()
        d = QDialog(self)
        d.setWindowTitle("Anki")
        d.setMinimumWidth(400)
        l = QVBoxLayout()
        lab = QLabel(_("""\
Enter deck to place new %s cards in, or leave blank:""") %
                           self.card.template()['name'])
        lab.setWordWrap(True)
        l.addWidget(lab)
        te = TagEdit(d, type=1)
        te.setCol(self.col)
        l.addWidget(te)
        if t['did']:
            te.setText(self.col.decks.get(t['did'])['name'])
            te.selectAll()
        bb = QDialogButtonBox(QDialogButtonBox.Close)
        bb.rejected.connect(d.close)
        l.addWidget(bb)
        d.setLayout(l)
        d.exec_()
        if not te.text().strip():
            t['did'] = None
        else:
            t['did'] = self.col.decks.id(te.text())

    def onAddField(self):
        diag = QDialog(self)
        form = aqt.forms.addfield.Ui_Dialog()
        form.setupUi(diag)
        fields = [f['name'] for f in self.model['flds']]
        form.fields.addItems(fields)
        form.font.setCurrentFont(QFont("Arial"))
        form.size.setValue(20)
        diag.show()
        # Work around a Qt bug,
        # https://bugreports.qt-project.org/browse/QTBUG-1894
        if isMac or isWin:
            # No problems on Macs or Windows.
            form.fields.showPopup()
        else:
            # Delay showing the pop-up.
            self.mw.progress.timer(200, form.fields.showPopup, False)
        if not diag.exec_():
            return
        if form.radioQ.isChecked():
            obj = self.tform.front
        else:
            obj = self.tform.back
        self._addField(obj,
                       fields[form.fields.currentIndex()],
                       form.font.currentFont().family(),
                       form.size.value())

    def _addField(self, widg, field, font, size):
        t = widg.toPlainText()
        t +="\n<div style='font-family: %s; font-size: %spx;'>{{%s}}</div>\n" % (
            font, size, field)
        widg.setPlainText(t)
        self.saveCard()

    # Closing & Help
    ######################################################################

    def accept(self):
        self.reject()

    def reject(self):
        self.cancelPreviewTimer()
        clearAudioQueue()
        if self.addMode:
            # remove the filler fields we added
            for name in self.emptyFields:
                self.note[name] = ""
            self.mw.col.db.execute("delete from notes where id = ?",
                                   self.note.id)
        self.mm.save(self.model, templates=True)
        self.mw.reset()
        saveGeom(self, "CardLayout")
        return QDialog.reject(self)

    def onHelp(self):
        openHelp("templates")
