import ipaddress
from typing import List

from platypush.plugins import action
from platypush.plugins.switch import SwitchPlugin
from platypush.utils.workers import Workers
from .lib import WemoRunner
from .scanner import Scanner


class SwitchWemoPlugin(SwitchPlugin):
    """
    Plugin to control a Belkin WeMo smart switches
    (https://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/)

    Requires:

        * **requests** (``pip install requests``)
    """

    _default_port = 49153

    def __init__(self, devices=None, netmask: str = None, port: int = _default_port, **kwargs):
        """
        :param devices: List of IP addresses or name->address map containing the WeMo Switch devices to control.
            This plugin previously used ouimeaux for auto-discovery but it's been dropped because
            1. too slow 2. too heavy 3. auto-discovery failed too often.
        :type devices: list or dict

        :param netmask: Alternatively to a list of static IP->name pairs,  you can specify the network mask where
            the devices should be scanned (e.g. '192.168.1.0/24')

        :param port: Port where the WeMo devices are expected to expose the RPC/XML over HTTP service (default: 49153)
        """

        super().__init__(**kwargs)
        self.port = port
        self.netmask = netmask
        self._devices = {}
        self._init_devices(devices)

    def _init_devices(self, devices):
        if devices:
            self._devices.update(devices if isinstance(devices, dict) else
                                 {addr: addr for addr in devices})
        else:
            self._devices = {}

        self._addresses = set(self._devices.values())

    @property
    def switches(self) -> List[dict]:
        """
        Get the list of available devices
        :returns: The list of devices.

            .. code-block:: json

                [
                    {
                        "ip": "192.168.1.123",
                        "name": "Switch 1",
                        "on": true
                    },
                    {
                        "ip": "192.168.1.124",
                        "name": "Switch 2",
                        "on": false
                    }
                ]

        """

        return [
                self.status(device).output
                for device in self._devices.values()
        ]

    def _get_address(self, device: str) -> str:
        if device not in self._addresses:
            try:
                return self._devices[device]
            except KeyError:
                pass

        return device

    @action
    def status(self, device: str = None, *args, **kwargs):
        devices = {device: device} if device else self._devices.copy()

        ret = [
            {
                'id': addr,
                'ip': addr,
                'name': name if name != addr else WemoRunner.get_name(addr),
                'on': WemoRunner.get_state(addr),
            }
            for (name, addr) in devices.items()
        ]

        return ret[0] if device else ret

    @action
    def on(self, device: str, **kwargs):
        """
        Turn a switch on

        :param device: Device name or address
        """
        device = self._get_address(device)
        WemoRunner.on(device)
        return self.status(device)

    @action
    def off(self, device: str, **kwargs):
        """
        Turn a switch off

        :param device: Device name or address
        """
        device = self._get_address(device)
        WemoRunner.off(device)
        return self.status(device)

    @action
    def toggle(self, device: str, *args, **kwargs):
        """
        Toggle a device on/off state

        :param device: Device name or address
        """
        device = self._get_address(device)
        WemoRunner.toggle(device)
        return self.status(device)

    @action
    def get_state(self, device: str):
        """
        Get the on state of a device (True/False)

        :param device: Device name or address
        """
        device = self._get_address(device)
        return WemoRunner.get_state(device)

    @action
    def get_name(self, device: str):
        """
        Get the friendly name of a device

        :param device: Device name or address
        """
        device = self._get_address(device)
        return WemoRunner.get_name(device)

    @action
    def scan(self, netmask: str = None):
        netmask = netmask or self.netmask
        assert netmask, 'Scan not supported: No netmask specified'

        workers = Workers(10, Scanner, port=self.port)
        with workers:
            for addr in ipaddress.IPv4Network(netmask):
                workers.put(addr.exploded)

        devices = {
            dev.name: dev.addr
            for dev in workers.responses
        }

        self._init_devices(devices)
        return self.status()


# vim:sw=4:ts=4:et:
