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

try :
    from PyQt5.Qt import QDialog, QCheckBox, Qt, QDialogButtonBox, QLabel, QGridLayout, QPushButton, QDockWidget, QWidget, QTextEdit, \
    QIcon, QHBoxLayout, QToolButton, QSize, QTableWidget, QVBoxLayout, QTableWidgetItem, QFont, QAbstractScrollArea, QCursor, \
    QPlainTextEdit, QFontMetrics, QApplication, QComboBox, QPixmap, QStackedLayout, QStyledItemDelegate, QColor, \
    QTextDocument, QStyleOptionViewItem, QStyle, QAbstractTextDocumentLayout, QPalette, QRectF, QPen, QProxyStyle, QBrush, QTextCharFormat

    from PyQt5.QtCore import pyqtSignal

except ImportError:
    from PyQt4.Qt import QDialog, QCheckBox, Qt, QDialogButtonBox, QLabel, QGridLayout, QPushButton, QDockWidget, QWidget, QTextEdit, \
    QIcon, QHBoxLayout, QToolButton, QSize, QTableWidget, QVBoxLayout, QTableWidgetItem, QFont, QAbstractScrollArea, QCursor, \
    QPlainTextEdit, QFontMetrics, QApplication, QComboBox, QPixmap, QStackedLayout, QStyledItemDelegate, QColor, \
    QTextDocument, QStyleOptionViewItem, QStyle, QAbstractTextDocumentLayout, QPalette, QRectF, QPen, QProxyStyle, QBrush, QTextCharFormat

    from PyQt4.QtCore import pyqtSignal

import re, functools, regex, unicodedata, textwrap, time
from calibre import prepare_string_for_xml
from calibre.utils.config import JSONConfig
from calibre.gui2 import error_dialog, info_dialog, gprefs
from calibre.gui2.tweak_book import editors, dictionaries, editor_name
from calibre.gui2.tweak_book.widgets import BusyCursor, Dialog
from calibre_plugins.typex.utils import polish_text, get_icon, comment_reader, log, anon, load_resources
from calibre_plugins.typex import PLUGIN_VERSION

try:
    load_translations()
except NameError:
    pass
prefs = JSONConfig('plugins/Typex.json')


class RegexStrainer(QDockWidget):

    typexclosed = pyqtSignal()

    def __init__(self, parent, searches, count_map):
        self.count_map = count_map
        self.parent = parent
        QDockWidget.__init__(self)
        self.setAttribute(Qt.WA_DeleteOnClose, on=True)

        self.setWindowTitle('Typex {}'.format(PLUGIN_VERSION))
        self.setAllowedAreas(Qt.AllDockWidgetAreas)
        self.setObjectName('typex-view')
        self.busy = BusyWidget(self)
        self.widg = StrainerWidget(self, searches, count_map)

        self.setWidget(self.widg)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setFocus()
        self.dockLocationChanged.connect(self.location_changed)
        self.topLevelChanged.connect(self.floating_changed)

        floating = prefs.get('floating', True)
        da = prefs.get('dockarea', Qt.TopDockWidgetArea)
        self.parent.boss.gui.addDockWidget(da, self)
        self.setFloating(floating)

        for widget in self.parent.gui.children():
            if isinstance(widget, QDockWidget) and widget.objectName() in ('epubcheck-dock', 'check-book-dock'):
                widget.close()

        geometry = prefs.get('geometry', None)
        if geometry is not None:
            self.restoreGeometry(geometry)

    def location_changed(self, da):
        prefs['dockarea'] = da

    def floating_changed(self, fl):
        prefs['floating'] = fl

    def get_keys(self):
        return self.widg.table.get_keys()

    def closeEvent(self, *args, **kwargs):
        self.typexclosed.emit()
        prefs['geometry'] = bytearray(self.saveGeometry())
        return QDockWidget.closeEvent(self, *args, **kwargs)

    def keyPressEvent(self, event):
        return self.widg.keyPressEvent(event)


