#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
This module implements the interface to Denon AVR receivers.

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

import asyncio
import logging
import time
from typing import Callable, Dict, List, Literal, Optional, Union

import attr
import httpx

from .audyssey import DenonAVRAudyssey, audyssey_factory
from .const import (
    AVR_X,
    DENON_ATTR_SETATTR,
    MAIN_ZONE,
    VALID_ZONES,
    AudioRestorers,
    AutoStandbys,
    BluetoothOutputModes,
    DimmerModes,
    DynamicVolumeSettings,
    EcoModes,
    HDMIAudioDecodes,
    HDMIOutputs,
    Illuminations,
    InputModes,
    MultiEQModes,
    PanelLocks,
    ReferenceLevelOffsets,
    RoomSizes,
    TransducerLPFs,
    VideoProcessingModes,
)
from .dirac import DenonAVRDirac, dirac_factory
from .exceptions import AvrCommandError, AvrForbiddenError, AvrIncompleteResponseError
from .foundation import DenonAVRFoundation, set_api_host, set_api_timeout
from .input import DenonAVRInput, input_factory
from .soundmode import DenonAVRSoundMode, sound_mode_factory
from .tonecontrol import DenonAVRToneControl, tone_control_factory
from .volume import DenonAVRVolume, volume_factory

_LOGGER = logging.getLogger(__name__)


