#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module implements the handler for state of Denon AVR receivers.

:copyright: (c) 2021 by Oliver Goetz.
:license: MIT, see LICENSE for more details.
"""

import asyncio
import logging
import time
from collections.abc import Hashable
from typing import Optional

import attr

from .appcommand import AppCommandCmdParam, AppCommands
from .const import DENON_ATTR_SETATTR
from .exceptions import AvrCommandError, AvrIncompleteResponseError, AvrProcessingError
from .foundation import DenonAVRFoundation, convert_string_int_bool

_LOGGER = logging.getLogger(__name__)


@attr.s(auto_attribs=True, on_setattr=DENON_ATTR_SETATTR)
class DenonAVRToneControl(DenonAVRFoundation):
    """This class implements tone control functions of Denon AVR receiver."""

    _support_tone_control: Optional[bool] = attr.ib(
        converter=attr.converters.optional(bool), default=None
    )
    _tone_control_status: Optional[bool] = attr.ib(
        converter=attr.converters.optional(convert_string_int_bool), default=None
    )
    _tone_control_adjust: Optional[bool] = attr.ib(
        converter=attr.converters.optional(convert_string_int_bool), default=None
    )
    _bass: Optional[int] = attr.ib(
        converter=attr.converters.optional(int), default=None
    )
    _bass_level: Optional[str] = attr.ib(
        converter=attr.converters.optional(str), default=None
    )
    _treble: Optional[int] = attr.ib(
        converter=attr.converters.optional(int), default=None
    )
    _treble_level: Optional[str] = attr.ib(
        converter=attr.converters.optional(str), default=None
    )
    _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock))

    # Update tags for attributes
    # AppCommand.xml interface
    appcommand_attrs = {AppCommands.GetToneControl: None}

    async def async_setup(self) -> None:
        """Ensure that the instance is initialized."""
        async with self._setup_lock:
            _LOGGER.debug("Starting tone control setup")

            # The first update determines if sound mode is supported
            await self.async_update_tone_control()

            # Add tags for a potential AppCommand.xml update
            if self.support_tone_control:
                for tag in self.appcommand_attrs:
                    self._device.api.add_appcommand_update_tag(tag)

            self._device.telnet_api.register_callback(
                "PS", self._async_sound_detail_callback
            )

            self._is_setup = True
            _LOGGER.debug("Finished tone control setup")

    async def _async_sound_detail_callback(
        self, zone: str, event: str, parameter: str
    ) -> None:
        """Handle a sound detail change event."""
        if self._device.zone != zone:
            return

        if parameter[0:3] == "BAS":
            self._bass = int(parameter[4:]) - 44
            self._bass_level = f"{int(parameter[4:]) - 50}dB"
        elif parameter[0:3] == "TRE":
            self._treble = int(parameter[4:]) - 44
            self._treble_level = f"{int(parameter[4:]) - 50}dB"
        elif parameter == "TONE CTRL OFF":
            self._tone_control_adjust = False
            self._tone_control_status = True
        elif parameter == "TONE CTRL ON":
            self._tone_control_adjust = True
            self._tone_control_status = True

    async def async_update(
        self, global_update: bool = False, cache_id: Optional[Hashable] = None
    ) -> None:
        """Update volume asynchronously."""
        _LOGGER.debug("Starting tone control update")
        # Ensure instance is setup before updating
        if not self._is_setup:
            await self.async_setup()

        # Update state
        await self.async_update_tone_control(
            global_update=global_update, cache_id=cache_id
        )
        _LOGGER.debug("Finished tone control update")

    async def async_update_tone_control(
        self, global_update: bool = False, cache_id: Optional[Hashable] = None
    ):
        """Update tone control status of device."""
        if self._device.use_avr_2016_update is None:
            raise AvrProcessingError(
                "Device is not setup correctly, update method not set"
            )

        if self._is_setup and not self._support_tone_control:
            return

        # Tone control is only available for avr 2016 update
        if not self._device.use_avr_2016_update:
            self._support_tone_control = False
            _LOGGER.info("Tone control not supported")
            return

        try:
            await self.async_update_attrs_appcommand(
                self.appcommand_attrs,
                global_update=global_update,
                cache_id=cache_id,
            )
        except (AvrProcessingError, AvrIncompleteResponseError):
            self._support_tone_control = False
            _LOGGER.info("Tone control not supported")
            return

        if not self._is_setup:
            self._support_tone_control = True
            _LOGGER.info("Tone control supported")

    async def async_set_tone_control_command(
        self, parameter_type: str, value: int
    ) -> None:
        """Post request for tone control commands."""
        cmd = (
            attr.evolve(
                AppCommands.SetToneControl,
                set_command=AppCommandCmdParam(name=parameter_type, text=value),
            ),
        )
        await self._device.api.async_post_appcommand(
            self._device.urls.appcommand, cmd, cache_id=time.time()
        )

    ##############
    # Properties #
    ##############
    @property
    def support_tone_control(self) -> Optional[bool]:
        """Return True if tone control is supported."""
        return self._support_tone_control

    @property
    def tone_control_status(self) -> Optional[bool]:
        """Return value of tone control status."""
        return self._tone_control_status

    @property
    def tone_control_adjust(self) -> Optional[bool]:
        """Return value of tone control adjust."""
        return self._tone_control_adjust

    @property
    def bass(self) -> Optional[int]:
        """Return value of bass."""
        return self._bass

    @property
    def bass_level(self) -> Optional[str]:
        """Return level of bass."""
        return self._bass_level

    @property
    def treble(self) -> Optional[int]:
        """Return value of treble."""
        return self._treble

    @property
    def treble_level(self) -> Optional[str]:
        """Return level of treble."""
        return self._treble_level

    ##########
    # Setter #
    ##########
    async def async_enable_tone_control(self) -> None:
        """Enable tone control to change settings like bass or treble."""
        if self._tone_control_status is None:
            raise AvrCommandError(
                "Cannot enable tone control, Dynamic EQ must be deactivated"
            )

        if self._device.telnet_available:
            telnet_command = self._device.telnet_commands.command_tonecontrol + "ON"
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_set_tone_control_command("adjust", 1)

    async def async_disable_tone_control(self) -> None:
        """Disable tone control to change settings like bass or treble."""
        if self._tone_control_status is None:
            raise AvrCommandError(
                "Cannot disable tone control, Dynamic EQ must be deactivated"
            )
        if self._device.telnet_available:
            telnet_command = self._device.telnet_commands.command_tonecontrol + "OFF"
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_set_tone_control_command("adjust", 0)

    async def async_set_bass(self, value: int) -> None:
        """
        Set receiver bass.

        Minimum is 0, maximum at 12

        Note:
        Doesn't work, if Dynamic Equalizer is active.
        """
        if value < 0 or value > 12:
            raise AvrCommandError("Invalid value for bass")
        if self._device.telnet_available:
            if not self.tone_control_adjust:
                await self.async_enable_tone_control()
            telnet_command = self._device.telnet_commands.command_bass + str(value + 44)
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_enable_tone_control()
            await self.async_set_tone_control_command("bassvalue", value)

    async def async_bass_up(self) -> None:
        """
        Increase level of Bass.

        Note:
        Doesn't work, if Dynamic Equalizer is active.
        """
        if self.bass == 12:
            return
        if self._device.telnet_available:
            if not self.tone_control_adjust:
                await self.async_enable_tone_control()
            telnet_command = self._device.telnet_commands.command_bass + "UP"
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_enable_tone_control()
            await self.async_set_tone_control_command("bassvalue", self.bass + 1)
            await self.async_update()

    async def async_bass_down(self) -> None:
        """
        Decrease level of Bass.

        Note:
        Doesn't work, if Dynamic Equalizer is active.
        """
        if self.bass == 0:
            return
        if self._device.telnet_available:
            if not self.tone_control_adjust:
                await self.async_enable_tone_control()
            telnet_command = self._device.telnet_commands.command_bass + "DOWN"
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_enable_tone_control()
            await self.async_set_tone_control_command("bassvalue", self.bass - 1)
            await self.async_update()

    async def async_set_treble(self, value: int) -> None:
        """
        Set receiver treble.

        Minimum is 0, maximum at 12

        Note:
        Doesn't work, if Dynamic Equalizer is active.
        """
        if value < 0 or value > 12:
            raise AvrCommandError("Invalid value for treble")
        if self._device.telnet_available:
            if not self.tone_control_adjust:
                await self.async_enable_tone_control()
            telnet_command = self._device.telnet_commands.command_treble + str(
                value + 44
            )
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_enable_tone_control()
            await self.async_set_tone_control_command("treblevalue", value)

    async def async_treble_up(self) -> None:
        """
        Increase level of Treble.

        Note:
        Doesn't work, if Dynamic Equalizer is active.
        """
        if self.treble == 12:
            return
        if self._device.telnet_available:
            if not self.tone_control_adjust:
                await self.async_enable_tone_control()
            telnet_command = self._device.telnet_commands.command_treble + "UP"
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_enable_tone_control()
            await self.async_set_tone_control_command("treblevalue", self.treble + 1)
            await self.async_update()

    async def async_treble_down(self) -> None:
        """
        Decrease level of Treble.

        Note:
        Doesn't work, if Dynamic Equalizer is active.
        """
        if self.treble == 0:
            return
        if self._device.telnet_available:
            if not self.tone_control_adjust:
                await self.async_enable_tone_control()
            telnet_command = self._device.telnet_commands.command_treble + "DOWN"
            await self._device.telnet_api.async_send_commands(telnet_command)
        else:
            await self.async_enable_tone_control()
            await self.async_set_tone_control_command("treblevalue", self.treble - 1)
            await self.async_update()


def tone_control_factory(instance: DenonAVRFoundation) -> DenonAVRToneControl:
    """Create DenonAVRToneControl at receiver instances."""
    # pylint: disable=protected-access
    new = DenonAVRToneControl(device=instance._device)
    return new
