"""Support for controlling Sisyphus Kinetic Art Tables."""
import asyncio
import logging

from sisyphus_control import Table
import voluptuous as vol

from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.typing import ConfigType

_LOGGER = logging.getLogger(__name__)

DATA_SISYPHUS = "sisyphus"
DOMAIN = "sisyphus"

AUTODETECT_SCHEMA = vol.Schema({})

TABLE_SCHEMA = vol.Schema(
    {vol.Required(CONF_NAME): cv.string, vol.Required(CONF_HOST): cv.string}
)

TABLES_SCHEMA = vol.Schema([TABLE_SCHEMA])

CONFIG_SCHEMA = vol.Schema(
    {DOMAIN: vol.Any(AUTODETECT_SCHEMA, TABLES_SCHEMA)}, extra=vol.ALLOW_EXTRA
)

# Silence these loggers by default. Their INFO level is super chatty and we
# only need error-level logging from the integration itself by default.
logging.getLogger("socketio.client").setLevel(logging.CRITICAL + 1)
logging.getLogger("engineio.client").setLevel(logging.CRITICAL + 1)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
    """Set up the sisyphus component."""

    tables = hass.data.setdefault(DATA_SISYPHUS, {})
    table_configs = config[DOMAIN]
    session = async_get_clientsession(hass)

    async def add_table(host, name=None):
        """Add platforms for a single table with the given hostname."""
        tables[host] = TableHolder(hass, session, host, name)

        hass.async_create_task(
            async_load_platform(hass, Platform.LIGHT, DOMAIN, {CONF_HOST: host}, config)
        )
        hass.async_create_task(
            async_load_platform(
                hass, Platform.MEDIA_PLAYER, DOMAIN, {CONF_HOST: host}, config
            )
        )

    if isinstance(table_configs, dict):  # AUTODETECT_SCHEMA
        for ip_address in await Table.find_table_ips(session):
            await add_table(ip_address)
    else:  # TABLES_SCHEMA
        for conf in table_configs:
            await add_table(conf[CONF_HOST], conf[CONF_NAME])

    async def close_tables(*args):
        """Close all table objects."""
        tasks = [table.close() for table in tables.values()]
        if tasks:
            await asyncio.wait(tasks)

    hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_tables)

    return True


class TableHolder:
    """Holds table objects and makes them available to platforms."""

    def __init__(self, hass, session, host, name):
        """Initialize the table holder."""
        self._hass = hass
        self._session = session
        self._host = host
        self._name = name
        self._table = None
        self._table_task = None

    @property
    def available(self):
        """Return true if the table is responding to heartbeats."""
        if self._table_task and self._table_task.done():
            return self._table_task.result().is_connected
        return False

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

    async def get_table(self):
        """Return the Table held by this holder, connecting to it if needed."""
        if self._table:
            return self._table

        if not self._table_task:
            self._table_task = self._hass.async_create_task(self._connect_table())

        return await self._table_task

    async def _connect_table(self):
        try:
            self._table = await Table.connect(self._host, self._session)
            if self._name is None:
                self._name = self._table.name
                _LOGGER.debug("Connected to %s at %s", self._name, self._host)
            return self._table
        finally:
            self._table_task = None

    async def close(self):
        """Close the table held by this holder, if any."""
        if self._table:
            await self._table.close()
            self._table = None
            self._table_task = None
