"""Support for Hyperion remotes."""
import json
import logging
import socket

import voluptuous as vol

from homeassistant.components.light import (
    ATTR_BRIGHTNESS,
    ATTR_EFFECT,
    ATTR_HS_COLOR,
    PLATFORM_SCHEMA,
    SUPPORT_BRIGHTNESS,
    SUPPORT_COLOR,
    SUPPORT_EFFECT,
    LightEntity,
)
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util

_LOGGER = logging.getLogger(__name__)

CONF_DEFAULT_COLOR = "default_color"
CONF_PRIORITY = "priority"
CONF_HDMI_PRIORITY = "hdmi_priority"
CONF_EFFECT_LIST = "effect_list"

DEFAULT_COLOR = [255, 255, 255]
DEFAULT_NAME = "Hyperion"
DEFAULT_PORT = 19444
DEFAULT_PRIORITY = 128
DEFAULT_HDMI_PRIORITY = 880
DEFAULT_EFFECT_LIST = [
    "HDMI",
    "Cinema brighten lights",
    "Cinema dim lights",
    "Knight rider",
    "Blue mood blobs",
    "Cold mood blobs",
    "Full color mood blobs",
    "Green mood blobs",
    "Red mood blobs",
    "Warm mood blobs",
    "Police Lights Single",
    "Police Lights Solid",
    "Rainbow mood",
    "Rainbow swirl fast",
    "Rainbow swirl",
    "Random",
    "Running dots",
    "System Shutdown",
    "Snake",
    "Sparks Color",
    "Sparks",
    "Strobe blue",
    "Strobe Raspbmc",
    "Strobe white",
    "Color traces",
    "UDP multicast listener",
    "UDP listener",
    "X-Mas",
]

SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
    {
        vol.Required(CONF_HOST): cv.string,
        vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port,
        vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): vol.All(
            list,
            vol.Length(min=3, max=3),
            [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))],
        ),
        vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
        vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int,
        vol.Optional(
            CONF_HDMI_PRIORITY, default=DEFAULT_HDMI_PRIORITY
        ): cv.positive_int,
        vol.Optional(CONF_EFFECT_LIST, default=DEFAULT_EFFECT_LIST): vol.All(
            cv.ensure_list, [cv.string]
        ),
    }
)


