import struct
from enum import Enum
from typing import TYPE_CHECKING

from parameter_decorators import path_to_str

from rpcclient.clients.darwin.consts import AVAudioSessionCategoryOptions, AVAudioSessionRouteSharingPolicy
from rpcclient.clients.darwin.symbol import DarwinSymbol
from rpcclient.core.allocated import Allocated
from rpcclient.exceptions import BadReturnValueError, RpcFailedToPlayError, RpcFailedToRecordError

if TYPE_CHECKING:
    from rpcclient.clients.darwin.client import DarwinClient


class AVAudioSessionCategory(Enum):
    """https://developer.apple.com/documentation/avfaudio/AVAudioSessionCategory?language=objc"""

    PlayAndRecord = "AVAudioSessionCategoryPlayAndRecord"
    Ambient = "AVAudioSessionCategoryAmbient"
    MultiRoute = "AVAudioSessionCategoryMultiRoute"
    Playback = "AVAudioSessionCategoryPlayback"
    Record = "AVAudioSessionCategoryRecord"
    SoloAmbient = "AVAudioSessionCategorySoloAmbient"
    AudioProcessing = "AVAudioSessionCategoryAudioProcessing"


class AVAudioSessionMode(Enum):
    """https://developer.apple.com/documentation/avfaudio/avaudiosessionmode?language=objc"""

    Default = "AVAudioSessionModeDefault"
    GameChat = "AVAudioSessionModeGameChat"
    Measurement = "AVAudioSessionModeMeasurement"
    MoviePlayback = "AVAudioSessionModeMoviePlayback"
    SpokenAudio = "AVAudioSessionModeSpokenAudio"
    VideoChat = "AVAudioSessionModeVideoChat"
    VideoRecording = "AVAudioSessionModeVideoRecording"
    VoiceChat = "AVAudioSessionModeVoiceChat"
    VoicePrompt = "AVAudioSessionModeVoicePrompt"


class InterruptionPriority(Enum):
    Default = 0
    PhoneCall = 10
    EmergencyAlert = 20


class Recorder(Allocated):
    """
    Wrapper for AVAudioRecorder
    https://developer.apple.com/documentation/avfaudio/avaudiorecorder?language=objc
    """

    def __init__(self, client: "DarwinClient", session: "AudioSession", recorder: DarwinSymbol):
        super().__init__()
        self._client = client
        self._session = session
        self._recorder = recorder

    def _deallocate(self):
        self._recorder.objc_call("release")

    def record(self):
        self._session.set_category(AVAudioSessionCategory.PlayAndRecord)
        self._session.set_active(True)
        self._recorder.objc_call("record")
        if not self.recording:
            raise RpcFailedToRecordError()

    def pause(self):
        self._recorder.objc_call("pause")
        self._session.set_active(False)

    def stop(self):
        self._recorder.objc_call("stop")
        self._session.set_active(False)

    def delete_recording(self):
        if not self._recorder.objc_call("deleteRecording"):
            raise BadReturnValueError("deleteRecording failed")

    @property
    def recording(self) -> bool:
        return bool(self._recorder.objc_call("isRecording"))


class Player(Allocated):
    """
    Wrapper for AVAudioPlayer
    https://developer.apple.com/documentation/avfaudio/avaudioplayer?language=objc
    """

    def __init__(self, client: "DarwinClient", session: "AudioSession", player: DarwinSymbol):
        super().__init__()
        self._client = client
        self._session = session
        self._player = player

    def _deallocate(self):
        self._player.objc_call("release")

    def play(self):
        self._session.set_category(AVAudioSessionCategory.PlayAndRecord)
        self._session.set_active(True)
        self._player.objc_call("play")
        if not self.playing:
            raise RpcFailedToPlayError()

    def pause(self):
        self._player.objc_call("pause")

    def stop(self):
        self._player.objc_call("stop")
        self._session.set_active(False)

    def set_volume(self, value: float):
        self._player.objc_call("setVolume:", struct.pack("<f", value))

    @property
    def playing(self) -> bool:
        return bool(self._player.objc_call("isPlaying"))

    @property
    def loops(self) -> int:
        return self._player.objc_call("numberOfLoops")

    @loops.setter
    def loops(self, value: int):
        self._player.objc_call("setNumberOfLoops:", value)


