"""Device class"""
import asyncio
import logging

from .exceptions import PykulerskyException

_LOGGER = logging.getLogger(__name__)

CHARACTERISTIC_COMMAND_COLOR = "8d96b002-0002-64c2-0001-9acc4838521c"

# Default to a 5s timeout on remote calls
DEFAULT_TIMEOUT = 5


class Light():
    """Represents one connected light"""

    def __init__(self, address_or_ble_device, name=None, *args,
                 default_timeout=DEFAULT_TIMEOUT):
        import bleak

        self._address = getattr(
            address_or_ble_device,
            'address',
            address_or_ble_device
        )
        self._name = name
        self._client = bleak.BleakClient(address_or_ble_device)
        self._default_timeout = DEFAULT_TIMEOUT

    @property
    def address(self):
        """Return the mac address of this light."""
        return self._address

    @property
    def name(self):
        """Return the discovered name of this light."""
        return self._name

    async def is_connected(self, *args, timeout=None):
        """Returns true if the light is connected."""
        try:
            return await asyncio.wait_for(
                self._client.is_connected(),
                self._default_timeout if timeout is None else timeout)
        except asyncio.TimeoutError:
            return False
        except Exception as ex:
            raise PykulerskyException() from ex

    async def connect(self, *args, timeout=None):
        """Connect to this light"""
        _LOGGER.debug("Connecting to %s", self._address)

        try:
            await asyncio.wait_for(
                self._client.connect(),
                self._default_timeout if timeout is None else timeout)
        except Exception as ex:
            raise PykulerskyException() from ex

        _LOGGER.debug("Connected to %s", self._address)

    async def disconnect(self, *args, timeout=None):
        """Close the connection to the light."""
        _LOGGER.debug("Disconnecting from %s", self._address)
        try:
            await asyncio.wait_for(
                self._client.disconnect(),
                self._default_timeout if timeout is None else timeout)
        except Exception as ex:
            raise PykulerskyException() from ex
        _LOGGER.debug("Disconnected from %s", self._address)

    async def _do_set_color(self, r, g, b, w):
        """Set the color of the light"""
        for value in (r, g, b, w):
            if not 0 <= value <= 255:
                raise ValueError(
                    "Value {} is outside the valid range of 0-255")

        old_color = await self._do_get_color()
        was_on = max(old_color) > 0

        _LOGGER.debug("Changing color of %s to #%02x%02x%02x%02x",
                      self.address, r, g, b, w)

        if r == 0 and g == 0 and b == 0 and w == 0:
            color_string = b'\x32\xFF\xFF\xFF\xFF'
        else:
            if not was_on and w > 0:
                # These lights have a firmware bug. When turning the light on
                # from off, the white channel is broken until it is first set
                # to zero. If the light was off, first apply the color with a
                # zero white channel, then write the actual color we want.
                color_string = b'\x02' + bytes((r, g, b, 0))
                await self._write(CHARACTERISTIC_COMMAND_COLOR, color_string)
            color_string = b'\x02' + bytes((r, g, b, w))

        await self._write(CHARACTERISTIC_COMMAND_COLOR, color_string)
        _LOGGER.debug("Changed color of %s", self.address)

    async def set_color(self, r, g, b, w, *args, timeout=None):
        """Set the color of the light

        Accepts red, green, blue, and white values from 0-255
        """
        try:
            await asyncio.wait_for(
                self._do_set_color(r, g, b, w),
                self._default_timeout if timeout is None else timeout)
        except asyncio.TimeoutError as ex:
            raise PykulerskyException() from ex

    async def _do_get_color(self):
        """Get the current color of the light"""
        color_string = await self._read(CHARACTERISTIC_COMMAND_COLOR)

        on_off_value = int(color_string[0])

        r = int(color_string[1])
        g = int(color_string[2])
        b = int(color_string[3])
        w = int(color_string[4])

        if on_off_value == 0x32:
            color = (0, 0, 0, 0)
        else:
            color = (r, g, b, w)

        _LOGGER.debug("Got color of %s: %s", self.address, color)

        return color

    async def get_color(self, *args, timeout=None):
        """Get the current color of the light"""
        try:
            return await asyncio.wait_for(
                self._do_get_color(),
                self._default_timeout if timeout is None else timeout)
        except asyncio.TimeoutError as ex:
            raise PykulerskyException() from ex

    async def _read(self, uuid):
        """Internal method to read from the device"""
        _LOGGER.debug("Reading from characteristic %s", uuid)
        try:
            value = await self._client.read_gatt_char(uuid)
        except Exception as ex:
            raise PykulerskyException() from ex
        _LOGGER.debug("Read 0x%s from characteristic %s", value.hex(), uuid)

        return value

    async def _write(self, uuid, value):
        """Internal method to write to the device"""
        _LOGGER.debug("Writing 0x%s to characteristic %s", value.hex(), uuid)
        try:
            await self._client.write_gatt_char(uuid, bytearray(value))
        except Exception as ex:
            raise PykulerskyException() from ex
        _LOGGER.debug("Wrote 0x%s to characteristic %s", value.hex(), uuid)