class StrainerWidget(QWidget):

    applysignal = pyqtSignal()

    def __init__(self, parent, searches, count_map):
        QWidget.__init__(self, parent)
        self.searches = searches
        self.parent = parent
        self.boss = self.parent.parent.boss
        self.setFocusPolicy(Qt.StrongFocus)
        l = QVBoxLayout()
        self.setLayout(l)
        first = QHBoxLayout()
        first.addStretch()
        self.selectbutton = QPushButton(_('Tout sélectionner'), self)
        self.selectbutton.resize(QSize(80, 40))
        self.selectbutton.setToolTip(_('Sélectionne/Déselectionne toutes les règles positives de cet écran'))
        self.sb_state = True
        first.addWidget(self.selectbutton, alignment=Qt.AlignHCenter)
        self.jumpcb = HTMLComboBox(self)
        for i in range(0, len(searches)) :
            self.jumpcb.addItem('<h3>{}</h3>'.format(searches[i][0]['name'].replace('<==', '').replace('==>', '').strip()))
        first.addWidget(self.jumpcb)
        self.refreshbutton = QPushButton(_('Rafraichir'), self)
        self.refreshbutton.resize(QSize(80, 40))
        first.addWidget(self.refreshbutton, alignment=Qt.AlignHCenter)
        first.addStretch()
        l.addLayout(first)

        self.stacks = center = QStackedLayout(self)
        self.busy = BusyWidget(self)
        center.addWidget(self.busy)
        tbcont = QWidget(self)
        hb = QHBoxLayout()
        hb.addStretch()
        self.table = RegTable(self, searches)
        hb.addWidget(self.table)
        hb.addStretch()
        tbcont.setLayout(hb)
        center.addWidget(tbcont)
        l.addLayout(center)

        last = QHBoxLayout()
        self.previousbutton = QPushButton(_('Précédent'), self)
        applybutton = QPushButton(_('&Appliquer'), self)
        applybutton.setToolTip(_('Appliquer les sélections de l\'écran EN COURS'))
        quitbutton = QPushButton(_('&Quitter Typex'), self)
        self.nextbutton = QPushButton(_('Suivant'), self)
        reportbutton = QPushButton(_('Comptage global'), self)
        bb = QDialogButtonBox(Qt.Horizontal)
        bb.addButton(self.previousbutton, QDialogButtonBox.ActionRole)
        bb.addButton(applybutton, QDialogButtonBox.ActionRole)
        bb.addButton(self.nextbutton, QDialogButtonBox.ActionRole)
        bb.addButton(quitbutton, QDialogButtonBox.ActionRole)
        bb.addButton(reportbutton, QDialogButtonBox.ActionRole)
        last.addWidget(bb, alignment=Qt.AlignHCenter)
        applybutton.clicked.connect(self.apply_clicked)
        quitbutton.clicked.connect(self._quit)
        reportbutton.clicked.connect(self.do_report)
        self.previousbutton.clicked.connect(self.do_prev)
        self.refreshbutton.clicked.connect(self.refresh)
        self.selectbutton.clicked.connect(self.toggle_select)
        self.nextbutton.clicked.connect(self.do_next)
        self.jumpcb.currentIndexChanged.connect(self.jump)
        l.addLayout(last)

        self.i = 0
        self.refresh()
        self.previousbutton.setDisabled(True)
        self.setFocus()

    @property
    def title(self):
        return self.searches[self.i][0]['name'].replace('<==', '').replace('==>', '').strip()

    def jump(self, index):
        self.previousbutton.setDisabled(False)
        self.nextbutton.setDisabled(False)
        self.i = index
        if self.i == len(self.searches) - 1:
            self.nextbutton.setDisabled(True)
        if self.i == 0:
            self.previousbutton.setDisabled(True)
        return self.refresh()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Q:
            event.accept()
            self._quit()
        elif event.key() == Qt.Key_Left:
            event.accept()
            self.do_prev()
        elif event.key() == Qt.Key_Right:
            event.accept()
            self.do_next()
        elif event.key() == Qt.Key_A:
            event.accept()
            self.apply_clicked()
        return QWidget.keyPressEvent(self, event)

    def do_next(self):
        self.previousbutton.setDisabled(False)
        if self.i < len(self.searches) - 1:
            self.i += 1
            if self.i == len(self.searches) - 1:
                self.nextbutton.setDisabled(True)
        self.jumpcb.setCurrentIndex(self.i)
        return self.refresh()

    def toggle_select(self):
        checked = Qt.Checked if self.sb_state else Qt.Unchecked
        self.sb_state = not self.sb_state
        text = _('Tout sélectionner') if self.sb_state else _('Tout déselectionner')
        self.selectbutton.setText(text)
        return self.table.select(checked)

    def refresh(self):
        self.stacks.setCurrentIndex(0)
        self.busy.setVisible(True)
        QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
        QApplication.processEvents()
        self.parent.parent.do_count(self.i)
        self.table.update_prefs()
        self.refreshbutton.setStyleSheet("color:auto;")
        self.stacks.setCurrentIndex(1)
        QApplication.restoreOverrideCursor()
        self.editor_changed()
        self.boss.gui.central.current_editor_changed.connect(self.editor_changed)
        return self.table.populate(self.i)

    def do_prev(self):
        self.nextbutton.setDisabled(False)
        if self.i > 0 :
            self.i -= 1
            if self.i == 0:
                self.previousbutton.setDisabled(True)
        self.jumpcb.setCurrentIndex(self.i)
        return self.refresh()

    def apply_clicked(self):
        self.applysignal.emit()

    def do_report(self):
        from calibre.gui2.dialogs.message_box import ViewLog
        title = _('Comptage global actuel')
        text = ''
        for k, v in self.parent.parent.global_count.items():
            text += k.strip().ljust(110, '.') + str(v[0]).strip() + ' en {:.1f}ms'.format(v[1]).strip() + '\n'
            # text += '{0:<120}{1:<5}\n'.format(k, str(v))
        ViewLog(title, text).exec_()

    def editor_changed(self):
        if self.boss.gui.central.current_editor is not None:
            ed = self.boss.gui.central.current_editor.editor
            ed.textChanged.connect(self.save_data)

    def save_data(self):
        name = self.boss.currently_editing
        raw = editors[name].get_raw_data()  # ed.data
        self.parent.parent.cache[name] = raw
        self.parent.parent.current_container.open(name, 'wb').write(raw.encode('utf-8'))

    def _quit(self):
        self.table.update_prefs()
        self.parent.close()


class TableWidgetItemWithIcon(QTableWidgetItem):

    def __init__(self, icon, text):
        QTableWidgetItem.__init__(self, icon, text, QTableWidgetItem.UserType)
        self.setFlags(Qt.ItemIsEnabled)


