# -*- coding: utf-8 -*-

# AwesomeTTS text-to-speech add-on for Anki
# Copyright (C) 2010-Present  Anki AwesomeTTS Development Team
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
Sound tag-stripper dialog
"""

from PyQt5 import QtCore, QtWidgets

from .base import Dialog
from .common import Checkbox, Label, Note

__all__ = ['BrowserStripper']


class BrowserStripper(Dialog):
    """
    Provides a dialog that can be invoked when the user wants to remove
    [sound] tags from a selection of notes in the card browser.
    """

    __slots__ = [
        '_alerts',   # callable for reporting errors and summaries
        '_browser',  # reference to the current Anki browser window
        '_notes',    # list of Note objects selected when window opened
    ]

    def __init__(self, browser, alerts, *args, **kwargs):
        """
        Sets our title and initializes our selected notes.
        """

        self._alerts = alerts
        self._browser = browser
        self._notes = None  # set in show()

        super(BrowserStripper, self).__init__(
            title="Remove Audio from Selected Notes",
            *args, **kwargs
        )

    # UI Construction ########################################################

    def _ui(self):
        """
        Prepares the basic layout structure, including the intro label,
        scroll area, radio buttons, and help/okay/cancel buttons.
        """

        intro = Note()  # see show() for where the text is initialized
        intro.setObjectName('intro')

        scroll = QtWidgets.QScrollArea()
        scroll.setObjectName('scroll')

        layout = super(BrowserStripper, self)._ui()
        layout.addWidget(intro)
        layout.addWidget(scroll)
        layout.addSpacing(self._SPACING)
        layout.addWidget(Label("... and remove the following:"))

        for value, label in [
                (
                    'ours',
                    "only [sound] tags or paths generated by Awesome&TTS",
                ),
                (
                    'theirs',
                    "only [sound] tags &not generated by AwesomeTTS",
                ),
                (
                    'any',
                    "&all [sound] tags, regardless of origin, and paths "
                    "generated by AwesomeTTS",
                ),
        ]:
            radio = QtWidgets.QRadioButton(label)
            radio.setObjectName(value)
            layout.addWidget(radio)

        layout.addWidget(self._ui_buttons())

        return layout

    def _ui_buttons(self):
        """
        Adjust title of the OK button.
        """

        buttons = super(BrowserStripper, self)._ui_buttons()
        buttons.findChild(QtWidgets.QAbstractButton, 'okay').setText("&Remove Now")

        return buttons

    # Events #################################################################

    def show(self, *args, **kwargs):
        """
        Populate the checkbox list of available fields and initialize
        the introduction message, both based on what is selected.
        """

        self._notes = [
            self._browser.mw.col.getNote(note_id)
            for note_id in self._browser.selectedNotes()
        ]

        self.findChild(Note, 'intro').setText(
            "From the %d note%s selected in the Browser, scan the following "
            "fields:" %
            (len(self._notes), "s" if len(self._notes) != 1 else "")
        )

        layout = QtWidgets.QVBoxLayout()
        for field in sorted({field
                             for note in self._notes
                             for field in note.keys()}):
            checkbox = Checkbox(field)
            checkbox.atts_field_name = field
            layout.addWidget(checkbox)

        panel = QtWidgets.QWidget()
        panel.setLayout(layout)

        self.findChild(QtWidgets.QScrollArea, 'scroll').setWidget(panel)

        (
            self.findChild(
                QtWidgets.QRadioButton,
                self._addon.config['last_strip_mode'],
            )
            or self.findChild(QtWidgets.QRadioButton)  # use first if config bad
        ).setChecked(True)

        super(BrowserStripper, self).show(*args, **kwargs)

    def help_request(self):
        """
        Launch the web browser pointed at the subsection of the Browser
        page about stripping sounds.
        """

        self._launch_link('usage/removing')

    def accept(self):
        """
        Iterates over the selected notes and scans the checked fields
        for [sound] tags, stripping the ones requested by the user.
        """

        fields = [
            checkbox.atts_field_name
            for checkbox in self.findChildren(Checkbox)
            if checkbox.isChecked()
        ]

        if not fields:
            self._alerts("You must select at least one field.", self)
            return

        self.setDisabled(True)
        QtCore.QTimer.singleShot(
            100,
            lambda: self._accept_process(fields),
        )

    def _accept_process(self, fields):
        """
        Backend processing for accept(), called after a delay.
        """

        mode = next(
            radio.objectName()
            for radio in self.findChildren(QtWidgets.QRadioButton)
            if radio.isChecked()
        )

        self._browser.mw.checkpoint("AwesomeTTS Sound Removal")

        stat = dict(
            notes=dict(proc=0, upd=0),
            fields=dict(proc=0, upd=0, skip=0),
        )

        for note in self._notes:
            note_updated = False
            stat['notes']['proc'] += 1

            for field in fields:
                try:
                    old_value = note[field]
                    stat['fields']['proc'] += 1
                except KeyError:
                    stat['fields']['skip'] += 1
                    continue

                strips = self._addon.strip.sounds
                new_value = (strips.ours(old_value) if mode == 'ours'
                             else strips.theirs(old_value) if mode == 'theirs'
                             else strips.univ(old_value))

                if old_value == new_value:
                    self._addon.logger.debug("Note %d unchanged for %s\n%s",
                                             note.id, field, old_value)
                else:
                    self._addon.logger.info("Note %d upd for %s\n%s\n%s",
                                            note.id, field, old_value,
                                            new_value)
                    note[field] = new_value.strip()
                    note_updated = True
                    stat['fields']['upd'] += 1

            if note_updated:
                note.flush()
                stat['notes']['upd'] += 1

        messages = [
            "%d %s processed and %d %s updated." % (
                stat['notes']['proc'],
                "note was" if stat['notes']['proc'] == 1 else "notes were",
                stat['notes']['upd'],
                "was" if stat['notes']['upd'] == 1 else "were",
            ),
        ]

        if stat['notes']['proc'] != stat['fields']['proc']:
            messages.append("\nOf %s, %d %s checked and %d %s changed." % (
                "this" if stat['notes']['proc'] == 1 else "these",
                stat['fields']['proc'],
                "field was" if stat['fields']['proc'] == 1 else "fields were",
                stat['fields']['upd'],
                "was" if stat['fields']['upd'] == 1 else "were",
            ))

        if stat['fields']['skip'] == 1:
            messages.append("\n1 field was not present on one of the notes.")
        elif stat['fields']['skip'] > 1:
            messages.append("\n%d fields were not present on some notes." %
                            stat['fields']['skip'])

        if stat['notes']['upd']:
            messages.append("\n\n"
                            "To purge sounds from your Anki collection that "
                            'are no longer used in any notes, select "Check '
                            'Media" from the Anki "Tools" menu in the main '
                            "window.")

        self._browser.model.reset()
        self._addon.config['last_strip_mode'] = mode
        self.setDisabled(False)
        self._notes = None

        super(BrowserStripper, self).accept()

        # this alert is done by way of a singleShot() callback to avoid random
        # crashes on Mac OS X, which happen <5% of the time if called directly
        QtCore.QTimer.singleShot(
            0,
            lambda: self._alerts("".join(messages), self._browser),
        )