"""Support for interfacing with WS66i 6 zone home audio controller."""
from pyws66i import WS66i, ZoneStatus

from homeassistant.components.media_player import (
    MediaPlayerEntity,
    MediaPlayerEntityFeature,
    MediaPlayerState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, MAX_VOL
from .coordinator import Ws66iDataUpdateCoordinator
from .models import Ws66iData

PARALLEL_UPDATES = 1


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the WS66i 6-zone amplifier platform from a config entry."""
    ws66i_data: Ws66iData = hass.data[DOMAIN][config_entry.entry_id]

    # Build and add the entities from the data class
    async_add_entities(
        Ws66iZone(
            device=ws66i_data.device,
            ws66i_data=ws66i_data,
            entry_id=config_entry.entry_id,
            zone_id=zone_id,
            data_idx=idx,
            coordinator=ws66i_data.coordinator,
        )
        for idx, zone_id in enumerate(ws66i_data.zones)
    )


class Ws66iZone(CoordinatorEntity[Ws66iDataUpdateCoordinator], MediaPlayerEntity):
    """Representation of a WS66i amplifier zone."""

    _attr_has_entity_name = True
    _attr_name = None

    def __init__(
        self,
        device: WS66i,
        ws66i_data: Ws66iData,
        entry_id: str,
        zone_id: int,
        data_idx: int,
        coordinator: Ws66iDataUpdateCoordinator,
    ) -> None:
        """Initialize a zone entity."""
        super().__init__(coordinator)
        self._ws66i: WS66i = device
        self._ws66i_data: Ws66iData = ws66i_data
        self._zone_id: int = zone_id
        self._zone_id_idx: int = data_idx
        self._status: ZoneStatus = coordinator.data[data_idx]
        self._attr_source_list = ws66i_data.sources.name_list
        self._attr_unique_id = f"{entry_id}_{self._zone_id}"
        self._attr_supported_features = (
            MediaPlayerEntityFeature.VOLUME_MUTE
            | MediaPlayerEntityFeature.VOLUME_SET
            | MediaPlayerEntityFeature.VOLUME_STEP
            | MediaPlayerEntityFeature.TURN_ON
            | MediaPlayerEntityFeature.TURN_OFF
            | MediaPlayerEntityFeature.SELECT_SOURCE
        )
        self._attr_device_info = DeviceInfo(
            identifiers={(DOMAIN, str(self.unique_id))},
            name=f"Zone {self._zone_id}",
            manufacturer="Soundavo",
            model="WS66i 6-Zone Amplifier",
        )
        self._set_attrs_from_status()

    @callback
    def _handle_coordinator_update(self) -> None:
        """Handle updated data from the coordinator."""
        # This will be called for each of the entities after the coordinator
        # finishes executing _async_update_data()

        # Save a reference to the zone status that this entity represents
        self._status = self.coordinator.data[self._zone_id_idx]
        self._set_attrs_from_status()

        # Parent will notify HA of the update
        super()._handle_coordinator_update()

    @callback
    def _set_attrs_from_status(self) -> None:
        status = self._status
        sources = self._ws66i_data.sources.id_name
        self._attr_state = MediaPlayerState.ON if status.power else MediaPlayerState.OFF
        self._attr_volume_level = status.volume / float(MAX_VOL)
        self._attr_is_volume_muted = status.mute
        self._attr_source = self._attr_media_title = sources[status.source]

    @callback
    def _async_update_attrs_write_ha_state(self) -> None:
        self._set_attrs_from_status()
        self.async_write_ha_state()

    async def async_select_source(self, source: str) -> None:
        """Set input source."""
        idx = self._ws66i_data.sources.name_id[source]
        await self.hass.async_add_executor_job(
            self._ws66i.set_source, self._zone_id, idx
        )
        self._status.source = idx
        self._async_update_attrs_write_ha_state()

    async def async_turn_on(self) -> None:
        """Turn the media player on."""
        await self.hass.async_add_executor_job(
            self._ws66i.set_power, self._zone_id, True
        )
        self._status.power = True
        self._async_update_attrs_write_ha_state()

    async def async_turn_off(self) -> None:
        """Turn the media player off."""
        await self.hass.async_add_executor_job(
            self._ws66i.set_power, self._zone_id, False
        )
        self._status.power = False
        self._async_update_attrs_write_ha_state()

    async def async_mute_volume(self, mute: bool) -> None:
        """Mute (true) or unmute (false) media player."""
        await self.hass.async_add_executor_job(
            self._ws66i.set_mute, self._zone_id, mute
        )
        self._status.mute = bool(mute)
        self._async_update_attrs_write_ha_state()

    async def async_set_volume_level(self, volume: float) -> None:
        """Set volume level, range 0..1."""
        await self.hass.async_add_executor_job(self._set_volume, int(volume * MAX_VOL))
        self._async_update_attrs_write_ha_state()

    async def async_volume_up(self) -> None:
        """Volume up the media player."""
        await self.hass.async_add_executor_job(
            self._set_volume, min(self._status.volume + 1, MAX_VOL)
        )
        self._async_update_attrs_write_ha_state()

    async def async_volume_down(self) -> None:
        """Volume down media player."""
        await self.hass.async_add_executor_job(
            self._set_volume, max(self._status.volume - 1, 0)
        )
        self._async_update_attrs_write_ha_state()

    def _set_volume(self, volume: int) -> None:
        """Set the volume of the media player."""
        # Can't set a new volume level when this zone is muted.
        # Follow behavior of keypads, where zone is unmuted when volume changes.
        if self._status.mute:
            self._ws66i.set_mute(self._zone_id, False)
            self._status.mute = False

        self._ws66i.set_volume(self._zone_id, volume)
        self._status.volume = volume