#
# 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 deluge.decorators import overrides
from deluge.ui.console.modes.basemode import InputKeyHandler
from deluge.ui.console.utils import curses_util as util
from deluge.ui.console.utils import format_utils
from deluge.ui.console.widgets import BaseInputPane, BaseWindow

try:
    import curses
except ImportError:
    pass

log = logging.getLogger(__name__)


class ALIGN:
    TOP_LEFT = 1
    TOP_CENTER = 2
    TOP_RIGHT = 3
    MIDDLE_LEFT = 4
    MIDDLE_CENTER = 5
    MIDDLE_RIGHT = 6
    BOTTOM_LEFT = 7
    BOTTOM_CENTER = 8
    BOTTOM_RIGHT = 9
    DEFAULT = MIDDLE_CENTER


class PopupsHandler:
    def __init__(self):
        self._popups = []

    @property
    def popup(self):
        if self._popups:
            return self._popups[-1]
        return None

    def push_popup(self, pu, clear=False):
        if clear:
            self._popups = []
        self._popups.append(pu)

    def pop_popup(self):
        if self.popup:
            return self._popups.pop()

    def report_message(self, title, message):
        self.push_popup(MessagePopup(self, title, message))


class Popup(BaseWindow, InputKeyHandler):
    def __init__(
        self,
        parent_mode,
        title,
        width_req=0,
        height_req=0,
        align=ALIGN.DEFAULT,
        close_cb=None,
        encoding=None,
        base_popup=None,
        **kwargs
    ):
        """
        Init a new popup.  The default constructor will handle sizing and borders and the like.

        Args:
            parent_mode (basemode subclass): The mode which the popup will be drawn over
            title (str): the title of the popup window
            width_req (int or float): An integer value will be used as the width of the popup in character.
                                      A float value will indicate the requested ratio in relation to the
                                      parents screen width.
            height_req (int or float): An integer value will be used as the height of the popup in character.
                                       A float value will indicate the requested ratio in relation to the
                                       parents screen height.
            align (ALIGN): The alignment controlling the position of the popup on the screen.
            close_cb (func): Function to be called when the popup is closed
            encoding (str): The terminal encoding
            base_popup (Popup): A popup used to inherit width_req and height_req if not explicitly specified.

        Note: The parent mode is responsible for calling refresh on any popups it wants to show.
            This should be called as the last thing in the parents refresh method.

            The parent *must* also call read_input on the popup instead of/in addition to
            running its own read_input code if it wants to have the popup handle user input.

        Popups have two methods that must be implemented:

        refresh(self) - draw the popup window to screen.  this default mode simply draws a bordered window
                        with the supplied title to the screen

        read_input(self) - handle user input to the popup.

        """
        InputKeyHandler.__init__(self)
        self.parent = parent_mode
        self.close_cb = close_cb
        self.height_req = height_req
        self.width_req = width_req
        self.align = align
        if base_popup:
            if not self.width_req:
                self.width_req = base_popup.width_req
            if not self.height_req:
                self.height_req = base_popup.height_req

        hr, wr, posy, posx = self.calculate_size()
        BaseWindow.__init__(self, title, wr, hr, encoding=None)
        self.move_window(posy, posx)
        self._closed = False

    @overrides(BaseWindow)
    def refresh(self):
        self.screen.erase()
        height = self.get_content_height()
        self.ensure_content_pane_height(
            height + self.border_off_north + self.border_off_south
        )
        BaseInputPane.render_inputs(self, focused=True)
        BaseWindow.refresh(self)

    def calculate_size(self):

        if isinstance(self.height_req, float) and 0.0 < self.height_req <= 1.0:
            height = int((self.parent.rows - 2) * self.height_req)
        else:
            height = self.height_req

        if isinstance(self.width_req, float) and 0.0 < self.width_req <= 1.0:
            width = int((self.parent.cols - 2) * self.width_req)
        else:
            width = self.width_req

        # Height
        if height == 0:
            height = int(self.parent.rows / 2)
        elif height == -1:
            height = self.parent.rows - 2
        elif height > self.parent.rows - 2:
            height = self.parent.rows - 2

        # Width
        if width == 0:
            width = int(self.parent.cols / 2)
        elif width == -1:
            width = self.parent.cols
        elif width >= self.parent.cols:
            width = self.parent.cols

        if self.align in [ALIGN.TOP_CENTER, ALIGN.TOP_LEFT, ALIGN.TOP_RIGHT]:
            begin_y = 1
        elif self.align in [ALIGN.MIDDLE_CENTER, ALIGN.MIDDLE_LEFT, ALIGN.MIDDLE_RIGHT]:
            begin_y = (self.parent.rows / 2) - (height / 2)
        elif self.align in [ALIGN.BOTTOM_CENTER, ALIGN.BOTTOM_LEFT, ALIGN.BOTTOM_RIGHT]:
            begin_y = self.parent.rows - height - 1

        if self.align in [ALIGN.TOP_LEFT, ALIGN.MIDDLE_LEFT, ALIGN.BOTTOM_LEFT]:
            begin_x = 0
        elif self.align in [ALIGN.TOP_CENTER, ALIGN.MIDDLE_CENTER, ALIGN.BOTTOM_CENTER]:
            begin_x = (self.parent.cols / 2) - (width / 2)
        elif self.align in [ALIGN.TOP_RIGHT, ALIGN.MIDDLE_RIGHT, ALIGN.BOTTOM_RIGHT]:
            begin_x = self.parent.cols - width

        return height, width, begin_y, begin_x

    def handle_resize(self):
        height, width, begin_y, begin_x = self.calculate_size()
        self.resize_window(height, width)
        self.move_window(begin_y, begin_x)

    def closed(self):
        return self._closed

    def close(self, *args, **kwargs):
        self._closed = True
        if kwargs.get('call_cb', True):
            self._call_close_cb(*args)
        self.panel.hide()

    def _call_close_cb(self, *args, **kwargs):
        if self.close_cb:
            self.close_cb(*args, base_popup=self, **kwargs)

    @overrides(InputKeyHandler)
    def handle_read(self, c):
        if c == util.KEY_ESC:  # close on esc, no action
            self.close(None)
            return util.ReadState.READ
        return util.ReadState.IGNORED