class RegTable(QTableWidget):

    def __init__(self, parent, searches):
        QTableWidget.__init__(self, parent)
        self.parent = parent
        self.searches = searches
        self.keys = []
        self.step = []
        self.link = {}
        self.com = comment_reader()
        self.setColumnCount(4)
        self.verticalHeader().hide()
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setShowGrid(False)
        self.setStyleSheet('''QTableWidgetItem {font-weight: bold;}''')

        self.f = f = QFont()
        self.f.setWeight(75)
        cb0 = QTableWidgetItem('')
        cb1 = QTableWidgetItem(_('Expression régulière'))
        self.oldfont = cb1.font()
        cb1.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
        cb1.setCheckState(Qt.Unchecked)
        cb1.setFont(f)
        label2 = QTableWidgetItem(_('Seront effectués :'))
        label2.setFont(f)
        label3 = QTableWidgetItem(_('Pas à pas'))
        label3.setFont(f)
        self.setHorizontalHeaderItem(0, cb0)
        self.setHorizontalHeaderItem(1, cb1)
        self.setHorizontalHeaderItem(2, label2)
        self.setHorizontalHeaderItem(3, label3)

    def populate(self, i):

        reglist = self.searches[i]
        count_map = self.parent.parent.parent.count_map
        self.setRowCount(len(reglist) - 1)

        self.spacer = ''
        sheet = "margin:auto"
        for row, reg in enumerate([reg for reg in reglist if re.search(r'(\[\w*?\])', reg['name'])]) :
            key, linkkey, keyitems = self.extract_keys(reg['name'])
            if linkkey :
                self.link[key.group()] = keyitems
                self.spacer = ''
                sheet = "margin:auto"

            self.setCellWidget(row, 0, QCheckBox())
            self.cellWidget(row, 0).setLayoutDirection(Qt.RightToLeft)
            self.cellWidget(row, 0).setStyleSheet(sheet)
            self.cellWidget(row, 0).stateChanged.connect(functools.partial(self.item_was_clicked, row, 0))
            if key.group() in self.com :
                icon = get_icon('resources/{}.png'.format(self.com[key.group()][0]))
                self.setItem(row, 1, TableWidgetItemWithIcon(icon, self.spacer + reg['name']))
                tt = self.com[key.group()][1]
                tt = self.parent.parent.parent.current_container.decode(tt)
                self.item(row, 1).setToolTip(tt)
            else:
                self.setItem(row, 1, QTableWidgetItem(self.spacer + reg['name']))
            self.item(row, 1).setFlags(Qt.ItemIsEnabled)
            self.item(row, 1).setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter)

            itemlabel = _('{0}{1} remplacement{2}').format(self.spacer, count_map[reg['name']][0], _('s') if count_map[reg['name']][0] > 1 else '')
            self.setItem(row, 2, QTableWidgetItem(itemlabel))
            self.item(row, 2).setTextAlignment(Qt.AlignVCenter)
            self.item(row, 2).setFlags(Qt.ItemIsEnabled)
            self.item(row, 2).setToolTip(_('en {:.1f}ms').format(count_map[reg['name']][1]))

            self.setCellWidget(row, 3, QCheckBox())
            self.cellWidget(row, 3).setCheckState(Qt.Unchecked)
            self.cellWidget(row, 3).setStyleSheet("margin:auto")
            self.cellWidget(row, 3).stateChanged.connect(functools.partial(self.item_was_clicked, row, 3))

            self.cellWidget(row, 0).setChecked(prefs.get(key.group(0)[0], False))
            self.cellWidget(row, 3).setChecked(prefs.get(key.group(0)[1], False))
            if count_map[reg['name']][0] == 0 and key.group(0) not in list(self.link.keys()):
                self.cellWidget(row, 0).setCheckState(Qt.Unchecked)
            if count_map[reg['name']][0] != 0 or key.group(0) in list(self.link.keys()):
                self.item(row, 1).setFont(self.f)
                self.item(row, 2).setFont(self.f)

            if _('[S]') in reg['name'] and self.cellWidget(row, 0).checkState == Qt.Checked and count_map[reg['name']] != 0:
                self.cellWidget(row, 3).setCheckState(Qt.Checked)
            if _('[A]') in reg['name'] :
                self.cellWidget(row, 3).setDisabled(True)
            if _('[F1]') in reg['name'] :
                self.cellWidget(row, 3).setToolTip(_('''La règle ci-dessous active une fonction avancée :
            Dans la télécommande du pas à pas, le libellé "Voir le remplacement (survol)" devient rouge lorsque le remplacement 
            est différent de la chaîne remplacée. 
            On visualise le remplacement proposé en survolant le libellé avec la souris.'''))
            if linkkey:
                self.spacer = ' ' * 5
                sheet = "margin-left:90%; margin-right:10%;"
            for k, v in list(self.link.items()):
                if key.group(0) in v :
                    self.cellWidget(row, 0).setCheckState(Qt.Unchecked)
                    self.cellWidget(row, 0).setDisabled(True)
                    self.cellWidget(row, 3).setDisabled(True)
                    self.item(row, 1).setFont(self.oldfont)
                    self.item(row, 2).setFont(self.oldfont)

        self.resizeColumnsToContents()
        w, h = self.width(), self.height()
        self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
        self.viewport().resize(w, h)
        self.updateGeometry()
        self.setFocus()

    def extract_keys(self, name):
        key = re.search(r'(\[\w*?\])', name)
        linkkey = re.search(r'(?:\[\w*?\])(\[=.*?\])', name)
        if linkkey:
            listitems = linkkey.group(1)[2:-1].split('+')
            keyitems = ['[{}]'.format(li) for li in listitems]
            return key, linkkey, keyitems
        return key, None, []

    def select(self, checked):
        for row in range(0, self.rowCount()) :
            if  not self.item(row, 2).text().lstrip().startswith('0') and '___' not in self.cellWidget(row, 0).text():
                self.cellWidget(row, 0).setCheckState(checked)

    def item_was_clicked(self, row, column):
        __, linkkey, keyitems = self.extract_keys(self.item(row, 1).text())
        if column == 0:
            if not self.cellWidget(row, 0).isChecked() :
                self.cellWidget(row, 3).setCheckState(Qt.Unchecked)
            if self.cellWidget(row, 0).isChecked() and _('[S]') in self.item(row, 1).text():
                self.cellWidget(row, 3).setCheckState(Qt.Checked)
        elif column == 3:
            self.cellWidget(row, 0).setCheckState(self.cellWidget(row, 3).checkState())
        if linkkey:
            for r in range(0, self.rowCount()):
                try:
                    for k in [x for x in keyitems if x in self.item(r, 1).text()]:
                        self.cellWidget(r, column).setCheckState(self.cellWidget(row, column).checkState() \
                                if not self.item(r, 2).text().lstrip().startswith('0') else Qt.Unchecked)
                        self.cellWidget(r, column).setDisabled(True)
                except AttributeError:
                    # the table was not totally populated
                    pass
        if self.item(row, 2).text().startswith('0') and not linkkey:
            self.cellWidget(row, column).setCheckState(Qt.Unchecked)
        if '[A]' in self.item(row, 1).text():
                self.cellWidget(row, 3).setCheckState(Qt.Unchecked)

    def get_keys(self):
        selkey = []
        self.step = []
        for row in range(0, self.rowCount()):
            key, linkkey, __ = self.extract_keys(self.item(row, 1).text())
            if self.cellWidget(row, 0).isChecked() and not linkkey  and not self.cellWidget(row, 3).isChecked() :
                selkey.append(key.group(0))
            if self.cellWidget(row, 3).isChecked() and not linkkey :
                self.step.append(key.group(0))
        return selkey, self.parent.title, self.step

    def keyPressEvent(self, event):
        return self.parent.keyPressEvent(event)

    def update_prefs(self):
        for row in range(0, self.rowCount()):
            key, linkkey, keyitems = self.extract_keys(self.item(row, 1).text())
            if key.group(0) not in keyitems and not linkkey:
                prefs[key.group(0)] = self.cellWidget(row, 0).checkState() == Qt.Checked, self.cellWidget(row, 3).checkState() == Qt.Checked or False


