#!/usr/pkg/bin/python3.12


__license__   = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

import copy
from collections import defaultdict

from qt.core import Qt, QComboBox, QListWidgetItem

from calibre.customize.ui import is_disabled
from calibre.gui2 import error_dialog, question_dialog, warning_dialog
from calibre.gui2.device import device_name_for_plugboards
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.plugboard_ui import Ui_Form
from calibre.customize.ui import metadata_writers, device_plugins, disabled_device_plugins
from calibre.library.save_to_disk import plugboard_any_format_value, \
                    plugboard_any_device_value, plugboard_save_to_disk_value, \
                    find_plugboard
from calibre.srv.content import plugboard_content_server_value, plugboard_content_server_formats
from calibre.gui2.email import plugboard_email_value, plugboard_email_formats
from calibre.utils.formatter import validation_formatter
from polyglot.builtins import native_string_type


class ConfigWidget(ConfigWidgetBase, Ui_Form):

    def genesis(self, gui):
        self.gui = gui
        self.db = gui.library_view.model().db

    def initialize(self):
        ConfigWidgetBase.initialize(self)

        self.current_plugboards = copy.deepcopy(self.db.prefs.get('plugboards',{}))
        self.current_device = None
        self.current_format = None

        if self.gui.device_manager.connected_device is not None:
            self.device_label.setText(_('Device currently connected: ') +
                    self.gui.device_manager.connected_device.__class__.__name__)
        else:
            self.device_label.setText(_('Device currently connected: None'))

        self.devices = ['', 'APPLE', 'FOLDER_DEVICE']
        self.disabled_devices = []
        self.device_to_formats_map = {}
        for device in device_plugins():
            n = device_name_for_plugboards(device)
            self.device_to_formats_map[n] = set(device.settings().format_map)
            if getattr(device, 'CAN_DO_DEVICE_DB_PLUGBOARD', False):
                self.device_to_formats_map[n].add('device_db')
            if n not in self.devices:
                self.devices.append(n)

        for device in disabled_device_plugins():
            n = device_name_for_plugboards(device)
            if n not in self.disabled_devices:
                self.disabled_devices.append(n)

        self.devices.sort(key=lambda x: x.lower())
        self.devices.insert(1, plugboard_save_to_disk_value)
        self.devices.insert(1, plugboard_content_server_value)
        self.device_to_formats_map[plugboard_content_server_value] = \
                        plugboard_content_server_formats
        self.devices.insert(1, plugboard_email_value)
        self.device_to_formats_map[plugboard_email_value] = \
                        plugboard_email_formats
        self.devices.insert(1, plugboard_any_device_value)
        self.new_device.addItems(self.devices)

        self.formats = ['']
        self.format_to_writers_map = defaultdict(list)
        for w in metadata_writers():
            for f in w.file_types:
                if f not in self.formats:
                    self.formats.append(f)
                self.format_to_writers_map[f].append(w)
        self.formats.append('device_db')
        self.formats.sort()
        self.formats.insert(1, plugboard_any_format_value)
        self.new_format.addItems(self.formats)

        self.dest_fields = ['',
                            'authors', 'author_sort', 'language', 'publisher',
                            'series', 'tags', 'title', 'title_sort', 'comments']

        self.source_widgets = []
        self.dest_widgets = []
        for i in range(0, len(self.dest_fields)-1):
            w = TemplateLineEditor(self)
            self.source_widgets.append(w)
            self.fields_layout.addWidget(w, 5+i, 0, 1, 1)
            w = QComboBox(self)
            self.dest_widgets.append(w)
            self.fields_layout.addWidget(w, 5+i, 1, 1, 1)

        self.edit_device.currentIndexChanged[native_string_type].connect(self.edit_device_changed)
        self.edit_format.currentIndexChanged[native_string_type].connect(self.edit_format_changed)
        self.new_device.currentIndexChanged[native_string_type].connect(self.new_device_changed)
        self.new_format.currentIndexChanged[native_string_type].connect(self.new_format_changed)
        self.existing_plugboards.itemClicked.connect(self.existing_pb_clicked)
        self.ok_button.clicked.connect(self.ok_clicked)
        self.del_button.clicked.connect(self.del_clicked)

        self.refilling = False
        self.refill_all_boxes()

    def clear_fields(self, edit_boxes=False, new_boxes=False):
        self.ok_button.setEnabled(False)
        self.del_button.setEnabled(False)
        for w in self.source_widgets:
            w.clear()
        for w in self.dest_widgets:
            w.clear()
        if edit_boxes:
            self.edit_device.setCurrentIndex(0)
            self.edit_format.setCurrentIndex(0)
        if new_boxes:
            self.new_device.setCurrentIndex(0)
            self.new_format.setCurrentIndex(0)

    def set_fields(self):
        self.ok_button.setEnabled(True)
        self.del_button.setEnabled(True)
        for w in self.source_widgets:
            w.clear()
        for w in self.dest_widgets:
            w.addItems(self.dest_fields)

    def set_field(self, i, src, dst):
        self.source_widgets[i].setText(src)
        idx = self.dest_fields.index(dst)
        self.dest_widgets[i].setCurrentIndex(idx)

    def edit_device_changed(self, txt):
        self.current_device = None
        if txt == '':
            self.clear_fields(new_boxes=False)
            return
        self.clear_fields(new_boxes=True)
        self.current_device = str(txt)
        fpb = self.current_plugboards.get(self.current_format, None)
        if fpb is None:
            print('edit_device_changed: none format!')
            return
        dpb = fpb.get(self.current_device, None)
        if dpb is None:
            print('edit_device_changed: none device!')
            return
        self.set_fields()
        for i,op in enumerate(dpb):
            self.set_field(i, op[0], op[1])
        self.ok_button.setEnabled(True)
        self.del_button.setEnabled(True)

    def edit_format_changed(self, txt):
        self.edit_device.setCurrentIndex(0)
        self.current_device = None
        self.current_format = None
        if txt == '':
            self.clear_fields(new_boxes=False)
            return
        self.clear_fields(new_boxes=True)
        txt = str(txt)
        fpb = self.current_plugboards.get(txt, None)
        if fpb is None:
            print('edit_format_changed: none editable format!')
            return
        self.current_format = txt
        self.check_if_writer_disabled(txt)
        devices = ['']
        for d in fpb:
            devices.append(d)
        self.edit_device.clear()
        self.edit_device.addItems(devices)

    def check_if_writer_disabled(self, format_name):
        if format_name in ['device_db', plugboard_any_format_value]:
            return
        show_message = True
        for writer in self.format_to_writers_map[format_name]:
            if not is_disabled(writer):
                show_message = False
        if show_message:
            warning_dialog(self, '',
                     _('That format has no metadata writers enabled. A plugboard '
                       'will probably have no effect.'),
                     show=True)

    def new_device_changed(self, txt):
        self.current_device = None
        if txt == '':
            self.clear_fields(edit_boxes=False)
            return
        self.clear_fields(edit_boxes=True)
        self.current_device = str(txt)

        if self.current_format in self.current_plugboards and \
                self.current_device in self.current_plugboards[self.current_format]:
            error_dialog(self, '',
                     _('That format and device already has a plugboard.'),
                     show=True)
            self.new_device.setCurrentIndex(0)
            return

        # If we have a specific format/device combination, check if a more
        # general combination matches.
        if self.current_format != plugboard_any_format_value and \
                self.current_device != plugboard_any_device_value:
            if find_plugboard(self.current_device, self.current_format,
                      self.current_plugboards):
                if not question_dialog(self.gui,
                        _('Possibly override plugboard?'),
                        _('A more general plugboard already exists for '
                          'that format and device. '
                          'Are you sure you want to add the new plugboard?')):
                    self.new_device.setCurrentIndex(0)
                    return

        # If we have a specific format, check if we are adding a possibly-
        # covered plugboard
        if self.current_format != plugboard_any_format_value:
            if self.current_format in self.current_plugboards:
                if self.current_device == plugboard_any_device_value:
                    if not question_dialog(self.gui,
                               _('Add possibly overridden plugboard?'),
                               _('More specific device plugboards exist for '
                                 'that format. '
                                 'Are you sure you want to add the new plugboard?')):
                        self.new_device.setCurrentIndex(0)
                        return
        # We are adding an 'any format' entry. Check if we are adding a specific
        # device and if so, does some other plugboard match that device.
        elif self.current_device != plugboard_any_device_value:
            for fmt in self.current_plugboards:
                if find_plugboard(self.current_device, fmt, self.current_plugboards):
                    if not question_dialog(self.gui,
                            _('Really add plugboard?'),
                            _('A different plugboard matches that format and '
                              'device combination. '
                              'Are you sure you want to add the new plugboard?')):
                        self.new_device.setCurrentIndex(0)
                        return
        # We are adding an any format/any device entry, which will be overridden
        # by any other entry. Ask if such entries exist.
        elif len(self.current_plugboards):
            if not question_dialog(self.gui,
                       _('Add possibly overridden plugboard?'),
                       _('More specific format and device plugboards '
                         'already exist. '
                         'Are you sure you want to add the new plugboard?')):
                self.new_device.setCurrentIndex(0)
                return

        if self.current_format != plugboard_any_format_value and \
                    self.current_device in self.device_to_formats_map:
            allowable_formats = self.device_to_formats_map[self.current_device]
            if self.current_format not in allowable_formats:
                error_dialog(self, '',
                     _('The {0} device does not support the {1} format.').
                                format(self.current_device, self.current_format), show=True)
                self.new_device.setCurrentIndex(0)
                return

        if self.current_format == plugboard_any_format_value and \
                    self.current_device == plugboard_content_server_value:
            warning_dialog(self, '',
                 _('The {0} device supports only the {1} format(s).').
                            format(plugboard_content_server_value,
                                   ', '.join(plugboard_content_server_formats)), show=True)

        self.set_fields()

    def new_format_changed(self, txt):
        self.current_format = None
        self.current_device = None
        self.new_device.setCurrentIndex(0)
        if txt:
            self.clear_fields(edit_boxes=True)
            self.current_format = str(txt)
            self.check_if_writer_disabled(self.current_format)
        else:
            self.clear_fields(edit_boxes=False)

    def ok_clicked(self):
        pb = []
        comments_in_dests = False
        for i in range(0, len(self.source_widgets)):
            s = str(self.source_widgets[i].text())
            if s:
                d = self.dest_widgets[i].currentIndex()
                if d != 0:
                    try:
                        validation_formatter.validate(s)
                    except Exception as err:
                        error_dialog(self, _('Invalid template'),
                                '<p>'+_('The template %s is invalid:')%s +
                                '<br>'+str(err), show=True)
                        return
                    pb.append((s, self.dest_fields[d]))
                    comments_in_dests = comments_in_dests or self.dest_fields[d] == 'comments'
                else:
                    error_dialog(self, _('Invalid destination'),
                            '<p>'+_('The destination field cannot be blank'),
                            show=True)
                    return
        if len(pb) == 0:
            if self.current_format in self.current_plugboards:
                fpb = self.current_plugboards[self.current_format]
                if self.current_device in fpb:
                    del fpb[self.current_device]
                if len(fpb) == 0:
                    del self.current_plugboards[self.current_format]
        else:
            if comments_in_dests and not question_dialog(self.gui, _('Plugboard modifies comments'),
                     _('This plugboard modifies the comments metadata. '
                       'If the comments are set to invalid HTML, it could cause problems on the device. '
                       'Are you sure you wish to save this plugboard?'
                       ),
                        skip_dialog_name='plugboard_comments_in_dests'
                        ):
                return
            if self.current_format not in self.current_plugboards:
                self.current_plugboards[self.current_format] = {}
            fpb = self.current_plugboards[self.current_format]
            fpb[self.current_device] = pb
        self.changed_signal.emit()
        self.refill_all_boxes()

    def del_clicked(self):
        if self.current_format in self.current_plugboards:
            fpb = self.current_plugboards[self.current_format]
            if self.current_device in fpb:
                del fpb[self.current_device]
            if len(fpb) == 0:
                del self.current_plugboards[self.current_format]
        self.changed_signal.emit()
        self.refill_all_boxes()

    def existing_pb_clicked(self, qitem):
        item = qitem.data(Qt.ItemDataRole.UserRole)
        if (qitem.flags() & Qt.ItemFlag.ItemIsEnabled):
            self.edit_format.setCurrentIndex(self.edit_format.findText(item[0]))
            self.edit_device.setCurrentIndex(self.edit_device.findText(item[1]))
        else:
            warning_dialog(self, '',
                 _('The {0} device plugin is disabled.').format(item[1]),
                 show=True)

    def refill_all_boxes(self):
        if self.refilling:
            return
        self.refilling = True
        self.current_device = None
        self.current_format = None
        self.clear_fields(new_boxes=True)
        self.edit_format.clear()
        self.edit_format.addItem('')
        for format_ in self.current_plugboards:
            self.edit_format.addItem(format_)
        self.edit_format.setCurrentIndex(0)
        self.edit_device.clear()
        self.ok_button.setEnabled(False)
        self.del_button.setEnabled(False)
        self.existing_plugboards.clear()
        for f in self.formats:
            if f not in self.current_plugboards:
                continue
            for d in sorted(self.devices + self.disabled_devices, key=lambda x:x.lower()):
                if d not in self.current_plugboards[f]:
                    continue
                ops = []
                for op in self.current_plugboards[f][d]:
                    ops.append('([' + op[0] + '] -> ' + op[1] + ')')
                txt = '%s:%s = %s\n'%(f, d, ', '.join(ops))
                item = QListWidgetItem(txt)
                item.setData(Qt.ItemDataRole.UserRole, (f, d))
                if d in self.disabled_devices:
                    item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEnabled)
                self.existing_plugboards.addItem(item)
        self.refilling = False

    def restore_defaults(self):
        ConfigWidgetBase.restore_defaults(self)
        self.current_plugboards = {}
        self.refill_all_boxes()
        self.changed_signal.emit()

    def commit(self):
        self.db.new_api.set_pref('plugboards', self.current_plugboards)
        return ConfigWidgetBase.commit(self)

    def refresh_gui(self, gui):
        pass


if __name__ == '__main__':
    from qt.core import QApplication
    app = QApplication([])
    test_widget('Import/Export', 'Plugboard')
