#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# 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, move_cursor
from deluge.ui.console.utils import curses_util as util
from deluge.ui.console.widgets.fields import (
    CheckedInput,
    CheckedPlusInput,
    ComboInput,
    DividerField,
    FloatSpinInput,
    Header,
    InfoField,
    IntSpinInput,
    NoInputField,
    SelectInput,
    TextArea,
    TextField,
    TextInput,
)

try:
    import curses
except ImportError:
    pass

log = logging.getLogger(__name__)


class BaseInputPane(InputKeyHandler):
    def __init__(
        self,
        mode,
        allow_rearrange=False,
        immediate_action=False,
        set_first_input_active=True,
        border_off_west=0,
        border_off_north=0,
        border_off_east=0,
        border_off_south=0,
        active_wrap=False,
        **kwargs
    ):
        InputKeyHandler.__init__(self)
        self.inputs = []
        self.mode = mode
        self.active_input = 0
        self.set_first_input_active = set_first_input_active
        self.allow_rearrange = allow_rearrange
        self.immediate_action = immediate_action
        self.move_active_many = 4
        self.active_wrap = active_wrap
        self.lineoff = 0
        self.border_off_west = border_off_west
        self.border_off_north = border_off_north
        self.border_off_east = border_off_east
        self.border_off_south = border_off_south
        self.last_lineoff_move = 0

        if not hasattr(self, 'visible_content_pane_height'):
            log.error(
                'The class "%s" does not have the attribute "%s" required by super class "%s"',
                self.__class__.__name__,
                'visible_content_pane_height',
                BaseInputPane.__name__,
            )
            raise AttributeError('visible_content_pane_height')

    @property
    def visible_content_pane_width(self):
        return self.mode.width

    def add_spaces(self, num):
        string = ''
        for i in range(num):
            string += '\n'

        self.add_text_area('space %d' % len(self.inputs), string)

    def add_text(self, string):
        self.add_text_area('', string)

    def move(self, r, c):
        self._cursor_row = r
        self._cursor_col = c

    def get_input(self, name):
        for e in self.inputs:
            if e.name == name:
                return e

    def _add_input(self, input_element):
        for e in self.inputs:
            if isinstance(e, NoInputField):
                continue
            if e.name == input_element.name:
                import traceback

                log.warning(
                    'Input element with name "%s" already exists in input pane (%s):\n%s',
                    input_element.name,
                    e,
                    ''.join(traceback.format_stack(limit=5)),
                )
                return

        self.inputs.append(input_element)
        if self.set_first_input_active and input_element.selectable():
            self.active_input = len(self.inputs) - 1
            self.set_first_input_active = False
        return input_element

    def add_header(self, header, space_above=False, space_below=False, **kwargs):
        return self._add_input(Header(self, header, space_above, space_below, **kwargs))

    def add_info_field(self, name, label, value):
        return self._add_input(InfoField(self, name, label, value))

    def add_text_field(self, name, message, selectable=True, col='+1', **kwargs):
        return self._add_input(
            TextField(self, name, message, selectable=selectable, col=col, **kwargs)
        )

    def add_text_area(self, name, message, **kwargs):
        return self._add_input(TextArea(self, name, message, **kwargs))

    def add_divider_field(self, name, message, **kwargs):
        return self._add_input(DividerField(self, name, message, **kwargs))

    def add_text_input(self, name, message, value='', col='+1', **kwargs):
        """
        Add a text input field

        :param message: string to display above the input field
        :param name: name of the field, for the return callback
        :param value: initial value of the field
        :param complete: should completion be run when tab is hit and this field is active
        """
        return self._add_input(
            TextInput(
                self,
                name,
                message,
                self.move,
                self.visible_content_pane_width,
                value,
                col=col,
                **kwargs
            )
        )

    def add_select_input(self, name, message, opts, vals, default_index=0, **kwargs):
        return self._add_input(
            SelectInput(self, name, message, opts, vals, default_index, **kwargs)
        )

    def add_checked_input(self, name, message, checked=False, col='+1', **kwargs):
        return self._add_input(
            CheckedInput(self, name, message, checked=checked, col=col, **kwargs)
        )

    def add_checkedplus_input(
        self, name, message, child, checked=False, col='+1', **kwargs
    ):
        return self._add_input(
            CheckedPlusInput(
                self, name, message, child, checked=checked, col=col, **kwargs
            )
        )

    def add_float_spin_input(self, name, message, value=0.0, col='+1', **kwargs):
        return self._add_input(
            FloatSpinInput(self, name, message, self.move, value, col=col, **kwargs)
        )

    def add_int_spin_input(self, name, message, value=0, col='+1', **kwargs):
        return self._add_input(
            IntSpinInput(self, name, message, self.move, value, col=col, **kwargs)
        )

    def add_combo_input(self, name, message, choices, col='+1', **kwargs):
        return self._add_input(
            ComboInput(self, name, message, choices, col=col, **kwargs)
        )

    @overrides(InputKeyHandler)
    def handle_read(self, c):
        if not self.inputs:  # no inputs added yet
            return util.ReadState.IGNORED
        ret = self.inputs[self.active_input].handle_read(c)
        if ret != util.ReadState.IGNORED:
            if self.immediate_action:
                self.immediate_action_cb(
                    state_changed=False if ret == util.ReadState.READ else True
                )
            return ret

        ret = util.ReadState.READ

        if c == curses.KEY_UP:
            self.move_active_up(1)
        elif c == curses.KEY_DOWN:
            self.move_active_down(1)
        elif c == curses.KEY_HOME:
            self.move_active_up(len(self.inputs))
        elif c == curses.KEY_END:
            self.move_active_down(len(self.inputs))
        elif c == curses.KEY_PPAGE:
            self.move_active_up(self.move_active_many)
        elif c == curses.KEY_NPAGE:
            self.move_active_down(self.move_active_many)
        elif c == util.KEY_ALT_AND_ARROW_UP:
            self.lineoff = max(self.lineoff - 1, 0)
        elif c == util.KEY_ALT_AND_ARROW_DOWN:
            tot_height = self.get_content_height()
            self.lineoff = min(
                self.lineoff + 1, tot_height - self.visible_content_pane_height
            )
        elif c == util.KEY_CTRL_AND_ARROW_UP:
            if not self.allow_rearrange:
                return ret
            val = self.inputs.pop(self.active_input)
            self.active_input -= 1
            self.inputs.insert(self.active_input, val)
            if self.immediate_action:
                self.immediate_action_cb(state_changed=True)
        elif c == util.KEY_CTRL_AND_ARROW_DOWN:
            if not self.allow_rearrange:
                return ret
            val = self.inputs.pop(self.active_input)
            self.active_input += 1
            self.inputs.insert(self.active_input, val)
            if self.immediate_action:
                self.immediate_action_cb(state_changed=True)
        else:
            ret = util.ReadState.IGNORED
        return ret

    def get_values(self):
        vals = {}
        for i, ipt in enumerate(self.inputs):
            if not ipt.has_input():
                continue
            vals[ipt.name] = {
                'value': ipt.get_value(),
                'order': i,
                'active': self.active_input == i,
            }
        return vals

    def immediate_action_cb(self, state_changed=True):
        pass

    def move_active(self, direction, amount):
        """
        direction == -1: Up
        direction ==  1: Down

        """
        self.last_lineoff_move = direction * amount

        if direction > 0:
            if self.active_wrap:
                limit = self.active_input - 1
                if limit < 0:
                    limit = len(self.inputs) + limit
            else:
                limit = len(self.inputs) - 1
        else:
            limit = 0
            if self.active_wrap:
                limit = self.active_input + 1

        def next_move(nc, direction, limit):
            next_index = nc
            while next_index != limit:
                next_index += direction
                if direction > 0:
                    next_index %= len(self.inputs)
                elif next_index < 0:
                    next_index = len(self.inputs) + next_index

                if self.inputs[next_index].selectable():
                    return next_index
                if next_index == limit:
                    return nc
            return nc

        next_sel = self.active_input
        for a in range(amount):
            cur_sel = next_sel
            next_sel = next_move(next_sel, direction, limit)
            if cur_sel == next_sel:
                tot_height = (
                    self.get_content_height()
                    + self.border_off_north
                    + self.border_off_south
                )
                if direction > 0:
                    self.lineoff = min(
                        self.lineoff + 1, tot_height - self.visible_content_pane_height
                    )
                else:
                    self.lineoff = max(self.lineoff - 1, 0)

        if next_sel is not None:
            self.active_input = next_sel

    def move_active_up(self, amount):
        self.move_active(-1, amount)
        if self.immediate_action:
            self.immediate_action_cb(state_changed=False)

    def move_active_down(self, amount):
        self.move_active(1, amount)
        if self.immediate_action:
            self.immediate_action_cb(state_changed=False)

    def get_content_height(self):
        height = 0
        for i, ipt in enumerate(self.inputs):
            if ipt.depend_skip():
                continue
            height += ipt.height
        return height

    def ensure_active_visible(self):
        start_row = 0
        end_row = self.border_off_north
        for i, ipt in enumerate(self.inputs):
            if ipt.depend_skip():
                continue
            start_row = end_row
            end_row += ipt.height
            if i != self.active_input or not ipt.has_input():
                continue
            height = self.visible_content_pane_height
            if end_row > height + self.lineoff:
                self.lineoff += end_row - (
                    height + self.lineoff
                )  # Correct result depends on paranthesis
            elif start_row < self.lineoff:
                self.lineoff -= self.lineoff - start_row
            break

    def render_inputs(self, focused=False):
        self._cursor_row = -1
        self._cursor_col = -1
        util.safe_curs_set(util.Curser.INVISIBLE)

        self.ensure_active_visible()

        crow = self.border_off_north
        for i, ipt in enumerate(self.inputs):
            if ipt.depend_skip():
                continue
            col = self.border_off_west
            field_width = self.width - self.border_off_east - self.border_off_west
            cursor_offset = self.border_off_west

            if ipt.default_col != -1:
                default_col = int(ipt.default_col)
                if isinstance(ipt.default_col, ''.__class__) and ipt.default_col[0] in [
                    '+',
                    '-',
                ]:
                    col += default_col
                    cursor_offset += default_col
                    field_width -= default_col  # Increase to col must be reflected here
                else:
                    col = default_col
            crow += ipt.render(
                self.screen,
                crow,
                width=field_width,
                active=i == self.active_input,
                focused=focused,
                col=col,
                cursor_offset=cursor_offset,
            )

        if self._cursor_row >= 0:
            util.safe_curs_set(util.Curser.VERY_VISIBLE)
            move_cursor(self.screen, self._cursor_row, self._cursor_col)