class ReplaceCount(QDialog):

    def __init__(self, datas, parent):
        QDialog.__init__(self, parent)
        forcediff = False
        self.setWindowTitle(_('Typex a travaillé :'))
        l = QGridLayout(self)
        self.setLayout(l)
        self.setContentsMargins(3, 3, 3, 3)
        l.setContentsMargins(0, 2, 0, 2)
        i = 0
        for key, val in list(datas.items()):
            item = QLabel(_('{} remplacements pour  {}').format(val, key))
            if '[A]' in key :
                forcediff = True
            l.addWidget(item, i, 0, 1, 1, Qt.AlignLeft)
            i += 1

        hl = QHBoxLayout()
        b = QPushButton(QIcon(I('ok.png')), _('OK'), self)
        b.clicked.connect(self.accept)
        b.setDisabled(forcediff)
        hl.addWidget(b)

        b = QPushButton(QIcon(I('diff.png')), _('Voir les différences'), self)
        b.clicked.connect(self.reject)
        hl.addWidget(b)

        l.addLayout(hl, i + 2, 0, 1, 2)


class RemoteCtrl(QDockWidget):

    remoteclosed = pyqtSignal()

    def __init__(self, parent, searches, counts):
        QDockWidget.__init__(self, flags=Qt.Window | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.CustomizeWindowHint)
        self.parent = parent
        self.searches = searches
        self.counts = counts
        self.boss = self.parent.boss
        self.json_func = self.parent.json_func
        self.setWindowTitle(_('Télécommande'))
        self.setFeatures(QDockWidget.AllDockWidgetFeatures)
        self.setAllowedAreas(Qt.AllDockWidgetAreas)
        self.setObjectName('typex-remote')
        self.setAttribute(Qt.WA_DeleteOnClose, on=True)
        self.setStyleSheet('QToolTip {font-weight:bold;font-style:normal;font-size:11px}')

        for widget in self.parent.gui.children():
            if  widget.objectName() == 'typex-view':
                widget.hide()
        self.parent.gui.addDockWidget(Qt.LeftDockWidgetArea, self)
        # self.parent.boss.add_savepoint(_('Avant pas à pas'))

        rw = QWidget()
        remotegrid = QGridLayout(self)
        self.setWidget(rw)
        self.bprev = QToolButton(self)
        self.bprev.setIcon(QIcon(I('previous.png')))
        self.bprev.setIconSize(QSize(32, 32))
        remotegrid.addWidget(self.bprev, 0, 0, Qt.AlignHCenter)
        self.applybutton = QPushButton(_('Appliquer'), self)
        remotegrid.addWidget(self.applybutton, 0, 1)
        self.applynextbutton = QPushButton(_('Appliquer\net trouver'), self)
        remotegrid.addWidget(self.applynextbutton, 1, 1)
        self.bnext = QToolButton(self)
        self.bnext.setIcon(QIcon(I('next.png')))
        self.bnext.setIconSize(QSize(32, 32))
        remotegrid.addWidget(self.bnext, 0, 2, Qt.AlignHCenter)
        self.applyallbutton = QPushButton(_('Appliquer sur le reste'), self)
        self.applyallbutton.setToolTip(_('''Appliquer les changements restants uniquement pour la règle en cours,\n
             le pas à pas continuera avec la règle suivante'''))
        remotegrid.addWidget(self.applyallbutton, 1, 0)
        self.quit_onebutton = QPushButton(_("Quitter cette règle"), self)
        self.quit_onebutton.setToolTip(_('Quitte cette règle en l\'état et passe à la suivante'))
        remotegrid.addWidget(self.quit_onebutton, 2, 0)
        self.quitbutton = QPushButton(_("Quitter en l'état"), self)
        self.quitbutton.setToolTip(_('Prend en compte les modifications déjà faites et revient à l\'interface principale Typex'))
        remotegrid.addWidget(self.quitbutton, 2, 1)
        self.cancelbutton = QPushButton(_("Annuler modifs\net quitter"), self)
        remotegrid.addWidget(self.cancelbutton, 1, 2)
        self.labelreg = QLabel()
        remotegrid.addWidget(self.labelreg, 3, 0, 1, 3, Qt.AlignLeft)
        self.poslabel = QLabel(_("<b>Voir le<br/>remplacement<br/>(survol)</b>"))
        remotegrid.addWidget(self.poslabel, 4, 2, 1, 2, Qt.AlignLeft)
        self.curlabel = QLabel()
        remotegrid.addWidget(self.curlabel, 4, 0, 1, 2, Qt.AlignLeft)

        self.applybutton.clicked.connect(self.apply_clicked)
        self.applynextbutton.clicked.connect(self.applynext_clicked)
        self.applyallbutton.clicked.connect(self.apply_all_clicked)
        self.quit_onebutton.clicked.connect(self.quit_one)
        self.quitbutton.clicked.connect(self.quit_clicked)
        self.cancelbutton.clicked.connect(self.cancel_clicked)

        rw.setLayout(remotegrid)
        rw.adjustSize()

        self.i = self.f = self.pos = 0
        self.file_list = self.get_file_list()
        if not len(self.file_list):
            return self.quit_clicked()
        self.found = True
        self.newend = None
        self.res = 1
        self.find_step()
        self.bprev.setDisabled(True)
        self.bprev.clicked.connect(self.previous_click_handler)
        self.bnext.clicked.connect(self.next_click_handler)

    def next_click_handler(self):
        try:
            self.editor.textChanged.disconnect(self.save_data)
        except TypeError:
            pass
        self.bprev.setDisabled(False)
        self.applybutton.setDisabled(False)
        self.applynextbutton.setDisabled(False)
        self.applyallbutton.setDisabled(False)
        self.curlabel.setText('')
        if self.found:
            # self.pos = self.match.end() if self.match else 0
            self.pos = self.newend or 0
            return self.find_step()
        else :
            if self.f < len(self.file_list) - 1:
                self.f += 1
                self.pos = 0
                return self.find_step()
            if self.i < len(self.searches) - 1:
                self.i += 1
                self.res = 1
                self.file_list = self.get_file_list()
                self.f = 0
                self.pos = 0
                return self.find_step()
            self.bnext.setDisabled(True)
            self.quit_onebutton.setDisabled(True)
            self.applybutton.setDisabled(True)
            self.applynextbutton.setDisabled(True)
            self.applyallbutton.setDisabled(True)
            self.res -= 1
            self.match = None
            self.curlabel.setText(_("Dernier résultat atteint"))
            self.found = True
            return True

    def previous_click_handler(self):
        try:
            self.editor.textChanged.disconnect(self.save_data)
        except TypeError:
            pass
        self.bnext.setDisabled(False)
        self.quit_onebutton.setDisabled(False)
        self.applybutton.setDisabled(False)
        self.applynextbutton.setDisabled(False)
        self.applyallbutton.setDisabled(False)
        self.curlabel.setText('')
        if self.found:
            self.pos = self.match.start() if self.match else None
            return self.find_step(reverse=True)
        else :
            if self.f > 0:
                self.f -= 1
                self.pos = None
                return self.find_step(reverse=True)
            if self.i > 0 :
                self.i -= 1
                self.file_list = self.get_file_list()
                self.f = len(self.file_list) - 1
                self.pos = None
                self.res = self.counts[self.i][0]
                return self.find_step(reverse=True)
            self.bprev.setDisabled(True)
            self.quit_onebutton.setDisabled(True)
            self.applybutton.setDisabled(True)
            self.applynextbutton.setDisabled(True)
            self.applyallbutton.setDisabled(True)
            self.res = 0
            self.match = None
            self.curlabel.setText(_("Premier résultat atteint"))
            self.found = True
            return False

    def find_step(self, reverse=False):
        file = self.file_list[self.f]
        name = textwrap.fill(self.searches[self.i]['name'], 75)
        flags = self.parent.flags
        if reverse :
            flags |= regex.REVERSE
        if not self.searches[self.i]['case_sensitive']:
            flags |= regex.IGNORECASE
        if self.searches[self.i]['dot_all']:
            flags |= regex.DOTALL
        pat = regex.compile(self.searches[self.i]['find'], flags=flags)
        self.labelreg.setText(_('Expression rationnelle sélectionnée :\n{0}').format(name))
        self.curlabel.setText(_('occurrence {0} sur {1}').format(self.res, self.counts[self.i][0]))
        self.boss.edit_file(file)
        self.editor = self.boss.gui.central.current_editor.editor
        self.editor.setMouseTracking(True)

        self.c = self.editor.textCursor()
        self.data = editors[file].get_raw_data()
        self.match = pat.search(self.data, endpos=self.pos) if reverse else pat.search(self.data, pos=self.pos)
        if self.match:
            self.found = True
            self.newend = self.match.end()
            self.repl = self.get_replace(self.match)
            r = prepare_string_for_xml(polish_text(self.repl))
            r = "<p style='white-space:pre'>" + r
            self.poslabel.setToolTip(r)
            if self.repl != self.match.group():
                self.poslabel.setStyleSheet('color:red;')
                self.res = self.res - 1 if reverse else self.res + 1
            else :
                self.poslabel.setStyleSheet('color:auto;')

            return self.show_step()
        else:
            self.found = False
        try:
            return self.previous_click_handler() if reverse else self.next_click_handler()
        except RuntimeError:
            time.sleep(.1)
            return self.previous_click_handler() if reverse else self.next_click_handler()

    def show_step(self):
        start, end = self.match.span()
        self.c.clearSelection()
        self.c.setPosition(start, self.c.MoveAnchor)
        self.c.setPosition(end, self.c.KeepAnchor)
        self.fmt = self.c.charFormat()
        redfmt = QTextCharFormat()
        bluefmt = QTextCharFormat()
        redfmt.setUnderlineStyle(QTextCharFormat.WaveUnderline)
        redfmt.setUnderlineColor(Qt.red)
        bluefmt.setUnderlineStyle(QTextCharFormat.WaveUnderline)
        bluefmt.setUnderlineColor(Qt.blue)
        if self.repl != self.match.group():
            self.c.setCharFormat(redfmt)
        else:
            self.c.setCharFormat(bluefmt)
        # self.c.clearSelection()
        self.editor.setTextCursor(self.c)
        self.editor.centerCursor()
        self.boss.sync_preview_to_editor()
        self.editor.textChanged.connect(self.save_data)

    def get_file_list(self):
        key = regex.search(r'(\[\w*?\])', self.searches[self.i]['name']).group(0)
        return self.parent.key_file_map[key]

    def get_replace(self, match):
        rep = self.searches[self.i]['replace']
        if self.searches[self.i]['mode'] == 'regex' :
            return match.expand(self.searches[self.i]['replace'])
        elif self.searches[self.i]['mode'] == 'function':
            try:
                f = self.parent.fdict[rep]
                f.init_env()
            except KeyError :
                info_dialog(None, _('Erreur'), _("Le nom de la fonction-regex '{}' ne correspond à rien dans le fichier 'typex_user_functions.json'.\
                    ({})").format(rep, self.searches[self.i]['name']), show=True)
            return f(match)

    def apply_clicked(self):
        self.editor.textChanged.disconnect(self.save_data)
        repl = self.get_replace(self.match)
        first = self.c.selectionStart()
        self.c.insertText(unicodedata.normalize('NFC', str(repl)))
        self.c.setPosition(first)
        self.c.setPosition(first + len(repl), self.c.KeepAnchor)
        self.newend = first + len(repl)
        self.c.setCharFormat(self.fmt)
        self.editor.setTextCursor(self.c)
        self.editor.textChanged.connect(self.save_data)

    def applynext_clicked(self):
        self.apply_clicked()
        if self.bnext.isEnabled() :
            self.next_click_handler()

    def apply_all_clicked(self):
        with BusyCursor() :
            current = self.i
            while True:
                self.apply_clicked()
                complete = self.next_click_handler()
                if complete:
                    # end of all rules, exit remote control
                    self.quit_clicked()
                    return
                if self.i != current:
                    # end of one rule, get out and come back with the next rule
                    return

            # le block "legacy" ci-dessous était exécuté en sortie de boucle et semble ne servir à rien,
            # à part le quit_clicked qui a été rajouté dans la boucle elle-même, avant le return (précédemment break)
            '''flags = self.parent.flags
            pat = regex.compile(self.searches[self.i]['find'], flags=flags)
            if not self.searches[self.i]['case_sensitive']:
                flags |= regex.IGNORECASE
            if self.searches[self.i]['dot_all']:
                flags |= regex.DOTALL
            for file in self.file_list[self.f:] :
                self.boss.edit_file(file)
                data = editors[file].get_raw_data()
                for m in pat.finditer(data) :
                    repl = self.get_replace(m)
                    data = pat.sub(repl, data, 1)
                editors[file].replace_data(data)
                self.editor.textChanged.connect(self.save_data)
        self.quit_clicked()'''

    def quit_one(self):
        if self.i < len(self.searches) - 1:
            self.i += 1
            self.res = 1
            self.file_list = self.get_file_list()
            self.f = 0
            self.pos = 0
            return self.find_step()
        self.quit_clicked()

    def quit_clicked(self):
        for name, ed in editors.items():
            ed = editors[name]
            raw = ed.get_raw_data()
            self.parent.cache[name] = raw
            self.parent.current_container.open(name, 'wb').write(raw.encode('utf-8'))
        self.close()
        self.parent.dlg.widg.refresh()

    def save_data(self):
        ed = editors[self.boss.currently_editing]
        name = editor_name(ed)
        raw = ed.get_raw_data()
        self.parent.cache[name] = raw
        self.parent.current_container.open(name, 'wb').write(raw.encode('utf-8'))

    def cancel_clicked(self):
        self.boss.rewind_savepoint()
        self.boss.apply_container_update_to_gui()
        self.close()

    def closeEvent(self, *args, **kwargs):
        del self.searches[:]
        for widget in self.parent.gui.children():
            if isinstance(widget, QDockWidget) and widget.objectName() == 'typex-view':
                widget.show()
                widget.setFocus()
        return QDockWidget.closeEvent(self, *args, **kwargs)