@attr.s(auto_attribs=True, on_setattr=DENON_ATTR_SETATTR)
class DenonAVR(DenonAVRFoundation):
    """
    Representing a Denon AVR Device.

    Initialize MainZone of DenonAVR.

    :param host: IP or HOSTNAME.
    :type host: str

    :param name: Device name, if None FriendlyName of device is used.
    :type name: str or None

    :param show_all_inputs: If True deleted input functions are also shown
    :type show_all_inputs: bool

    :param timeout: Timeout when calling device APIs.
    :type timeout: float

    :param add_zones: Additional Zones for which an instance are created
    :type add_zones: dict [str, str] or None
    """

    _host: str = attr.ib(converter=str, on_setattr=[*DENON_ATTR_SETATTR, set_api_host])
    _name: Optional[str] = attr.ib(
        converter=attr.converters.optional(str), default=None
    )
    _show_all_inputs: bool = attr.ib(converter=bool, default=False)
    _add_zones: Optional[Dict[str, str]] = attr.ib(
        validator=attr.validators.optional(
            attr.validators.deep_mapping(
                attr.validators.in_(VALID_ZONES),
                attr.validators.optional(attr.validators.instance_of(str)),
                attr.validators.instance_of(dict),
            )
        ),
        default=None,
    )
    _timeout: float = attr.ib(
        converter=float, on_setattr=[*DENON_ATTR_SETATTR, set_api_timeout], default=2.0
    )
    _zones: Dict[str, "DenonAVR"] = attr.ib(
        validator=attr.validators.deep_mapping(
            attr.validators.in_(VALID_ZONES),
            attr.validators.instance_of(DenonAVRFoundation),
            attr.validators.instance_of(dict),
        ),
        default=attr.Factory(dict),
        init=False,
    )
    _setup_lock: asyncio.Lock = attr.ib(default=attr.Factory(asyncio.Lock))
    _allow_recovery: bool = attr.ib(converter=bool, default=True, init=True)
    audyssey: DenonAVRAudyssey = attr.ib(
        validator=attr.validators.instance_of(DenonAVRAudyssey),
        default=attr.Factory(audyssey_factory, takes_self=True),
        init=False,
    )
    dirac: DenonAVRDirac = attr.ib(
        validator=attr.validators.instance_of(DenonAVRDirac),
        default=attr.Factory(dirac_factory, takes_self=True),
        init=False,
    )
    input: DenonAVRInput = attr.ib(
        validator=attr.validators.instance_of(DenonAVRInput),
        default=attr.Factory(input_factory, takes_self=True),
        init=False,
    )
    soundmode: DenonAVRSoundMode = attr.ib(
        validator=attr.validators.instance_of(DenonAVRSoundMode),
        default=attr.Factory(sound_mode_factory, takes_self=True),
        init=False,
    )
    tonecontrol: DenonAVRToneControl = attr.ib(
        validator=attr.validators.instance_of(DenonAVRToneControl),
        default=attr.Factory(tone_control_factory, takes_self=True),
        init=False,
    )
    vol: DenonAVRVolume = attr.ib(
        validator=attr.validators.instance_of(DenonAVRVolume),
        default=attr.Factory(volume_factory, takes_self=True),
        init=False,
    )

    def __attrs_post_init__(self) -> None:
        """Initialize special attributes."""
        # Set host and timeout again to start its custom setattr function
        self._host = self._host
        self._timeout = self._timeout

        # Add own instance to zone dictionary
        self._zones[self._device.zone] = self

        # Create instances of additional zones if requested
        if self._device.zone == MAIN_ZONE and self._add_zones is not None:
            self.create_zones(self._add_zones)

    def create_zones(self, add_zones):
        """Create instances of additional zones for the receiver."""
        for zone, zname in add_zones.items():
            # Name either set explicitly or name of Main Zone with suffix
            zonename = None
            if zname is None and self._name is not None:
                zonename = f"{self._name} {zone}"
            zone_device = attr.evolve(self._device, zone=zone)
            zone_inst = DenonAVR(
                host=self._host,
                device=zone_device,
                name=zonename,
                timeout=self._timeout,
                show_all_inputs=self._show_all_inputs,
            )
            self._zones[zone] = zone_inst

    async def async_setup(self) -> None:
        """Ensure that configuration is loaded from receiver asynchronously."""
        async with self._setup_lock:
            _LOGGER.debug("Starting denonavr setup")
            # Device setup
            await self._device.async_setup()
            if self._name is None:
                self._name = self._device.friendly_name

            # Setup other functions
            self.input.setup()
            await self.soundmode.async_setup()
            await self.tonecontrol.async_setup()
            self.vol.setup()
            self.audyssey.setup()
            self.dirac.setup()

            for zone_name, zone_item in self._zones.items():
                if zone_name != self.zone:
                    await zone_item.async_setup()

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

    async def async_update(self):
        """
        Get the latest status information from device asynchronously.

        Method executes the update method for the current receiver type.
        """
        _LOGGER.debug("Starting denonavr update")
        # Ensure that the device is setup
        if not self._is_setup:
            await self.async_setup()

        # Create a cache id for this global update
        cache_id = time.time()

        try:
            # Update device
            await self._device.async_update(global_update=True, cache_id=cache_id)

            # Update other functions
            await self.input.async_update(global_update=True, cache_id=cache_id)
            await self.soundmode.async_update(global_update=True, cache_id=cache_id)
            await self.tonecontrol.async_update(global_update=True, cache_id=cache_id)
            await self.vol.async_update(global_update=True, cache_id=cache_id)
        except AvrForbiddenError:
            # Recovery in case receiver changes port from 80 to 8080 which
            # might happen at Denon AVR-X 2016 receivers
            if self._device.use_avr_2016_update and self._allow_recovery:
                self._allow_recovery = False
                _LOGGER.warning(
                    "AppCommand.xml returns HTTP status 403. Running setup"
                    " again once to check if receiver interface switched "
                    "ports"
                )
                self._is_setup = False
                await self.async_update()
            else:
                raise
        except AvrIncompleteResponseError:
            # Use status XML interface if Appcommand.xml returns an incomplete result
            # Only AVR_X devices support both interfaces
            if self._device.use_avr_2016_update and self._device.receiver == AVR_X:
                _LOGGER.warning(
                    "Error verifying Appcommand.xml update method, it returns "
                    "an incomplete result set. Deactivating the interface"
                )
                self._device.use_avr_2016_update = False
                await self.async_update()
            else:
                raise
        else:
            if not self._allow_recovery:
                self._allow_recovery = True
                _LOGGER.info("AppCommand.xml recovered from HTTP status 403 error")

        _LOGGER.debug("Finished denonavr update")

    async def async_update_tonecontrol(self):
        """Get Tonecontrol settings."""
        await self.tonecontrol.async_update()

    async def async_update_audyssey(self):
        """Get Audyssey settings."""
        await self.audyssey.async_update()

    async def async_get_command(self, request: str) -> str:
        """Send HTTP GET command to Denon AVR receiver asynchronously."""
        return await self._device.api.async_get_command(request)

    async def async_send_telnet_commands(self, *commands: str) -> None:
        """Send telnet commands to the receiver."""
        await self._device.telnet_api.async_send_commands(*commands)

    def send_telnet_commands(self, *commands: str) -> None:
        """Send telnet commands to the receiver."""
        self._device.telnet_api.send_commands(*commands)

    def register_callback(self, event: str, callback: Callable[[str, str, str], None]):
        """Register a callback for telnet events."""
        self._device.telnet_api.register_callback(event, callback=callback)

    def unregister_callback(
        self, event: str, callback: Callable[[str, str, str], None]
    ):
        """Unregister a callback for telnet events."""
        self._device.telnet_api.unregister_callback(event, callback=callback)

    async def async_telnet_connect(self):
        """Connect to the telnet interface of the receiver."""
        await self._device.telnet_api.async_connect()

    async def async_telnet_disconnect(self):
        """Disconnect from the telnet interface of the receiver."""
        await self._device.telnet_api.async_disconnect()

    ##############
    # Properties #
    ##############
    @property
    def zone(self) -> str:
        """Return Zone of this instance."""
        return self._device.zone

    @property
    def zones(self) -> Dict[str, DenonAVRFoundation]:
        """Return all Zone instances of the device."""
        zones = self._zones.copy()
        return zones

    @property
    def name(self) -> Optional[str]:
        """Return the name of the device as string."""
        return self._name

    @property
    def host(self) -> str:
        """Return the host of the device as string."""
        return self._host

    @property
    def manufacturer(self) -> Optional[str]:
        """Return the manufacturer of the device as string."""
        return self._device.manufacturer

    @property
    def model_name(self) -> Optional[str]:
        """Return the model name of the device as string."""
        return self._device.model_name

    @property
    def serial_number(self) -> Optional[str]:
        """Return the serial number of the device as string."""
        return self._device.serial_number

    @property
    def power(self) -> Optional[str]:
        """
        Return the power state of the device.

        Possible values are: "ON", "STANDBY" and "OFF"
        """
        return self._device.power

    @property
    def settings_menu(self) -> Optional[bool]:
        """
        Return the settings menu state of the device.

        Only available if using Telnet.
        """
        return self._device.settings_menu

    @property
    def state(self) -> Optional[str]:
        """
        Return the state of the device.

        Possible values are: "on", "off", "playing", "paused"
        "playing" and "paused" are only available for input functions
        in PLAYING_SOURCES.
        """
        return self.input.state

    @property
    def muted(self) -> Optional[bool]:
        """
        Boolean if volume is currently muted.

        Return "True" if muted and "False" if not muted.
        """
        return self.vol.muted

    @property
    def volume(self) -> Optional[float]:
        """
        Return volume of Denon AVR as float.

        Volume is send in a format like -50.0.
        Minimum is -80.0, maximum at 18.0
        """
        return self.vol.volume

    @property
    def input_func(self) -> Optional[str]:
        """Return the current input source as string."""
        return self.input.input_func

    @property
    def input_func_list(self) -> List[str]:
        """Return a list of available input sources as string."""
        return self.input.input_func_list

    @property
    def support_sound_mode(self) -> Optional[bool]:
        """Return True if sound mode is supported."""
        return self.soundmode.support_sound_mode

    @property
    def sound_mode(self) -> Optional[str]:
        """Return the matched current sound mode as a string."""
        return self.soundmode.sound_mode

    @property
    def sound_mode_list(self) -> List[str]:
        """Return a list of available sound modes as string."""
        return self.soundmode.sound_mode_list

    @property
    def sound_mode_map(self) -> Dict[str, str]:
        """Return a dict of available sound modes with their mapping values."""
        return self.soundmode.sound_mode_map

    @property
    def sound_mode_map_rev(self) -> Dict[str, str]:
        """Return a dict to map each sound_mode_raw to matching sound_mode."""
        return self.soundmode.sound_mode_map_rev

    @property
    def sound_mode_raw(self) -> Optional[str]:
        """Return the current sound mode as string as received from the AVR."""
        return self.soundmode.sound_mode_raw

    @property
    def image_url(self) -> Optional[str]:
        """Return image URL of current playing media when powered on."""
        return self.input.image_url

    @property
    def title(self) -> Optional[str]:
        """Return title of current playing media as string."""
        return self.input.title

    @property
    def artist(self) -> Optional[str]:
        """Return artist of current playing media as string."""
        return self.input.artist

    @property
    def album(self) -> Optional[str]:
        """Return album name of current playing media as string."""
        return self.input.album

    @property
    def band(self) -> Optional[str]:
        """Return band of current radio station as string."""
        return self.input.band

    @property
    def frequency(self) -> Optional[str]:
        """Return frequency of current radio station as string."""
        return self.input.frequency

    @property
    def station(self) -> Optional[str]:
        """Return current radio station as string."""
        return self.input.station

    @property
    def netaudio_func_list(self) -> List[str]:
        """Return list of network audio devices.

        Those devices should react to play, pause, next and previous
        track commands.
        """
        return self.input.netaudio_func_list

    @property
    def playing_func_list(self) -> List[str]:
        """Return list of playing devices.

        Those devices offer additional information about what they are playing
        (e.g. title, artist, album, band, frequency, station, image_url).
        """
        return self.input.playing_func_list

    @property
    def receiver_port(self) -> Optional[int]:
        """Return the receiver's port."""
        if self._device.receiver is None:
            return None
        return self._device.receiver.port

    @property
    def receiver_type(self) -> Optional[str]:
        """Return the receiver's type."""
        if self._device.receiver is None:
            return None
        return self._device.receiver.type

    @property
    def telnet_available(self) -> bool:
        """Return True if telnet is connected and healthy."""
        return self._device.telnet_available

    @property
    def telnet_connected(self) -> bool:
        """Return True if telnet is connected."""
        return self._device.telnet_api.connected

    @property
    def telnet_healthy(self) -> bool:
        """Return True if telnet connection is healthy."""
        return self._device.telnet_api.healthy

    @property
    def show_all_inputs(self) -> Optional[bool]:
        """Indicate if all inputs are shown or just active one."""
        return self._show_all_inputs

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

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

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

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

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

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

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

    @property
    def dynamic_eq(self) -> Optional[bool]:
        """Return value of Dynamic EQ."""
        return self.audyssey.dynamic_eq

    @property
    def reference_level_offset(self) -> Optional[str]:
        """Return value of Reference Level Offset."""
        return self.audyssey.reference_level_offset

    @property
    def reference_level_offset_setting_list(self) -> List[str]:
        """Return a list of available reference level offset settings."""
        return self.audyssey.reference_level_offset_setting_list

    @property
    def dynamic_volume(self) -> Optional[str]:
        """Return value of Dynamic Volume."""
        return self.audyssey.dynamic_volume

    @property
    def dynamic_volume_setting_list(self) -> List[str]:
        """Return a list of available Dynamic Volume settings."""
        return self.audyssey.dynamic_volume_setting_list

    @property
    def multi_eq(self) -> Optional[str]:
        """Return value of MultiEQ."""
        return self.audyssey.multi_eq

    @property
    def multi_eq_setting_list(self) -> List[str]:
        """Return a list of available MultiEQ settings."""
        return self.audyssey.multi_eq_setting_list

    @property
    def dimmer(self) -> Optional[str]:
        """
        Returns the dimmer state of the device.

        Possible values are: "Off", "Dark", "Dim" and "Bright"
        """
        return self._device.dimmer

    @property
    def auto_standby(self) -> Optional[str]:
        """
        Return the auto-standby state of the device.

        Possible values are: "OFF", "15M", "30M", "60M", "2H", "4H", "8H"
        """
        return self._device.auto_standby

    @property
    def sleep(self) -> Optional[Union[str, int]]:
        """
        Return the sleep timer for the device.

        Only available if using Telnet.

        Possible values are: "OFF" and 1-120 (in minutes)
        """
        return self._device.sleep

    @property
    def delay(self) -> Optional[int]:
        """
        Return the audio delay for the device in ms.

        Only available if using Telnet.
        """
        return self._device.delay

    @property
    def eco_mode(self) -> Optional[str]:
        """
        Returns the eco-mode for the device.

        Possible values are: "Off", "On", "Auto"
        """
        return self._device.eco_mode

    @property
    def hdmi_output(self) -> Optional[str]:
        """
        Returns the HDMI-output for the device.

        Only available if using Telnet.

        Possible values are: "Auto", "HDMI1", "HDMI2"
        """
        return self._device.hdmi_output

    @property
    def hdmi_audio_decode(self) -> Optional[str]:
        """
        Returns the HDMI Audio Decode mode for the device.

        Only available if using Telnet.

        Possible values are: "AMP", "TV"
        """
        return self._device.hdmi_audio_decode

    @property
    def video_processing_mode(self) -> Optional[str]:
        """
        Return the video processing mode for the device.

        Only available if using Telnet.

        Possible values are: "Auto", "Game", "Movie", "Bypass"
        """
        return self._device.video_processing_mode

    @property
    def tactile_transducer(self) -> Optional[str]:
        """
        Return the tactile transducer state of the device.

        Only available if using Telnet.
        """
        return self._device.tactile_transducer

    @property
    def tactile_transducer_level(self) -> Optional[float]:
        """
        Return the tactile transducer level in dB.

        Only available if using Telnet.
        """
        return self._device.tactile_transducer_level

    @property
    def tactile_transducer_lpf(self) -> Optional[str]:
        """
        Return the tactile transducer low pass filter frequency.

        Only available if using Telnet.
        """
        return self._device.tactile_transducer_lpf

    @property
    def room_size(self) -> Optional[str]:
        """
        Return the room size for the device.

        Only available if using Telnet.

        Possible values are: "S", "MS", "M", "ML", "L"
        """
        return self._device.room_size

    @property
    def triggers(self) -> Optional[Dict[int, str]]:
        """
        Return the triggers and their statuses for the device.

        Only available if using Telnet.
        """
        return self._device.triggers

    @property
    def speaker_preset(self) -> Optional[int]:
        """
        Return the speaker preset for the device.

        Only available if using Telnet.

        Possible values are: "1", "2"
        """
        return self._device.speaker_preset

    @property
    def bt_transmitter(self) -> Optional[bool]:
        """
        Return the Bluetooth transmitter state for the device.

        Only available if using Telnet.
        """
        return self._device.bt_transmitter

    @property
    def bt_output_mode(self) -> Optional[str]:
        """
        Return the Bluetooth output mode for the device.

        Only available if using Telnet.

        Possible values are: "Bluetooth + Speakers", "Bluetooth Only"
        """
        return self._device.bt_output_mode

    @property
    def delay_time(self) -> Optional[int]:
        """
        Return the delay time for the device in ms.

        Only available if using Telnet.
        """
        return self._device.delay_time

    @property
    def audio_restorer(self) -> Optional[str]:
        """
        Return the audio restorer for the device.

        Only available if using Telnet.

        Possible values are: "Off", "Low", "Medium", "High"
        """
        return self._device.audio_restorer

    @property
    def graphic_eq(self) -> Optional[bool]:
        """
        Return the Graphic EQ status for the device.

        Only available if using Telnet.
        """
        return self._device.graphic_eq

    @property
    def headphone_eq(self) -> Optional[bool]:
        """
        Return the Headphone EQ status for the device.

        Only available if using Telnet.
        """
        return self._device.headphone_eq

    @property
    def illumination(self) -> Optional[str]:
        """
        Return the illumination status for the device.

        Only available on Marantz devices and when using Telnet.

        Possible values are: "Auto", "Bright", "Dim", "Dark", "Off"
        """
        return self.illumination

    @property
    def auto_lip_sync(self) -> Optional[bool]:
        """
        Return the auto lip sync status for the device.

        Only available on Marantz devices and when using Telnet.
        """
        return self.auto_lip_sync

    ##########
    # Getter #
    ##########

    def get_trigger(self, trigger: int) -> Optional[str]:
        """
        Return the status of a specific trigger.

        Only available if using Telnet.

        Valid trigger values are 1-3.
        """
        return self._device.get_trigger(trigger)

    ##########
    # Setter #
    ##########
    def set_async_client_getter(
        self, async_client_getter: Callable[[], httpx.AsyncClient]
    ) -> None:
        """
        Set a custom httpx.AsyncClient getter for this instance.

        The function provided must return an instance of httpx.AsyncClient.
        This is a non-blocking method.
        """
        if not callable(async_client_getter):
            raise AvrCommandError("Provided object is not callable")
        self._device.api.httpx_async_client.client_getter = async_client_getter

    async def async_dynamic_eq_off(self) -> None:
        """Turn DynamicEQ off."""
        await self.audyssey.async_dynamiceq_off()

    async def async_dynamic_eq_on(self) -> None:
        """Turn DynamicEQ on."""
        await self.audyssey.async_dynamiceq_on()

    async def async_toggle_dynamic_eq(self) -> None:
        """Toggle DynamicEQ."""
        await self.audyssey.async_toggle_dynamic_eq()

    async def async_set_multieq(self, value: MultiEQModes) -> None:
        """Set MultiEQ mode."""
        await self.audyssey.async_set_multieq(value)

    async def async_set_reflevoffset(self, value: ReferenceLevelOffsets) -> None:
        """Set Reference Level Offset."""
        await self.audyssey.async_set_reflevoffset(value)

    async def async_set_dynamicvol(self, value: DynamicVolumeSettings) -> None:
        """Set Dynamic Volume."""
        await self.audyssey.async_set_dynamicvol(value)

    async def async_set_input_func(self, input_func: str) -> None:
        """
        Set input_func of device.

        Valid values depend on the device and should be taken from
        "input_func_list".
        """
        await self.input.async_set_input_func(input_func)

    async def async_set_sound_mode(self, sound_mode: str) -> None:
        """
        Set sound_mode of device.

        Valid values depend on the device and should be taken from
        "sound_mode_list".
        """
        await self.soundmode.async_set_sound_mode(sound_mode)

    async def async_toggle_play_pause(self) -> None:
        """Toggle play pause media player."""
        await self.input.async_toggle_play_pause()

    async def async_play(self) -> None:
        """Send play command to receiver command."""
        await self.input.async_play()

    async def async_pause(self) -> None:
        """Send pause command to receiver command."""
        await self.input.async_pause()

    async def async_stop(self) -> None:
        """Send stop command to receiver command."""
        await self.input.async_stop()

    async def async_previous_track(self) -> None:
        """Send previous track command to receiver command."""
        await self.input.async_previous_track()

    async def async_next_track(self) -> None:
        """Send next track command to receiver command."""
        await self.input.async_next_track()

    async def async_power_on(self) -> None:
        """Turn on receiver."""
        await self._device.async_power_on()

    async def async_power_off(self) -> None:
        """Turn off receiver."""
        await self._device.async_power_off()

    async def async_volume_up(self) -> None:
        """Volume up receiver."""
        await self.vol.async_volume_up()

    async def async_volume_down(self) -> None:
        """Volume down receiver."""
        await self.vol.async_volume_down()

    async def async_set_volume(self, volume: float) -> None:
        """
        Set receiver volume.

        Volume is send in a format like -50.0.
        Minimum is -80.0, maximum at 18.0
        """
        await self.vol.async_set_volume(volume)

    async def async_mute(self, mute: bool) -> None:
        """Mute receiver."""
        await self.vol.async_mute(mute)

    async def async_enable_tone_control(self) -> None:
        """Enable tone control to change settings like bass or treble."""
        await self.tonecontrol.async_enable_tone_control()

    async def async_disable_tone_control(self) -> None:
        """Disable tone control to change settings like bass or treble."""
        await self.tonecontrol.async_disable_tone_control()

    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.
        """
        await self.tonecontrol.async_set_bass(value)

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

        Note:
        Doesn't work, if Dynamic Equalizer is active
        """
        await self.tonecontrol.async_bass_up()

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

        Note:
        Doesn't work, if Dynamic Equalizer is active
        """
        await self.tonecontrol.async_bass_down()

    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.
        """
        await self.tonecontrol.async_set_treble(value)

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

        Note:
        Doesn't work, if Dynamic Equalizer is active
        """
        await self.tonecontrol.async_treble_up()

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

        Note:
        Doesn't work, if Dynamic Equalizer is active
        """
        await self.tonecontrol.async_treble_down()

    async def async_cursor_up(self) -> None:
        """Send cursor up to receiver."""
        await self._device.async_cursor_up()

    async def async_cursor_down(self) -> None:
        """Send cursor down to receiver."""
        await self._device.async_cursor_down()

    async def async_cursor_left(self) -> None:
        """Send cursor left to receiver."""
        await self._device.async_cursor_left()

    async def async_cursor_right(self) -> None:
        """Send cursor right to receiver."""
        await self._device.async_cursor_right()

    async def async_cursor_enter(self) -> None:
        """Send cursor enter to receiver."""
        await self._device.async_cursor_enter()

    async def async_back(self) -> None:
        """Send back to receiver."""
        await self._device.async_back()

    async def async_info(self) -> None:
        """Send info to receiver."""
        await self._device.async_info()

    async def async_options(self) -> None:
        """Raise options menu to receiver."""
        await self._device.async_options()

    async def async_settings_menu(self) -> None:
        """Raise settings menu to receiver."""
        await self._device.async_settings_menu()

    async def async_channel_level_adjust(self) -> None:
        """Toggle the channel level adjust menu on receiver."""
        await self._device.async_channel_level_adjust()

    async def async_dimmer_toggle(self) -> None:
        """Toggle dimmer on receiver."""
        await self._device.async_dimmer_toggle()

    async def async_dimmer(self, mode: DimmerModes) -> None:
        """Set dimmer mode on receiver."""
        await self._device.async_dimmer(mode)

    async def async_auto_standby(self, auto_standby: AutoStandbys) -> None:
        """Set auto standby on receiver."""
        await self._device.async_auto_standby(auto_standby)

    async def async_sleep(self, sleep: Union[Literal["OFF"], int]) -> None:
        """
        Set auto standby on receiver.

        Valid sleep values are "OFF" and 1-120 (in minutes)
        """
        await self._device.async_sleep(sleep)

    async def async_delay_up(self) -> None:
        """Increase delay of the audio."""
        await self._device.async_delay_up()

    async def async_delay_down(self) -> None:
        """Decrease delay of the audio."""
        await self._device.async_delay_down()

    async def async_eco_mode(self, mode: EcoModes) -> None:
        """Set Eco mode."""
        await self._device.async_eco_mode(mode)

    async def async_hdmi_output(self, output: HDMIOutputs) -> None:
        """Set HDMI output."""
        await self._device.async_hdmi_output(output)

    async def async_hdmi_audio_decode(self, mode: HDMIAudioDecodes) -> None:
        """Set HDMI Audio Decode mode on receiver."""
        await self._device.async_hdmi_audio_decode(mode)

    async def async_video_processing_mode(self, mode: VideoProcessingModes) -> None:
        """Set video processing mode on receiver."""
        await self._device.async_video_processing_mode(mode)

    async def async_status(self) -> None:
        """
        Toggles the display of status on the device.

        Only supported on Denon models.
        """
        return await self._device.async_status()

    async def async_system_reset(self) -> None:
        """DANGER! Reset the receiver."""
        await self._device.async_system_reset()

    async def async_network_restart(self) -> None:
        """Restart the network on the receiver."""
        await self._device.async_network_restart()

    async def async_speaker_preset(self, preset: int) -> None:
        """
        Set speaker preset on receiver.

        Valid preset values are 1-2.
        """
        await self._device.async_speaker_preset(preset)

    async def async_speaker_preset_toggle(self) -> None:
        """
        Toggle speaker preset on receiver.

        Only available if using Telnet.
        """
        await self._device.async_speaker_preset_toggle()

    async def async_bt_transmitter_on(self) -> None:
        """Turn on Bluetooth transmitter on receiver."""
        await self._device.async_bt_transmitter_on()

    async def async_bt_transmitter_off(self) -> None:
        """Turn off Bluetooth transmitter on receiver."""
        await self._device.async_bt_transmitter_off()

    async def async_bt_transmitter_toggle(self) -> None:
        """
        Toggle Bluetooth transmitter mode on receiver.

        Only available if using Telnet.
        """
        await self._device.async_bt_transmitter_toggle()

    async def async_bt_output_mode(self, mode: BluetoothOutputModes) -> None:
        """Set Bluetooth transmitter mode on receiver."""
        await self._device.async_bt_output_mode(mode)

    async def async_bt_output_mode_toggle(self) -> None:
        """
        Toggle Bluetooth output mode on receiver.

        Only available if using Telnet.
        """
        await self._device.async_bt_output_mode_toggle()

    async def async_delay_time_up(self) -> None:
        """Delay time up on receiver."""
        await self._device.async_delay_time_up()

    async def async_delay_time_down(self) -> None:
        """Delay time up on receiver."""
        await self._device.async_delay_time_down()

    async def async_delay_time(self, delay_time: int) -> None:
        """
        Set delay time on receiver.

        :param delay_time: Delay time in ms. Valid values are 0-999.
        """
        await self._device.async_delay_time(delay_time)

    async def async_audio_restorer(self, mode: AudioRestorers):
        """Set audio restorer on receiver."""
        await self._device.async_audio_restorer(mode)

    async def async_remote_control_lock(self):
        """Set remote control lock on receiver."""
        await self._device.async_remote_control_lock()

    async def async_remote_control_unlock(self):
        """Set remote control unlock on receiver."""
        await self._device.async_remote_control_unlock()

    async def async_panel_lock(self, panel_lock_mode: PanelLocks):
        """Set panel lock on receiver."""
        await self._device.async_panel_lock(panel_lock_mode)

    async def async_panel_unlock(self):
        """Set panel unlock on receiver."""
        await self._device.async_panel_unlock()

    async def async_graphic_eq_on(self) -> None:
        """Turn on Graphic EQ on receiver."""
        await self._device.async_graphic_eq_on()

    async def async_graphic_eq_off(self) -> None:
        """Turn off Graphic EQ on receiver."""
        await self._device.async_graphic_eq_off()

    async def async_graphic_eq_toggle(self) -> None:
        """
        Toggle Graphic EQ on receiver.

        Only available if using Telnet.
        """
        await self._device.async_graphic_eq_toggle()

    async def async_headphone_eq_on(self) -> None:
        """Turn on Headphone EQ on receiver."""
        await self._device.async_headphone_eq_on()

    async def async_headphone_eq_off(self) -> None:
        """Turn off Headphone EQ on receiver."""
        await self._device.async_headphone_eq_off()

    async def async_headphone_eq_toggle(self) -> None:
        """
        Toggle Headphone EQ on receiver.

        Only available if using Telnet.
        """
        await self._device.async_headphone_eq_toggle()

    async def async_tactile_transducer_on(self) -> None:
        """Turn on tactile transducer on receiver."""
        await self._device.async_tactile_transducer_on()

    async def async_tactile_transducer_off(self) -> None:
        """Turn on tactile transducer on receiver."""
        await self._device.async_tactile_transducer_off()

    async def async_tactile_transducer_toggle(self) -> None:
        """
        Turn on tactile transducer on receiver.

        Only available if using Telnet.
        """
        await self._device.async_tactile_transducer_toggle()

    async def async_tactile_transducer_level_up(self) -> None:
        """Increase the transducer level on receiver."""
        await self._device.async_tactile_transducer_level_up()

    async def async_tactile_transducer_level_down(self) -> None:
        """Decrease the transducer on receiver."""
        await self._device.async_tactile_transducer_level_down()

    async def async_transducer_lpf(self, lpf: TransducerLPFs) -> None:
        """Set transducer low pass filter on receiver."""
        await self._device.async_transducer_lpf(lpf)

    async def async_room_size(self, room_size: RoomSizes) -> None:
        """Set room size on receiver."""
        await self._device.async_room_size(room_size)

    async def async_trigger_on(self, trigger: int) -> None:
        """
        Set trigger to ON on receiver.

        :param trigger: Trigger number to set to ON. Valid values are 1-3.
        """
        await self._device.async_trigger_on(trigger)

    async def async_trigger_off(self, trigger: int) -> None:
        """
        Set trigger to OFF on receiver.

        :param trigger: Trigger number to set to OFF. Valid values are 1-3.
        """
        await self._device.async_trigger_off(trigger)

    async def async_trigger_toggle(self, trigger: int) -> None:
        """
        Toggle trigger on receiver.

        Only available if using Telnet.

        :param trigger: Trigger number to toggle. Valid values are 1-3.
        """
        await self._device.async_trigger_toggle(trigger)

    async def async_quick_select_mode(self, quick_select_number: int) -> None:
        """
        Set quick select mode on receiver.

        :param quick_select_number: Quick select number to set. Valid values are 1-5.
        """
        await self._device.async_quick_select_mode(quick_select_number)

    async def async_quick_select_memory(self, quick_select_number: int) -> None:
        """
        Set quick select memory on receiver.

        :param quick_select_number: Quick select number to set. Valid values are 1-5.
        """
        await self._device.async_quick_select_memory(quick_select_number)

    async def async_hdmi_cec_on(self) -> None:
        """Turn on HDMI CEC on receiver."""
        await self._device.async_hdmi_cec_on()

    async def async_hdmi_cec_off(self) -> None:
        """Turn off HDMI CEC on receiver."""
        await self._device.async_hdmi_cec_off()

    async def async_illumination(self, mode: Illuminations):
        """
        Set illumination mode on receiver.

        Only available for Marantz devices.
        """
        await self._device.async_illumination(mode)

    async def async_auto_lip_sync_on(self) -> None:
        """
        Turn on auto lip sync on receiver.

        Only available on Marantz devices.
        """
        await self._device.async_auto_lip_sync_on()

    async def async_auto_lip_sync_off(self) -> None:
        """
        Turn off auto lip sync on receiver.

        Only available on Marantz devices.
        """
        await self._device.async_auto_lip_sync_off()

    async def async_auto_lip_sync_toggle(self) -> None:
        """
        Toggle auto lip sync on receiver.

        Only available on Marantz devices and when using Telnet.
        """
        await self._device.async_auto_lip_sync_toggle()

    async def async_page_up(self) -> None:
        """Page Up on receiver."""
        await self._device.async_page_up()

    async def async_page_down(self) -> None:
        """Page Down on receiver."""
        await self._device.async_page_down()

    async def async_input_mode(self, mode: InputModes):
        """Set input mode on receiver."""
        await self._device.async_input_mode(mode)
