#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#

import logging
from collections import deque

import deluge.component as component
from deluge.decorators import overrides
from deluge.ui.client import client
from deluge.ui.console.modes.basemode import BaseMode
from deluge.ui.console.modes.preferences.preference_panes import (
    BandwidthPane,
    CachePane,
    DaemonPane,
    DownloadsPane,
    InterfacePane,
    NetworkPane,
    OtherPane,
    ProxyPane,
    QueuePane,
)
from deluge.ui.console.utils import curses_util as util
from deluge.ui.console.widgets.fields import SelectInput
from deluge.ui.console.widgets.popup import MessagePopup, PopupsHandler
from deluge.ui.console.widgets.sidebar import Sidebar

try:
    import curses
except ImportError:
    pass


log = logging.getLogger(__name__)


# Big help string that gets displayed when the user hits 'h'
HELP_STR = """This screen lets you view and configure various options in deluge.

There are three main sections to this screen. Only one section is active at a time. \
You can switch the active section by hitting TAB (or Shift-TAB to go back one)

The section on the left displays the various categories that the settings fall in. \
You can navigate the list using the up/down arrows

The section on the right shows the settings for the selected category. When this \
section is active you can navigate the various settings with the up/down arrows. \
Special keys for each input type are described below.

The final section is at the bottom right, the: [Cancel] [Apply] [OK] buttons.
When this section is active, simply select the option you want using the arrow
keys and press Enter to confim.


Special keys for various input types are as follows:
- For text inputs you can simply type in the value.

{|indent:  |}- For numeric inputs (indicated by the value being in []s), you can type a value, \
or use PageUp and PageDown to increment/decrement the value.

- For checkbox inputs use the spacebar to toggle

{|indent:  |}- For checkbox plus something else inputs (the something else being only visible \
when you check the box) you can toggle the check with space, use the right \
arrow to edit the other value, and escape to get back to the check box.

"""


class ZONE:
    length = 3
    CATEGORIES, PREFRENCES, ACTIONS = list(range(length))


class PreferenceSidebar(Sidebar):
    def __init__(self, torrentview, width):
        height = curses.LINES - 2
        Sidebar.__init__(
            self, torrentview, width, height, title=None, border_off_north=1
        )
        self.categories = [
            _('Interface'),
            _('Downloads'),
            _('Network'),
            _('Bandwidth'),
            _('Other'),
            _('Daemon'),
            _('Queue'),
            _('Proxy'),
            _('Cache'),
        ]
        for name in self.categories:
            self.add_text_field(
                name,
                name,
                selectable=True,
                font_unfocused_active='bold',
                color_unfocused_active='white,black',
            )

    def on_resize(self):
        self.resize_window(curses.LINES - 2, self.width)


