"""KeypadLink radio button property."""
from itertools import chain

from . import OFF_MASK, ON_MASK, get_usable_value
from ..utils import bit_is_set
from .derived_property import DerivedProperty


def sort_groups(groups):
    """Sort the radio button groups from smallest to largest."""
    for group in groups:
        group.sort()
    groups.sort()
    return groups


def on_mask_name(button):
    """Return the name of the on mask for a button."""
    button_str = "" if button == 1 else f"_{button}"
    return f"{ON_MASK}{button_str}"


def off_mask_name(button):
    """Return the name of the off mask for a button."""
    button_str = "" if button == 1 else f"_{button}"
    return f"{OFF_MASK}{button_str}"


def calc_radio_button_groups(device, device_view=True):
    """Calculate the radio button groups based on the on masks.

    Set `device_view` to `True` to see the actual device setting.
    """
    rb_groups = []
    for button in device.groups:
        if button not in list(chain.from_iterable(rb_groups)):
            if device_view:
                on_mask = device.properties[on_mask_name(button)].value
            else:
                on_mask = get_usable_value(device.properties[on_mask_name(button)])
            if on_mask is None:
                on_mask = 0
            if on_mask != 0:
                rb_group = [button]
                for bit in list(range(0, button - 1)) + list(range(button, 8)):
                    if bit_is_set(on_mask, bit):
                        rb_group.append(bit + 1)
                if len(rb_group) > 1:
                    rb_groups.append(rb_group)
    rb_groups = sort_groups(rb_groups)
    return rb_groups


def set_radio_buttons(device, radio_button_groups):
    """Set the radio button groups in the on and off mask properties."""
    group_lists = []
    for group in radio_button_groups:
        group = list(group)
        if len(group) < 2:
            raise ValueError("Cannot have a radio button group with one button.")
        group = [int(button) for button in group]
        group.sort()
        group_lists.append(group)
    group_lists = sort_groups(group_lists)
    reset_radio_button_groups(device)
    for new_group in group_lists:
        for button in new_group:
            if button not in device.groups:
                raise ValueError(f"Invalid button value: {button}")
        group_dirty = True
        for group in calc_radio_button_groups(device):
            if group == new_group:
                group_dirty = False
        if group_dirty:
            device.set_radio_buttons(new_group)

    clear_buttons = []
    for button in device.groups:
        button_in_group = False
        for group in radio_button_groups:
            if button in group:
                button_in_group = True
        if not button_in_group:
            clear_buttons.append(button)
    if clear_buttons:
        device.clear_radio_buttons(clear_buttons)


def reset_radio_button_groups(device):
    """Clear any dirty on or off mask properties."""
    for button in device.groups:
        device.properties[on_mask_name(button)].new_value = None
        device.properties[off_mask_name(button)].new_value = None


class RadioButtonGroupsProperty(DerivedProperty):
    """KeypadLink radio button groups property."""

    def __init__(
        self,
        device,
        name,
    ):
        """Init the RadioButtonGroupProperty class."""
        super().__init__(device.address, name, list, False, False)
        self._device = device

    @property
    def value(self):
        """Return the value of the radio button groups from the underlying properties."""
        return calc_radio_button_groups(self._device, device_view=True)

    @property
    def new_value(self):
        """Return the modified value of the property."""
        new_value = calc_radio_button_groups(self._device, device_view=False)
        if new_value == self.value:
            return None
        return new_value

    @new_value.setter
    def new_value(self, value):
        """Modify the value of the property.

        Set value to a list of lists (i.e. [[2, 3], [4, 5]]).

        Set `new_value` to `None` to retain the current groups.

        Set `new_value` to an empty set (i.e. `[]`) to remove all groups.
        """

        if value is None:
            reset_radio_button_groups(self._device)
            return

        set_radio_buttons(self._device, value)

    @property
    def is_dirty(self):
        """Return if the underlying property values have changed."""
        on_mask_status = [
            self._device.properties[on_mask_name(group)].is_dirty
            for group in self._device.groups
        ]
        off_mask_status = [
            self._device.properties[off_mask_name(group)].is_dirty
            for group in self._device.groups
        ]
        return any(on_mask_status) or any(off_mask_status)

    @property
    def is_loaded(self):
        """Return if the underlying property values are loaded.."""
        on_mask_status = [
            self._device.properties[on_mask_name(group)].is_loaded
            for group in self._device.groups
        ]
        off_mask_status = [
            self._device.properties[off_mask_name(group)].is_loaded
            for group in self._device.groups
        ]
        return all(on_mask_status) and all(off_mask_status)