class BusyWidget(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        l = QVBoxLayout()
        self.setLayout(l)
        l.addStretch(10)
        self.wi = QLabel()
        pixmap = QPixmap()
        pixmap.loadFromData(load_resources('resources/hourglass64.png'))
        self.wi.setPixmap(pixmap)
        l.addWidget(self.wi, alignment=Qt.AlignHCenter)
        self.dummy = QLabel('<h3> ')
        l.addSpacing(10)
        l.addWidget(self.dummy, alignment=Qt.AlignHCenter)
        l.addStretch(10)
        self.text = _('<h2>Comptage des remplacements...')
        self.dummy.setText(self.text)


class HTMLDelegate(QStyledItemDelegate):
    """
    copied from http://apocalyptech.com/linux/qt/qcombobox_html/
    
    Class for use in a QComboBox to allow HTML Text.  I'm still a bit
    miffed that this isn't just a default part of Qt.  There's a lot of
    Google hits from people looking to do this, most suggesting
    implementing something like this, but so far I've only found this
    one actual implementation, at the end of a thread here:
    http://www.qtcentre.org/threads/62867-HTML-rich-text-delegate-and-text-centering-aligning-code-amp-pictures

    I suspect this implementation is probably heavier than we actually
    need, but it seems fairly voodooey anyway.  And keep in mind that
    after all this, you've still got to produce a completely bloody
    different solution for displaying the currently-selected item in
    the QComboBox; this is only for the list of choices.  I'm happy
    to be leaving Gtk but this kind of thing makes the move more
    bittersweet than it should be.
    """

    def __init__(self, parent=None):
        super(HTMLDelegate, self).__init__()
        self.doc = QTextDocument(self)

    def paint(self, painter, option, index):
        """
        Paint routine for our items in the QComboBox
        """

        # Save our painter so it can be restored later
        painter.save()

        # Copy our option var so we can make some changes without modifying
        # the underlying object
        options = QStyleOptionViewItem(option)
        self.initStyleOption(options, index)

        # Add in our data to our QTextDocument
        self.doc.setHtml(options.text)

        # Acquire our style
        if options.widget is None:
            style = QApplication.style()
        else:
            style = options.widget.style()

        # Draw a barebones version of the control which doesn't have any
        # text specified - this is to render the background, basically, so
        # that when we're mousing over one of the items the bg changes.
        options.text = ''
        style.drawControl(QStyle.CE_ItemViewItem, options, painter)

        # Grab a PaintContext and set our text color depending on if we're
        # selected or not
        ctx = QAbstractTextDocumentLayout.PaintContext()
        if option.state & QStyle.State_Selected:
            ctx.palette.setColor(QPalette.Text, option.palette.color(
                QPalette.Active, QPalette.HighlightedText))
        else:
            ctx.palette.setColor(QPalette.Text, option.palette.color(
                QPalette.Active, QPalette.Text))

        # Calculating some rendering geometry.
        textRect = style.subElementRect(QStyle.SE_ItemViewItemText, options, None)
        textRect.adjust(3, 0, 0, 0)
        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))

        # Now, finally, actually render the text
        self.doc.documentLayout().draw(painter, ctx)

        # Restore our paintbrush
        painter.restore()

    def sizeHint(self, option, index):
        """
        Our size.  This actually gets called before `paint`, I think, and therefore
        is called before our text has actually been loaded into the QTextDocument,
        but apparently seems to Do The Right Thing Anyway.
        """
        return QSize(self.doc.idealWidth(), self.doc.size().height())