class SelectablePopup(BaseInputPane, Popup):
    """
    A popup which will let the user select from some of the lines that are added.
    """

    def __init__(
        self,
        parent_mode,
        title,
        selection_cb,
        close_cb=None,
        input_cb=None,
        allow_rearrange=False,
        immediate_action=False,
        **kwargs
    ):
        """
        Args:
            parent_mode (basemode subclass): The mode which the popup will be drawn over
            title (str): the title of the popup window
            selection_cb (func): Function to be called on selection
            close_cb (func, optional): Function to be called when the popup is closed
            input_cb (func, optional): Function to be called on every keyboard input
            allow_rearrange (bool): Allow rearranging the selectable value
            immediate_action (bool): If immediate_action_cb should be called for every action
            kwargs (dict): Arguments passed to Popup

        """
        Popup.__init__(self, parent_mode, title, close_cb=close_cb, **kwargs)
        kwargs.update(
            {'allow_rearrange': allow_rearrange, 'immediate_action': immediate_action}
        )
        BaseInputPane.__init__(self, self, **kwargs)
        self.selection_cb = selection_cb
        self.input_cb = input_cb
        self.hotkeys = {}
        self.cb_arg = {}
        self.cb_args = kwargs.get('cb_args', {})
        if 'base_popup' not in self.cb_args:
            self.cb_args['base_popup'] = self

    @property
    @overrides(BaseWindow)
    def visible_content_pane_height(self):
        """We want to use the Popup property"""
        return Popup.visible_content_pane_height.fget(self)

    def current_selection(self):
        """Returns a tuple of (selected index, selected data)."""
        return self.active_input

    def set_selection(self, index):
        """Set a selected index"""
        self.active_input = index

    def add_line(
        self,
        name,
        string,
        use_underline=True,
        cb_arg=None,
        foreground=None,
        selectable=True,
        selected=False,
        **kwargs
    ):
        hotkey = None
        self.cb_arg[name] = cb_arg
        if use_underline:
            udx = string.find('_')
            if udx >= 0:
                hotkey = string[udx].lower()
                string = (
                    string[:udx]
                    + '{!+underline!}'
                    + string[udx + 1]
                    + '{!-underline!}'
                    + string[udx + 2 :]
                )

        kwargs['selectable'] = selectable
        if foreground:
            kwargs['color_active'] = '%s,white' % foreground
            kwargs['color'] = '%s,black' % foreground

        field = self.add_text_field(name, string, **kwargs)
        if hotkey:
            self.hotkeys[hotkey] = field

        if selected:
            self.set_selection(len(self.inputs) - 1)

    @overrides(Popup, BaseInputPane)
    def handle_read(self, c):
        if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
            for k, v in self.get_values().items():
                if v['active']:
                    if self.selection_cb(k, **dict(self.cb_args, data=self.cb_arg)):
                        self.close(None)
            return util.ReadState.READ
        else:
            ret = BaseInputPane.handle_read(self, c)
            if ret != util.ReadState.IGNORED:
                return ret
            ret = Popup.handle_read(self, c)
            if ret != util.ReadState.IGNORED:
                if self.selection_cb(None):
                    self.close(None)
                return ret

            if self.input_cb:
                self.input_cb(c)

        self.refresh()
        return util.ReadState.IGNORED

    def add_divider(self, message=None, char='-', fill_width=True, color='white'):
        if message is not None:
            fill_width = False
        else:
            message = char
        self.add_divider_field('', message, selectable=False, fill_width=fill_width)