class AudioSession:
    """
    wrapper for AVAudioSession
    https://developer.apple.com/documentation/avfaudio/avaudiosession?language=objc
    """

    def __init__(self, client: "DarwinClient"):
        self._client = client
        self._session = self._client.symbols.objc_getClass("AVAudioSession").objc_call("sharedInstance")

    def set_active(self, is_active: bool):
        self._session.objc_call("setActive:error:", is_active, 0)

    def set_mode(self, mode: AVAudioSessionMode):
        mode = self._client.symbols[mode.value][0]
        self._session.objc_call("setMode:error:", mode, 0)

    def set_category(
        self,
        category: AVAudioSessionCategory,
        mode: AVAudioSessionMode = AVAudioSessionMode.Default,
        route_sharing_policy: AVAudioSessionRouteSharingPolicy = AVAudioSessionRouteSharingPolicy.Default,
        options: AVAudioSessionCategoryOptions = AVAudioSessionCategoryOptions.DefaultToSpeaker,
    ) -> None:
        category = self._client.symbols[category.value][0]
        mode = self._client.symbols[mode.value][0]
        self._session.objc_call(
            "setCategory:mode:routeSharingPolicy:options:error:", category, mode, route_sharing_policy, options, 0
        )

    def set_interruption_priority(self, priority: InterruptionPriority):
        self._session.objc_call("setInterruptionPriority:error:", priority, 0)

    def override_output_audio_port(self, port: int):
        self._session.objc_call("overrideOutputAudioPort:error:", port, 0)

    @property
    def other_audio_playing(self) -> bool:
        return bool(self._session.objc_call("isOtherAudioPlaying"))

    @property
    def record_permission(self):
        return struct.pack("<I", self._session.objc_call("recordPermission"))[::-1].decode()

    @property
    def available_categories(self) -> list[str]:
        return self._session.objc_call("availableCategories").py()

    @property
    def available_modes(self) -> list[str]:
        return self._session.objc_call("availableModes").py()

    @property
    def is_active(self) -> bool:
        return bool(self._session.objc_call("isActive"))


class DarwinMedia:
    """Media utils"""

    def __init__(self, client: "DarwinClient"):
        """
        :param rpcclient.darwin.client.DarwinClient client:
        """
        self._client = client
        self._client.load_framework("AVFoundation")
        self.session = AudioSession(self._client)

    @path_to_str("filename")
    def get_recorder(self, filename: str) -> Recorder:
        url = self._client.symbols.objc_getClass("NSURL").objc_call("fileURLWithPath:", self._client.cf(filename))
        settings = self._client.cf({
            "AVEncoderQualityKey": 100,
            "AVEncoderBitRateKey": 16,
            "AVNumberOfChannelsKey": 1,
            "AVSampleRateKey": 8000.0,
        })
        AVAudioRecorder = self._client.symbols.objc_getClass("AVAudioRecorder")
        recorder = AVAudioRecorder.objc_call("alloc").objc_call("initWithURL:settings:error:", url, settings, 0)

        return Recorder(self._client, self.session, recorder)

    @path_to_str("filename")
    def get_player(self, filename: str) -> Player:
        NSURL = self._client.symbols.objc_getClass("NSURL")
        url = NSURL.objc_call("fileURLWithPath:", self._client.cf(filename))

        AVAudioPlayer = self._client.symbols.objc_getClass("AVAudioPlayer")
        player = AVAudioPlayer.objc_call("alloc").objc_call("initWithContentsOfURL:error:", url, 0)

        return Player(self._client, self.session, player)