class Preferences(BaseMode, PopupsHandler):
    def __init__(self, parent_mode, stdscr, console_config, encoding=None):
        BaseMode.__init__(self, stdscr, encoding=encoding, do_refresh=False)
        PopupsHandler.__init__(self)
        self.parent_mode = parent_mode
        self.cur_cat = 0
        self.messages = deque()
        self.action_input = None
        self.config_loaded = False
        self.console_config = console_config
        self.active_port = -1
        self.active_zone = ZONE.CATEGORIES
        self.sidebar_width = 15  # Width of the categories pane

        self.sidebar = PreferenceSidebar(parent_mode, self.sidebar_width)
        self.sidebar.set_focused(True)
        self.sidebar.active_input = 0

        self._calc_sizes(resize=False)

        self.panes = [
            InterfacePane(self),
            DownloadsPane(self),
            NetworkPane(self),
            BandwidthPane(self),
            OtherPane(self),
            DaemonPane(self),
            QueuePane(self),
            ProxyPane(self),
            CachePane(self),
        ]

        self.action_input = SelectInput(
            self, None, None, [_('Cancel'), _('Apply'), _('OK')], [0, 1, 2], 0
        )

    def load_config(self):
        if self.config_loaded:
            return

        def on_get_config(core_config):
            self.core_config = core_config
            self.config_loaded = True
            for p in self.panes:
                p.create_pane(core_config, self.console_config)
            self.refresh()

        client.core.get_config().addCallback(on_get_config)

        def on_get_listen_port(port):
            self.active_port = port

        client.core.get_listen_port().addCallback(on_get_listen_port)

    @property
    def height(self):
        # top/bottom bars: 2, Action buttons (Cancel/Apply/OK): 1
        return self.rows - 3

    @property
    def width(self):
        return self.prefs_width

    def _calc_sizes(self, resize=True):
        self.prefs_width = self.cols - self.sidebar_width

        if not resize:
            return

        for p in self.panes:
            p.resize_window(self.height, p.pane_width)

    def _draw_preferences(self):
        self.cur_cat = self.sidebar.active_input
        self.panes[self.cur_cat].render(
            self, self.stdscr, self.prefs_width, self.active_zone == ZONE.PREFRENCES
        )
        self.panes[self.cur_cat].refresh()

    def _draw_actions(self):
        selected = self.active_zone == ZONE.ACTIONS
        self.stdscr.hline(self.rows - 3, self.sidebar_width, b'_', self.cols)
        self.action_input.render(
            self.stdscr,
            self.rows - 2,
            width=self.cols,
            active=selected,
            focus=True,
            col=self.cols - 22,
        )

    @overrides(BaseMode)
    def on_resize(self, rows, cols):
        BaseMode.on_resize(self, rows, cols)
        self._calc_sizes()

        if self.popup:
            self.popup.handle_resize()

        self.sidebar.on_resize()
        self.refresh()

    @overrides(component.Component)
    def update(self):
        for i, p in enumerate(self.panes):
            self.panes[i].update(i == self.cur_cat)

    @overrides(BaseMode)
    def resume(self):
        BaseMode.resume(self)
        self.sidebar.show()

    @overrides(BaseMode)
    def refresh(self):
        if (
            not component.get('ConsoleUI').is_active_mode(self)
            or not self.config_loaded
        ):
            return

        if self.popup is None and self.messages:
            title, msg = self.messages.popleft()
            self.push_popup(MessagePopup(self, title, msg))

        self.stdscr.erase()
        self.draw_statusbars()
        self._draw_actions()
        # Necessary to force updating the stdscr
        self.stdscr.noutrefresh()

        self.sidebar.refresh()

        # do this last since it moves the cursor
        self._draw_preferences()

        if self.popup:
            self.popup.refresh()

        curses.doupdate()

    def _apply_prefs(self):
        if self.core_config is None:
            return

        def update_conf_value(key, source_dict, dest_dict, updated):
            if dest_dict[key] != source_dict[key]:
                dest_dict[key] = source_dict[key]
                updated = True
            return updated

        new_core_config = {}
        for pane in self.panes:
            if not isinstance(pane, InterfacePane):
                pane.add_config_values(new_core_config)
        # Apply Core Prefs
        if client.connected():
            # Only do this if we're connected to a daemon
            config_to_set = {}
            for key in new_core_config:
                # The values do not match so this needs to be updated
                if self.core_config[key] != new_core_config[key]:
                    config_to_set[key] = new_core_config[key]

            if config_to_set:
                # Set each changed config value in the core
                client.core.set_config(config_to_set)
                client.force_call(True)
                # Update the configuration
                self.core_config.update(config_to_set)

        # Update Interface Prefs
        new_console_config = {}
        didupdate = False
        for pane in self.panes:
            # could just access panes by index, but that would break if panes
            # are ever reordered, so do it the slightly slower but safer way
            if isinstance(pane, InterfacePane):
                pane.add_config_values(new_console_config)
                for k in ['ring_bell', 'language']:
                    didupdate = update_conf_value(
                        k, new_console_config, self.console_config, didupdate
                    )
                for k in ['separate_complete', 'move_selection']:
                    didupdate = update_conf_value(
                        k,
                        new_console_config,
                        self.console_config['torrentview'],
                        didupdate,
                    )
                for k in [
                    'ignore_duplicate_lines',
                    'save_command_history',
                    'third_tab_lists_all',
                    'torrents_per_tab_press',
                ]:
                    didupdate = update_conf_value(
                        k, new_console_config, self.console_config['cmdline'], didupdate
                    )

        if didupdate:
            self.parent_mode.on_config_changed()

    def _update_preferences(self, core_config):
        self.core_config = core_config
        for pane in self.panes:
            pane.update_values(core_config)

    def _actions_read(self, c):
        self.action_input.handle_read(c)
        if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
            # take action
            if self.action_input.selected_index == 0:  # Cancel
                self.back_to_parent()
            elif self.action_input.selected_index == 1:  # Apply
                self._apply_prefs()
                client.core.get_config().addCallback(self._update_preferences)
            elif self.action_input.selected_index == 2:  # OK
                self._apply_prefs()
                self.back_to_parent()

    def back_to_parent(self):
        component.get('ConsoleUI').set_mode(self.parent_mode.mode_name)

    @overrides(BaseMode)
    def read_input(self):
        c = self.stdscr.getch()

        if self.popup:
            if self.popup.handle_read(c):
                self.pop_popup()
            self.refresh()
            return

        if util.is_printable_chr(c):
            char = chr(c)
            if char == 'Q':
                component.get('ConsoleUI').quit()
            elif char == 'h':
                self.push_popup(MessagePopup(self, 'Preferences Help', HELP_STR))

        if self.sidebar.has_focus() and c == util.KEY_ESC:
            self.back_to_parent()
            return

        def update_active_zone(val):
            self.active_zone += val
            if self.active_zone == -1:
                self.active_zone = ZONE.length - 1
            else:
                self.active_zone %= ZONE.length
            self.sidebar.set_focused(self.active_zone == ZONE.CATEGORIES)

        if c == util.KEY_TAB:
            update_active_zone(1)
        elif c == curses.KEY_BTAB:
            update_active_zone(-1)
        else:
            if self.active_zone == ZONE.CATEGORIES:
                self.sidebar.handle_read(c)
            elif self.active_zone == ZONE.PREFRENCES:
                self.panes[self.cur_cat].handle_read(c)
            elif self.active_zone == ZONE.ACTIONS:
                self._actions_read(c)

        self.refresh()

    def is_active_pane(self, pane):
        return pane == self.panes[self.cur_cat]