class HTMLStyle(QProxyStyle):
    """
    A QProxyStyle which can be used to render HTML/Rich text inside
    QComboBoxes, QCheckBoxes and QRadioButtons.  Note that for QComboBox,
    this does NOT alter rendering of the items when you're choosing from
    the list.  For that you'll need to set an item delegate.
    """

    def __init__(self, *args, **kwargs):
        super(HTMLStyle, self).__init__(*args, **kwargs)
        self.text_doc = QTextDocument()

    def drawItemText(self, painter, rect, alignment, pal, enabled, text, text_role):
        """
        This is what draws the text - we use an internal QTextDocument
        to do the formatting.  The general form of this function follows the
        C++ version at https://github.com/qt/qtbase/blob/5.9/src/widgets/styles/qstyle.cpp

        Note that we completely ignore the `alignment` and `enabled` variable.
        This is always left-aligned, and does not currently support disabled
        widgets.
        """
        if not text or text == '':
            return

        # Save our current pen if we need to
        saved_pen = None
        if text_role != QPalette.NoRole:
            saved_pen = painter.pen()
            painter.setPen(QPen(pal.brush(text_role), saved_pen.widthF()))

        # Render the text.  There's a bit of voodoo here with the rectangles
        # and painter translation; there was various bits of finagling necessary
        # to get this to seem to work with both combo boxes and checkboxes.
        # There's probably better ways to be doing this.
        margin = 3
        painter.save()
        painter.translate(rect.left() - margin, 0)
        self.text_doc.setHtml(text)
        self.text_doc.drawContents(painter,
                QRectF(rect.adjusted(-rect.left(), 0, -margin, 0)))
        painter.restore()

        # Restore our previous pen if we need to
        if text_role != QPalette.NoRole:
            painter.setPen(saved_pen)

    def sizeFromContents(self, contents_type, option, size, widget=None):
        """
        For ComboBoxes, this gets called to determine the size of the list of
        options for the comboboxes.  This is too wide for our HTMLComboBox, so
        we pull in the width from there instead.
        """
        width = size.width()
        height = size.height()
        if contents_type == self.CT_ComboBox and widget:  # and type(widget) == HTMLComboBox:
            size = widget.sizeHint()
            width = size.width() + widget.width_adjust_contents
        return QProxyStyle.sizeFromContents(self, contents_type,
                option,
                QSize(width, height),
                widget)


