"""
Defines the Button and button-like widgets which allow triggering
events or merely toggling between on-off states.
"""
from functools import partial

import param

from bokeh.models import (
    Button as _BkButton, Toggle as _BkToggle, Dropdown as _BkDropdown
)

from bokeh.events import MenuItemClick, ButtonClick

from .base import Widget


BUTTON_TYPES = ['default', 'primary', 'success', 'warning', 'danger','light']

class _ButtonBase(Widget):

    button_type = param.ObjectSelector(default='default', objects=BUTTON_TYPES, doc="""
        A button theme; should be one of 'default' (white), 'primary'
        (blue), 'success' (green), 'info' (yellow), 'light' (light),
        or 'danger' (red).""")

    _rename = {'name': 'label'}

    __abstract = True


class _ClickButton(_ButtonBase):

    __abstract = True

    _event = 'button_click'

    def _get_model(self, doc, root=None, parent=None, comm=None):
        model = super()._get_model(doc, root, parent, comm)
        if comm:
            model.on_event(self._event, self._comm_event)
        else:
            model.on_event(self._event, partial(self._server_event, doc))
        return model

    def js_on_click(self, args={}, code=""):
        """
        Allows defining a JS callback to be triggered when the button
        is clicked.

        Arguments
        ----------
        args: dict
          A mapping of objects to make available to the JS callback
        code: str
          The Javascript code to execute when the button is clicked.

        Returns
        -------
        callback: Callback
          The Callback which can be used to disable the callback.
        """
        from ..links import Callback
        return Callback(self, code={'event:'+self._event: code}, args=args)

    def jscallback(self, args={}, **callbacks):
        """
        Allows defining a JS callback to be triggered when a property
        changes on the source object. The keyword arguments define the
        properties that trigger a callback and the JS code that gets
        executed.

        Arguments
        ----------
        args: dict
          A mapping of objects to make available to the JS callback
        **callbacks: dict
          A mapping between properties on the source model and the code
          to execute when that property changes

        Returns
        -------
        callback: Callback
          The Callback which can be used to disable the callback.
        """
        from ..links import Callback
        for k, v in list(callbacks.items()):
            if k == 'clicks':
                k = 'event:'+self._event
            callbacks[k] = self._rename.get(v, v)
        return Callback(self, code=callbacks, args=args)


class Button(_ClickButton):
    """
    The `Button` widget allows triggering events when the button is
    clicked.

    The Button provides a `value` parameter, which will toggle from
    `False` to `True` while the click event is being processed

    It also provides an additional `clicks` parameter, that can be
    watched to subscribe to click events.

    Reference: https://panel.holoviz.org/reference/widgets/Button.html#widgets-gallery-button

    :Example:

    >>> pn.widgets.Button(name='Click me', button_type='primary')
    """

    clicks = param.Integer(default=0, doc="""
        Number of clicks (can be listened to)""")

    value = param.Event(doc="""
        Toggles from False to True while the event is being processed.""")

    _rename = {'clicks': None, 'name': 'label', 'value': None}

    _target_transforms = {'event:button_click': None, 'value': None}

    _widget_type = _BkButton

    @property
    def _linkable_params(self):
        return super()._linkable_params + ['value']

    def jslink(self, target, code=None, args=None, bidirectional=False, **links):
        links = {'event:'+self._event if p == 'value' else p: v for p, v in links.items()}
        super().jslink(target, code, args, bidirectional, **links)

    jslink.__doc__ = Widget.jslink.__doc__

    def _process_event(self, event):
        self.param.trigger('value')
        self.clicks += 1

    def on_click(self, callback):
        """
        Register a callback to be executed when the `Button` is clicked.

        The callback is given an `Event` argument declaring the number of clicks

        Arguments
        ---------
        callback: (callable)
            The function to run on click events. Must accept a positional `Event` argument
        """
        self.param.watch(callback, 'clicks', onlychanged=False)


class Toggle(_ButtonBase):
    """The `Toggle` widget allows toggling a single condition between `True`/`False` states.

    This widget is interchangeable with the `Checkbox` widget.

    Reference: https://panel.holoviz.org/reference/widgets/Toggle.html

    :Example:

    >>> Toggle(name='Toggle', button_type='success')
    """

    value = param.Boolean(default=False, doc="""
        Whether the button is currently toggled.""")

    _rename = {'value': 'active', 'name': 'label'}

    _supports_embed = True

    _widget_type = _BkToggle

    def _get_embed_state(self, root, values=None, max_opts=3):
        return (self, self._models[root.ref['id']][0], [False, True],
                lambda x: x.active, 'active', 'cb_obj.active')


class MenuButton(_ClickButton):
    """
    The `MenuButton` widget allows specifying a list of menu items to
    select from triggering events when the button is clicked.

    Unlike other widgets, it does not have a `value`
    parameter. Instead it has a `clicked` parameter that can be
    watched to trigger events and which reports the last clicked menu
    item.

    Reference: https://panel.holoviz.org/reference/widgets/MenuButton.html

    :Example:

    >>> menu_items = [('Option A', 'a'), ('Option B', 'b'), None, ('Option C', 'c')]
    >>> MenuButton(name='Dropdown', items=menu_items, button_type='primary')
    """

    clicked = param.String(default=None, doc="""
      Last menu item that was clicked.""")

    items = param.List(default=[], doc="""
      Menu items in the dropdown. Allows strings, tuples of the form
      (title, value) or Nones to separate groups of items.""")

    split = param.Boolean(default=False, doc="""
      Whether to add separate dropdown area to button.""")

    _widget_type = _BkDropdown

    _rename = {'name': 'label', 'items': 'menu', 'clicked': None}

    _event = 'menu_item_click'

    def _process_event(self, event):
        if isinstance(event, MenuItemClick):
            item = event.item
        elif isinstance(event, ButtonClick):
            item = self.name
        self.clicked = item

    def on_click(self, callback):
        """
        Register a callback to be executed when the button is clicked.

        The callback is given an `Event` argument declaring the number of clicks

        Arguments
        ---------
        callback: (callable)
            The function to run on click events. Must accept a positional `Event` argument
        """
        self.param.watch(callback, 'clicked', onlychanged=False)