class MessagePopup(Popup, BaseInputPane):
    """
    Popup that just displays a message
    """

    def __init__(
        self,
        parent_mode,
        title,
        message,
        align=ALIGN.DEFAULT,
        height_req=0.75,
        width_req=0.5,
        **kwargs
    ):
        self.message = message
        Popup.__init__(
            self,
            parent_mode,
            title,
            align=align,
            height_req=height_req,
            width_req=width_req,
        )
        BaseInputPane.__init__(self, self, immediate_action=True, **kwargs)
        lns = format_utils.wrap_string(self.message, self.width - 3, 3, True)

        if isinstance(self.height_req, float):
            self.height_req = min(len(lns) + 2, int(parent_mode.rows * self.height_req))

        self.handle_resize()
        self.no_refresh = False
        self.add_text_area('TextMessage', message)

    @overrides(Popup, BaseInputPane)
    def handle_read(self, c):
        ret = BaseInputPane.handle_read(self, c)
        if ret != util.ReadState.IGNORED:
            return ret
        return Popup.handle_read(self, c)


class InputPopup(Popup, BaseInputPane):
    def __init__(self, parent_mode, title, **kwargs):
        Popup.__init__(self, parent_mode, title, **kwargs)
        BaseInputPane.__init__(self, self, **kwargs)
        # We need to replicate some things in order to wrap our inputs
        self.encoding = parent_mode.encoding

    def _handle_callback(self, state_changed=True, close=True):
        self._call_close_cb(self.get_values(), state_changed=state_changed, close=close)

    @overrides(BaseInputPane)
    def immediate_action_cb(self, state_changed=True):
        self._handle_callback(state_changed=state_changed, close=False)

    @overrides(Popup, BaseInputPane)
    def handle_read(self, c):
        ret = BaseInputPane.handle_read(self, c)
        if ret != util.ReadState.IGNORED:
            return ret

        if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
            if self.close_cb:
                self._handle_callback(state_changed=False, close=False)
                util.safe_curs_set(util.Curser.INVISIBLE)
            return util.ReadState.READ
        elif c == util.KEY_ESC:  # close on esc, no action
            self._handle_callback(state_changed=False, close=True)
            self.close(None)
            return util.ReadState.READ

        self.refresh()
        return util.ReadState.READ