class HTMLComboBox(QComboBox):
    """
    Custom QComboBox class to handle dealing with HTML/Rich text.  This
    is basically just here to set a few attributes and then implement
    a custom sizeHint.  This implementation does NOT support ComboBoxes
    whose contents get updated - sizeHint will cache its information
    the first time it's called and then never update it.
    """

    def __init__(self, parent):
        super(HTMLComboBox, self).__init__(parent)
        self.setStyle(HTMLStyle())
        self.setItemDelegate(HTMLDelegate())
        self.stored_size = None

        # TODO: Figure out how to actually calculate these properly
        self.width_adjust_sizehint = 20
        self.width_adjust_contents = -30

    def sizeHint(self):
        """
        Use a QTextDocument to compute our rendered text size
        """
        if not self.stored_size:
            doc = QTextDocument()
            model = self.model()
            max_w = 0
            max_h = 0
            for rownum in range(model.rowCount()):
                item = model.item(rownum)
                doc.setHtml(item.text())
                size = doc.size()
                if size.width() > max_w:
                    max_w = size.width()
                if size.height() > max_h:
                    max_h = size.height()

            # Need to add in a bit of padding to account for the
            # arrow selector
            max_w += self.width_adjust_sizehint

            self.stored_size = QSize(max_w, max_h)
        return self.stored_size

    def minimumSizeHint(self):
        """
        Just use the same logic as `sizeHint`
        """
        return self.sizeHint()


