#!/usr/bin/env python
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid@kovidgoyal.net>

import re
from functools import partial

from qt.core import QDropEvent, QListWidget, QListWidgetItem, Qt

from calibre.constants import iswindows
from calibre.customize.ui import all_input_formats, available_output_formats
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.oeb.iterator import is_supported
from calibre.gui2 import config, dynamic, gprefs, info_dialog
from calibre.gui2.actions.choose_library import get_change_library_action_plugin
from calibre.gui2.preferences import ConfigWidgetBase, Setting, get_move_count, test_widget
from calibre.gui2.preferences.behavior_ui import Ui_Form
from calibre.utils.config import prefs
from calibre.utils.icu import sort_key
from calibre.utils.localization import _


class OutputFormatSetting(Setting):
    CHOICES_SEARCH_FLAGS = Qt.MatchFlag.MatchFixedString


class ConfigWidget(ConfigWidgetBase, Ui_Form):
    def genesis(self, gui):
        self.gui = gui
        db = gui.library_view.model().db

        r = self.register
        choices = (
            [(_('Low'), 'low'), (_('Normal'), 'normal'), (_('High'), 'high')]
            if iswindows
            else [(_('Normal'), 'normal'), (_('Low'), 'low'), (_('Very low'), 'high')]
        )
        r('worker_process_priority', prefs, choices=choices)

        r('network_timeout', prefs)

        r('new_version_notification', config)
        r('upload_news_to_device', config)
        r('delete_news_from_library_on_upload', config)

        output_formats = sorted(available_output_formats())
        output_formats.remove('oeb')
        choices = [(x.upper(), x) for x in output_formats]
        r('output_format', prefs, choices=choices, setting=OutputFormatSetting)

        restrictions = sorted(db.prefs['virtual_libraries'], key=sort_key)
        choices = [('', '')] + [(x, x) for x in restrictions]
        # check that the virtual library still exists
        vls = db.prefs['virtual_lib_on_startup']
        if vls and vls not in restrictions:
            db.prefs['virtual_lib_on_startup'] = ''
        r('virtual_lib_on_startup', db.prefs, choices=choices)
        self.reset_confirmation_button.clicked.connect(self.reset_confirmation_dialogs)

        self.input_up_button.clicked.connect(partial(self.up_input, use_kbd_modifiers=True))
        self.input_down_button.clicked.connect(partial(self.down_input, use_kbd_modifiers=True))
        self.opt_input_order.set_movement_functions(partial(self.up_input, use_kbd_modifiers=False), partial(self.down_input, use_kbd_modifiers=False))
        self.opt_input_order.handle_drop_event = self.input_order_drop_event
        for signal in ('Activated', 'Changed', 'DoubleClicked', 'Clicked'):
            signal = getattr(self.opt_internally_viewed_formats, 'item' + signal)
            signal.connect(self.internally_viewed_formats_changed)

        r('bools_are_tristate', db.prefs, restart_required=True)
        r('numeric_collation', prefs, restart_required=True)

    def input_order_drop_event(self, ev: QDropEvent | None) -> None:
        QListWidget.dropEvent(self.opt_input_order, ev)
        if ev is not None and ev.isAccepted():
            self.changed_signal.emit()

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

    def restore_defaults(self, *args):
        ConfigWidgetBase.restore_defaults(self)
        self.init_input_order(defaults=True)
        self.init_internally_viewed_formats(defaults=True)
        self.changed_signal.emit()

    def commit(self, *args):
        input_map = prefs['input_format_order']
        input_cols = []
        for i in range(self.opt_input_order.count()):
            _item = self.opt_input_order.item(i)
            assert _item is not None
            input_cols.append(str(_item.data(Qt.ItemDataRole.UserRole) or ''))
        if input_map != input_cols:
            prefs['input_format_order'] = input_cols
        fmts = self.current_internally_viewed_formats
        old = config['internally_viewed_formats']
        if fmts != old:
            config['internally_viewed_formats'] = fmts
        ret = ConfigWidgetBase.commit(self)
        # Signal a possible change of the VL at startup opt
        get_change_library_action_plugin().rebuild_change_library_menus.emit()
        return ret

    # Internally viewed formats {{{
    def internally_viewed_formats_changed(self, *args):
        fmts = self.current_internally_viewed_formats
        old = config['internally_viewed_formats']
        if fmts != old:
            self.changed_signal.emit()

    def init_internally_viewed_formats(self, defaults=False):
        if defaults:
            fmts = config.defaults['internally_viewed_formats']
        else:
            fmts = config['internally_viewed_formats']
        viewer = self.opt_internally_viewed_formats
        viewer.blockSignals(True)
        exts = set()
        for ext in BOOK_EXTENSIONS:
            ext = ext.lower()
            ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext)
            if ext == 'lrf' or is_supported('book.' + ext):
                exts.add(ext)
        viewer.clear()
        for ext in sorted(exts):
            viewer.addItem(ext.upper())
            item = viewer.item(viewer.count() - 1)
            assert item is not None
            item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsUserCheckable)
            item.setCheckState(Qt.CheckState.Checked if ext.upper() in fmts else Qt.CheckState.Unchecked)
        viewer.blockSignals(False)

    @property
    def current_internally_viewed_formats(self):
        fmts = []
        viewer = self.opt_internally_viewed_formats
        for i in range(viewer.count()):
            _item = viewer.item(i)
            assert _item is not None
            if _item.checkState() == Qt.CheckState.Checked:
                fmts.append(str(_item.text()))
        return fmts

    # }}}

    # Input format order {{{
    def init_input_order(self, defaults=False):
        if defaults:
            input_map = prefs.defaults['input_format_order']
        else:
            input_map = prefs['input_format_order']
        all_formats = set()
        self.opt_input_order.clear()
        for fmt in all_input_formats().union({'ZIP', 'RAR'}):
            all_formats.add(fmt.upper())
        for format in input_map + list(all_formats.difference(input_map)):
            item = QListWidgetItem(format, self.opt_input_order)
            item.setData(Qt.ItemDataRole.UserRole, (format))
            item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsDragEnabled)

    def up_input(self, use_kbd_modifiers, *args):
        count = get_move_count(self.opt_input_order.count()) if use_kbd_modifiers else 1
        for _x in range(count):
            idx = self.opt_input_order.currentRow()
            if idx > 0:
                self.opt_input_order.insertItem(idx - 1, self.opt_input_order.takeItem(idx))
                self.opt_input_order.setCurrentRow(idx - 1)
                self.changed_signal.emit()

    def down_input(self, use_kbd_modifiers, *args):
        count = get_move_count(self.opt_input_order.count()) if use_kbd_modifiers else 1
        for _x in range(count):
            idx = self.opt_input_order.currentRow()
            if idx < self.opt_input_order.count() - 1:
                self.opt_input_order.insertItem(idx + 1, self.opt_input_order.takeItem(idx))
                self.opt_input_order.setCurrentRow(idx + 1)
                self.changed_signal.emit()

    # }}}

    def reset_confirmation_dialogs(self, *args):
        from calibre.gui2.tweak_book import tprefs
        from calibre.gui2.viewer.config import vprefs

        for confloc in (dynamic, vprefs, tprefs):
            for key in confloc.keys():
                if key.endswith('_again') and confloc[key] is False:
                    confloc[key] = True
        gprefs['questions_to_auto_skip'] = []
        info_dialog(self, _('Done'), _('Confirmation dialogs have all been reset'), show=True)


if __name__ == '__main__':
    from calibre.gui2 import Application

    app = Application([])
    test_widget('Interface', 'Behavior')
