"""Methods and classes related to executing Z-Wave commands and publishing these to hass."""
import logging

from openzwavemqtt.const import CommandClass, ValueType
import voluptuous as vol

from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from . import const

_LOGGER = logging.getLogger(__name__)


class ZWaveServices:
    """Class that holds our services ( Zwave Commands) that should be published to hass."""

    def __init__(self, hass, manager):
        """Initialize with both hass and ozwmanager objects."""
        self._hass = hass
        self._manager = manager

    @callback
    def async_register(self):
        """Register all our services."""
        self._hass.services.async_register(
            const.DOMAIN,
            const.SERVICE_ADD_NODE,
            self.async_add_node,
            schema=vol.Schema(
                {
                    vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int),
                    vol.Optional(const.ATTR_SECURE, default=False): vol.Coerce(bool),
                }
            ),
        )
        self._hass.services.async_register(
            const.DOMAIN,
            const.SERVICE_REMOVE_NODE,
            self.async_remove_node,
            schema=vol.Schema(
                {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)}
            ),
        )

        self._hass.services.async_register(
            const.DOMAIN,
            const.SERVICE_SET_CONFIG_PARAMETER,
            self.async_set_config_parameter,
            schema=vol.Schema(
                {
                    vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int),
                    vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
                    vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int),
                    vol.Required(const.ATTR_CONFIG_VALUE): vol.Any(
                        vol.Coerce(int), cv.string
                    ),
                }
            ),
        )

    @callback
    def async_set_config_parameter(self, service):
        """Set a config parameter to a node."""
        instance_id = service.data[const.ATTR_INSTANCE_ID]
        node_id = service.data[const.ATTR_NODE_ID]
        param = service.data[const.ATTR_CONFIG_PARAMETER]
        selection = service.data[const.ATTR_CONFIG_VALUE]
        payload = None

        node = self._manager.get_instance(instance_id).get_node(node_id).values()

        for value in node:
            if (
                value.command_class != CommandClass.CONFIGURATION
                or value.index != param
            ):
                continue

            if value.type == ValueType.BOOL:
                payload = selection == "True"

            if value.type == ValueType.LIST:
                # accept either string from the list value OR the int value
                if isinstance(selection, int):
                    if selection > value.max or selection < value.min:
                        _LOGGER.error(
                            "Value %s out of range for parameter %s (Min: %s Max: %s)",
                            selection,
                            param,
                            value.min,
                            value.max,
                        )
                        return
                    payload = int(selection)

                # iterate list labels to get value
                for selected in value.value["List"]:
                    if selected["Label"] != selection:
                        continue
                    payload = int(selected["Value"])

                if payload is None:
                    _LOGGER.error(
                        "Invalid value %s for parameter %s", selection, param,
                    )
                    return

            if value.type == ValueType.BUTTON:
                # Unsupported at this time
                _LOGGER.info("Button type not supported yet")
                return

            if value.type == ValueType.STRING:
                payload = selection

            if value.type == ValueType.INT or value.type == ValueType.BYTE:
                if selection > value.max or selection < value.min:
                    _LOGGER.error(
                        "Value %s out of range for parameter %s (Min: %s Max: %s)",
                        selection,
                        param,
                        value.min,
                        value.max,
                    )
                    return
                payload = int(selection)

            value.send_value(payload)  # send the payload
            _LOGGER.info(
                "Setting configuration parameter %s on Node %s with value %s",
                param,
                node_id,
                payload,
            )
            return

    @callback
    def async_add_node(self, service):
        """Enter inclusion mode on the controller."""
        instance_id = service.data[const.ATTR_INSTANCE_ID]
        secure = service.data[const.ATTR_SECURE]
        instance = self._manager.get_instance(instance_id)
        instance.add_node(secure)

    @callback
    def async_remove_node(self, service):
        """Enter exclusion mode on the controller."""
        instance_id = service.data[const.ATTR_INSTANCE_ID]
        instance = self._manager.get_instance(instance_id)
        instance.remove_node()