class DisplayFunction(Dialog):

    def __init__(self):
        Dialog.__init__(self, _('Fonctions-regex de Typex'), 'function-display')
        self.setAttribute(Qt.WA_DeleteOnClose, False)

    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        hone = QHBoxLayout(self)
        namelb = QLabel(_('Fonction :'))
        self.namecb = QComboBox(self)
        hone.addWidget(namelb)
        hone.addWidget(self.namecb)
        hone.addStretch()
        l.addLayout(hone)

        htwo = QHBoxLayout(self)
        reglb = QLabel(_('Expression rationnelle :'))
        self.regte = QTextEdit(self)
        self.regte.setReadOnly(True)
        self.regte.setMaximumSize(QSize(16777215, 50))
        self.regte.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
        htwo.addWidget(reglb)
        htwo.addWidget(self.regte)
        l.addLayout(htwo)

        self.text = t = QPlainTextEdit(self)
        self.text.setReadOnly(True)

        l.addWidget(t)

        l.addWidget(self.bb)
        self.bb.setStandardButtons(self.bb.Close)
        self.cb = b = self.bb.addButton(_('Copier dans le presse-papier'), self.bb.ActionRole)
        b.clicked.connect(self.copy_to_clipboard)
        b.setIcon(QIcon(I('edit-copy.png')))
        self.namecb.currentTextChanged.connect(self.show_code)

    def init_display(self, d):
        self.d = d
        self.namecb.addItems(list(self.d.keys()))

    def show_code(self, text):
        self.text.setPlainText(self.d[text][0])
        self.regte.setText(self.d[text][1])

    def sizeHint(self):
        fm = QFontMetrics(self.text.font())
        return QSize(fm.averageCharWidth() * 120, 400)

    def copy_to_clipboard(self):
        QApplication.instance().clipboard().setText(self.text.toPlainText())