def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up a Hyperion server remote."""
    name = config[CONF_NAME]
    host = config[CONF_HOST]
    port = config[CONF_PORT]
    priority = config[CONF_PRIORITY]
    hdmi_priority = config[CONF_HDMI_PRIORITY]
    default_color = config[CONF_DEFAULT_COLOR]
    effect_list = config[CONF_EFFECT_LIST]

    device = Hyperion(
        name, host, port, priority, default_color, hdmi_priority, effect_list
    )

    if device.setup():
        add_entities([device])


class Hyperion(LightEntity):
    """Representation of a Hyperion remote."""

    def __init__(
        self, name, host, port, priority, default_color, hdmi_priority, effect_list
    ):
        """Initialize the light."""
        self._host = host
        self._port = port
        self._name = name
        self._priority = priority
        self._hdmi_priority = hdmi_priority
        self._default_color = default_color
        self._rgb_color = [0, 0, 0]
        self._rgb_mem = [0, 0, 0]
        self._brightness = 255
        self._icon = "mdi:lightbulb"
        self._effect_list = effect_list
        self._effect = None
        self._skip_update = False

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

    @property
    def brightness(self):
        """Return the brightness of this light between 0..255."""
        return self._brightness

    @property
    def hs_color(self):
        """Return last color value set."""
        return color_util.color_RGB_to_hs(*self._rgb_color)

    @property
    def is_on(self):
        """Return true if not black."""
        return self._rgb_color != [0, 0, 0]

    @property
    def icon(self):
        """Return state specific icon."""
        return self._icon

    @property
    def effect(self):
        """Return the current effect."""
        return self._effect

    @property
    def effect_list(self):
        """Return the list of supported effects."""
        return self._effect_list

    @property
    def supported_features(self):
        """Flag supported features."""
        return SUPPORT_HYPERION

    def turn_on(self, **kwargs):
        """Turn the lights on."""
        if ATTR_HS_COLOR in kwargs:
            rgb_color = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
        elif self._rgb_mem == [0, 0, 0]:
            rgb_color = self._default_color
        else:
            rgb_color = self._rgb_mem

        brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness)

        if ATTR_EFFECT in kwargs:
            self._skip_update = True
            self._effect = kwargs[ATTR_EFFECT]
            if self._effect == "HDMI":
                self.json_request({"command": "clearall"})
                self._icon = "mdi:video-input-hdmi"
                self._brightness = 255
                self._rgb_color = [125, 125, 125]
            else:
                self.json_request(
                    {
                        "command": "effect",
                        "priority": self._priority,
                        "effect": {"name": self._effect},
                    }
                )
                self._icon = "mdi:lava-lamp"
                self._rgb_color = [175, 0, 255]
            return

        cal_color = [int(round(x * float(brightness) / 255)) for x in rgb_color]
        self.json_request(
            {"command": "color", "priority": self._priority, "color": cal_color}
        )

    def turn_off(self, **kwargs):
        """Disconnect all remotes."""
        self.json_request({"command": "clearall"})
        self.json_request(
            {"command": "color", "priority": self._priority, "color": [0, 0, 0]}
        )

    def update(self):
        """Get the lights status."""
        # postpone the immediate state check for changes that take time
        if self._skip_update:
            self._skip_update = False
            return
        response = self.json_request({"command": "serverinfo"})
        if response:
            # workaround for outdated Hyperion
            if "activeLedColor" not in response["info"]:
                self._rgb_color = self._default_color
                self._rgb_mem = self._default_color
                self._brightness = 255
                self._icon = "mdi:lightbulb"
                self._effect = None
                return
            # Check if Hyperion is in ambilight mode trough an HDMI grabber
            try:
                active_priority = response["info"]["priorities"][0]["priority"]
                if active_priority == self._hdmi_priority:
                    self._brightness = 255
                    self._rgb_color = [125, 125, 125]
                    self._icon = "mdi:video-input-hdmi"
                    self._effect = "HDMI"
                    return
            except (KeyError, IndexError):
                pass

            led_color = response["info"]["activeLedColor"]
            if not led_color or led_color[0]["RGB Value"] == [0, 0, 0]:
                # Get the active effect
                if response["info"].get("activeEffects"):
                    self._rgb_color = [175, 0, 255]
                    self._icon = "mdi:lava-lamp"
                    try:
                        s_name = response["info"]["activeEffects"][0]["script"]
                        s_name = s_name.split("/")[-1][:-3].split("-")[0]
                        self._effect = [
                            x for x in self._effect_list if s_name.lower() in x.lower()
                        ][0]
                    except (KeyError, IndexError):
                        self._effect = None
                # Bulb off state
                else:
                    self._rgb_color = [0, 0, 0]
                    self._icon = "mdi:lightbulb"
                    self._effect = None
            else:
                # Get the RGB color
                self._rgb_color = led_color[0]["RGB Value"]
                self._brightness = max(self._rgb_color)
                self._rgb_mem = [
                    int(round(float(x) * 255 / self._brightness))
                    for x in self._rgb_color
                ]
                self._icon = "mdi:lightbulb"
                self._effect = None

    def setup(self):
        """Get the hostname of the remote."""
        response = self.json_request({"command": "serverinfo"})
        if response:
            if self._name == self._host:
                self._name = response["info"]["hostname"]
            return True
        return False

    def json_request(self, request, wait_for_response=False):
        """Communicate with the JSON server."""
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)

        try:
            sock.connect((self._host, self._port))
        except OSError:
            sock.close()
            return False

        sock.send(bytearray(f"{json.dumps(request)}\n", "utf-8"))
        try:
            buf = sock.recv(4096)
        except socket.timeout:
            # Something is wrong, assume it's offline
            sock.close()
            return False

        # Read until a newline or timeout
        buffering = True
        while buffering:
            if "\n" in str(buf, "utf-8"):
                response = str(buf, "utf-8").split("\n")[0]
                buffering = False
            else:
                try:
                    more = sock.recv(4096)
                except socket.timeout:
                    more = None
                if not more:
                    buffering = False
                    response = str(buf, "utf-8")
                else:
                    buf += more

        sock.close()
        return json.loads(response)