# Copyright: Ankitects Pty Ltd and contributors
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
from operator import itemgetter
from typing import Any, Dict

from PyQt5.QtWidgets import QLineEdit

import aqt
from anki.consts import NEW_CARDS_RANDOM
from anki.lang import without_unicode_isolation
from aqt import gui_hooks
from aqt.qt import *
from aqt.utils import (
    TR,
    askUser,
    getOnlyText,
    openHelp,
    restoreGeom,
    saveGeom,
    showInfo,
    showWarning,
    tooltip,
    tr,
)


class DeckConf(QDialog):
    def __init__(self, mw: aqt.AnkiQt, deck: Dict):
        QDialog.__init__(self, mw)
        self.mw = mw
        self.deck = deck
        self.childDids = [d[1] for d in self.mw.col.decks.children(self.deck["id"])]
        self._origNewOrder = None
        self.form = aqt.forms.dconf.Ui_Dialog()
        self.form.setupUi(self)
        gui_hooks.deck_conf_did_setup_ui_form(self)
        self.mw.checkpoint(tr(TR.ACTIONS_OPTIONS))
        self.setupCombos()
        self.setupConfs()
        self.setWindowModality(Qt.WindowModal)
        qconnect(self.form.buttonBox.helpRequested, lambda: openHelp("deck-options"))
        qconnect(self.form.confOpts.clicked, self.confOpts)
        qconnect(
            self.form.buttonBox.button(QDialogButtonBox.RestoreDefaults).clicked,
            self.onRestore,
        )
        self.setWindowTitle(
            without_unicode_isolation(tr(TR.ACTIONS_OPTIONS_FOR, val=self.deck["name"]))
        )
        # qt doesn't size properly with altered fonts otherwise
        restoreGeom(self, "deckconf", adjustSize=True)
        gui_hooks.deck_conf_will_show(self)
        self.show()
        self.exec_()
        saveGeom(self, "deckconf")

    def setupCombos(self):
        import anki.consts as cs

        f = self.form
        f.newOrder.addItems(list(cs.newCardOrderLabels(self.mw.col).values()))
        qconnect(f.newOrder.currentIndexChanged, self.onNewOrderChanged)

    # Conf list
    ######################################################################

    def setupConfs(self):
        qconnect(self.form.dconf.currentIndexChanged, self.onConfChange)
        self.conf = None
        self.loadConfs()

    def loadConfs(self):
        current = self.deck["conf"]
        self.confList = self.mw.col.decks.allConf()
        self.confList.sort(key=itemgetter("name"))
        startOn = 0
        self.ignoreConfChange = True
        self.form.dconf.clear()
        for idx, conf in enumerate(self.confList):
            self.form.dconf.addItem(conf["name"])
            if str(conf["id"]) == str(current):
                startOn = idx
        self.ignoreConfChange = False
        self.form.dconf.setCurrentIndex(startOn)
        if self._origNewOrder is None:
            self._origNewOrder = self.confList[startOn]["new"]["order"]
        self.onConfChange(startOn)

    def confOpts(self):
        m = QMenu(self.mw)
        a = m.addAction(tr(TR.ACTIONS_ADD))
        qconnect(a.triggered, self.addGroup)
        a = m.addAction(tr(TR.ACTIONS_DELETE))
        qconnect(a.triggered, self.remGroup)
        a = m.addAction(tr(TR.ACTIONS_RENAME))
        qconnect(a.triggered, self.renameGroup)
        a = m.addAction(tr(TR.SCHEDULING_SET_FOR_ALL_SUBDECKS))
        qconnect(a.triggered, self.setChildren)
        if not self.childDids:
            a.setEnabled(False)
        m.exec_(QCursor.pos())

    def onConfChange(self, idx):
        if self.ignoreConfChange:
            return
        if self.conf:
            self.saveConf()
        conf = self.confList[idx]
        self.deck["conf"] = conf["id"]
        self.mw.col.decks.save(self.deck)
        self.loadConf()
        cnt = len(self.mw.col.decks.didsForConf(conf))
        if cnt > 1:
            txt = tr(TR.SCHEDULING_YOUR_CHANGES_WILL_AFFECT_MULTIPLE_DECKS)
        else:
            txt = ""
        self.form.count.setText(txt)

    def addGroup(self) -> None:
        name = getOnlyText(tr(TR.SCHEDULING_NEW_OPTIONS_GROUP_NAME))
        if not name:
            return

        # first, save currently entered data to current conf
        self.saveConf()
        # then clone the conf
        id = self.mw.col.decks.add_config_returning_id(name, clone_from=self.conf)
        gui_hooks.deck_conf_did_add_config(self, self.deck, self.conf, name, id)
        # set the deck to the new conf
        self.deck["conf"] = id
        # then reload the conf list
        self.loadConfs()

    def remGroup(self) -> None:
        if int(self.conf["id"]) == 1:
            showInfo(tr(TR.SCHEDULING_THE_DEFAULT_CONFIGURATION_CANT_BE_REMOVED), self)
        else:
            gui_hooks.deck_conf_will_remove_config(self, self.deck, self.conf)
            self.mw.col.modSchema(check=True)
            self.mw.col.decks.remove_config(self.conf["id"])
            self.conf = None
            self.deck["conf"] = 1
            self.loadConfs()

    def renameGroup(self) -> None:
        old = self.conf["name"]
        name = getOnlyText(tr(TR.ACTIONS_NEW_NAME), default=old)
        if not name or name == old:
            return

        gui_hooks.deck_conf_will_rename_config(self, self.deck, self.conf, name)
        self.conf["name"] = name
        self.saveConf()
        self.loadConfs()

    def setChildren(self):
        if not askUser(tr(TR.SCHEDULING_SET_ALL_DECKS_BELOW_TO, val=self.deck["name"])):
            return
        for did in self.childDids:
            deck = self.mw.col.decks.get(did)
            if deck["dyn"]:
                continue
            deck["conf"] = self.deck["conf"]
            self.mw.col.decks.save(deck)
        tooltip(tr(TR.SCHEDULING_DECK_UPDATED, count=len(self.childDids)))

    # Loading
    ##################################################

    def listToUser(self, l):
        def num_to_user(n: Union[int, float]):
            if n == round(n):
                return str(int(n))
            else:
                return str(n)

        return " ".join(map(num_to_user, l))

    def parentLimText(self, type="new"):
        # top level?
        if "::" not in self.deck["name"]:
            return ""
        lim = -1
        for d in self.mw.col.decks.parents(self.deck["id"]):
            c = self.mw.col.decks.confForDid(d["id"])
            x = c[type]["perDay"]
            if lim == -1:
                lim = x
            else:
                lim = min(x, lim)
        return tr(TR.SCHEDULING_PARENT_LIMIT, val=lim)

    def loadConf(self):
        self.conf = self.mw.col.decks.confForDid(self.deck["id"])
        # new
        c = self.conf["new"]
        f = self.form
        f.lrnSteps.setText(self.listToUser(c["delays"]))
        f.lrnGradInt.setValue(c["ints"][0])
        f.lrnEasyInt.setValue(c["ints"][1])
        f.lrnFactor.setValue(c["initialFactor"] / 10.0)
        f.newOrder.setCurrentIndex(c["order"])
        f.newPerDay.setValue(c["perDay"])
        f.bury.setChecked(c.get("bury", True))
        f.newplim.setText(self.parentLimText("new"))
        # rev
        c = self.conf["rev"]
        f.revPerDay.setValue(c["perDay"])
        f.easyBonus.setValue(c["ease4"] * 100)
        f.fi1.setValue(c["ivlFct"] * 100)
        f.maxIvl.setValue(c["maxIvl"])
        f.revplim.setText(self.parentLimText("rev"))
        f.buryRev.setChecked(c.get("bury", True))
        f.hardFactor.setValue(int(c.get("hardFactor", 1.2) * 100))
        if self.mw.col.schedVer() == 1:
            f.hardFactor.setVisible(False)
            f.hardFactorLabel.setVisible(False)
        # lapse
        c = self.conf["lapse"]
        f.lapSteps.setText(self.listToUser(c["delays"]))
        f.lapMult.setValue(c["mult"] * 100)
        f.lapMinInt.setValue(c["minInt"])
        f.leechThreshold.setValue(c["leechFails"])
        f.leechAction.setCurrentIndex(c["leechAction"])
        # general
        c = self.conf
        f.maxTaken.setValue(c["maxTaken"])
        f.showTimer.setChecked(c.get("timer", 0))
        f.autoplaySounds.setChecked(c["autoplay"])
        f.replayQuestion.setChecked(c.get("replayq", True))
        # description
        f.desc.setPlainText(self.deck["desc"])
        gui_hooks.deck_conf_did_load_config(self, self.deck, self.conf)

    def onRestore(self):
        self.mw.progress.start()
        self.mw.col.decks.restoreToDefault(self.conf)
        self.mw.progress.finish()
        self.loadConf()

    # New order
    ##################################################

    def onNewOrderChanged(self, new):
        old = self.conf["new"]["order"]
        if old == new:
            return
        self.conf["new"]["order"] = new
        self.mw.progress.start()
        self.mw.col.sched.resortConf(self.conf)
        self.mw.progress.finish()

    # Saving
    ##################################################

    def updateList(self, conf: Any, key: str, w: QLineEdit, minSize: int = 1) -> None:
        items = str(w.text()).split(" ")
        ret = []
        for item in items:
            if not item:
                continue
            try:
                i = float(item)
                assert i > 0
                if i == int(i):
                    i = int(i)
                ret.append(i)
            except:
                # invalid, don't update
                showWarning(tr(TR.SCHEDULING_STEPS_MUST_BE_NUMBERS))
                return
        if len(ret) < minSize:
            showWarning(tr(TR.SCHEDULING_AT_LEAST_ONE_STEP_IS_REQUIRED))
            return
        conf[key] = ret

    def saveConf(self):
        # new
        c = self.conf["new"]
        f = self.form
        self.updateList(c, "delays", f.lrnSteps)
        c["ints"][0] = f.lrnGradInt.value()
        c["ints"][1] = f.lrnEasyInt.value()
        c["initialFactor"] = f.lrnFactor.value() * 10
        c["order"] = f.newOrder.currentIndex()
        c["perDay"] = f.newPerDay.value()
        c["bury"] = f.bury.isChecked()
        if self._origNewOrder != c["order"]:
            # order of current deck has changed, so have to resort
            if c["order"] == NEW_CARDS_RANDOM:
                self.mw.col.sched.randomizeCards(self.deck["id"])
            else:
                self.mw.col.sched.orderCards(self.deck["id"])
        # rev
        c = self.conf["rev"]
        c["perDay"] = f.revPerDay.value()
        c["ease4"] = f.easyBonus.value() / 100.0
        c["ivlFct"] = f.fi1.value() / 100.0
        c["maxIvl"] = f.maxIvl.value()
        c["bury"] = f.buryRev.isChecked()
        c["hardFactor"] = f.hardFactor.value() / 100.0
        # lapse
        c = self.conf["lapse"]
        self.updateList(c, "delays", f.lapSteps, minSize=0)
        c["mult"] = f.lapMult.value() / 100.0
        c["minInt"] = f.lapMinInt.value()
        c["leechFails"] = f.leechThreshold.value()
        c["leechAction"] = f.leechAction.currentIndex()
        # general
        c = self.conf
        c["maxTaken"] = f.maxTaken.value()
        c["timer"] = f.showTimer.isChecked() and 1 or 0
        c["autoplay"] = f.autoplaySounds.isChecked()
        c["replayq"] = f.replayQuestion.isChecked()
        # description
        self.deck["desc"] = f.desc.toPlainText()
        gui_hooks.deck_conf_will_save_config(self, self.deck, self.conf)
        self.mw.col.decks.save(self.deck)
        self.mw.col.decks.save(self.conf)

    def reject(self):
        self.accept()

    def accept(self):
        self.saveConf()
        self.mw.reset()
        QDialog.accept(self)
