use serde::{Deserialize, Serialize};

use symphonia::core::codecs::audio::{AudioCodecId, well_known::{CODEC_ID_AAC, CODEC_ID_AC4, CODEC_ID_ADPCM_G722, CODEC_ID_ADPCM_G726, CODEC_ID_ADPCM_G726LE, CODEC_ID_ADPCM_IMA_QT, CODEC_ID_ADPCM_IMA_WAV, CODEC_ID_ADPCM_MS, CODEC_ID_ALAC, CODEC_ID_ATRAC1, CODEC_ID_ATRAC3, CODEC_ID_ATRAC9, CODEC_ID_ATRAC3PLUS, CODEC_ID_DCA, CODEC_ID_EAC3, CODEC_ID_FLAC, CODEC_ID_MONKEYS_AUDIO, CODEC_ID_MP1, CODEC_ID_MP2, CODEC_ID_MP3, CODEC_ID_MUSEPACK, CODEC_ID_OPUS, CODEC_ID_PCM_ALAW, CODEC_ID_PCM_F32BE, CODEC_ID_PCM_F32BE_PLANAR, CODEC_ID_PCM_F32LE, CODEC_ID_PCM_F32LE_PLANAR, CODEC_ID_PCM_F64BE, CODEC_ID_PCM_F64BE_PLANAR, CODEC_ID_PCM_F64LE, CODEC_ID_PCM_F64LE_PLANAR, CODEC_ID_PCM_MULAW, CODEC_ID_PCM_S8, CODEC_ID_PCM_S8_PLANAR, CODEC_ID_PCM_S16BE, CODEC_ID_PCM_S16BE_PLANAR, CODEC_ID_PCM_S16LE, CODEC_ID_PCM_S16LE_PLANAR, CODEC_ID_PCM_S24BE, CODEC_ID_PCM_S24BE_PLANAR, CODEC_ID_PCM_S24LE, CODEC_ID_PCM_S24LE_PLANAR, CODEC_ID_PCM_S32BE, CODEC_ID_PCM_S32BE_PLANAR, CODEC_ID_PCM_S32LE, CODEC_ID_PCM_S32LE_PLANAR, CODEC_ID_PCM_U8, CODEC_ID_PCM_U8_PLANAR, CODEC_ID_PCM_U16BE, CODEC_ID_PCM_U16BE_PLANAR, CODEC_ID_PCM_U16LE, CODEC_ID_PCM_U16LE_PLANAR, CODEC_ID_PCM_U24BE, CODEC_ID_PCM_U24BE_PLANAR, CODEC_ID_PCM_U24LE, CODEC_ID_PCM_U24LE_PLANAR, CODEC_ID_PCM_U32BE, CODEC_ID_PCM_U32BE_PLANAR, CODEC_ID_PCM_U32LE, CODEC_ID_PCM_U32LE_PLANAR, CODEC_ID_SPEEX, CODEC_ID_TTA, CODEC_ID_VORBIS, CODEC_ID_WAVPACK, CODEC_ID_WMA}};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum CodecError {
    #[error("Unknown or unsupported codec: {0}")]
    Unknown(String),

    #[error("Codec '{0:?}' is missing an encoder")]
    NoEncoder(Codec),

    #[error("A codec is missing a symphonia decoder")]
    NoDecoder,

    #[error("Codec '{0:?}' is an unsupported codec")]
    UnsupportedCodec(Codec),
}

/// All supported audio codecs
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum Codec {
    Aac,
    Ac4,
    AdpcmG722,
    AdpcmG726,
    AdpcmG726Le,
    AdpcmImaQt,
    AdpcmImaWav,
    AdpcmMs,
    Alac,
    Atrac1,
    Atrac3,
    Atrac9,
    Atrac3Plus,
    Dca,
    Eac3,
    Flac,
    MonkeysAudio,
    Mp1,
    Mp2,
    Mp3,
    Musepack,
    Opus,
    PcmAlaw,
    PcmF32Be,
    PcmF32BePlanar,
    PcmF32Le,
    PcmF32LePlanar,
    PcmF64Be,
    PcmF64BePlanar,
    PcmF64Le,
    PcmF64LePlanar,
    PcmMulaw,
    PcmS8,
    PcmS8Planar,
    PcmS16Be,
    PcmS16BePlanar,
    PcmS16Le,
    PcmS16LePlanar,
    PcmS24Be,
    PcmS24BePlanar,
    PcmS24Le,
    PcmS24LePlanar,
    PcmS32Be,
    PcmS32BePlanar,
    PcmS32Le,
    PcmS32LePlanar,
    PcmU8,
    PcmU8Planar,
    PcmU16Be,
    PcmU16BePlanar,
    PcmU16Le,
    PcmU16LePlanar,
    PcmU24Be,
    PcmU24BePlanar,
    PcmU24Le,
    PcmU24LePlanar,
    PcmU32Be,
    PcmU32BePlanar,
    PcmU32Le,
    PcmU32LePlanar,
    Speex,
    Tta,
    Vorbis,
    WavPack,
    Wma,
}

impl Codec {
    #[must_use]
    pub fn is_pcm(&self) -> bool {
        matches!(
            self,
            Codec::PcmAlaw
                | Codec::PcmF32Be
                | Codec::PcmF32BePlanar
                | Codec::PcmF32Le
                | Codec::PcmF32LePlanar
                | Codec::PcmF64Be
                | Codec::PcmF64BePlanar
                | Codec::PcmF64Le
                | Codec::PcmF64LePlanar
                | Codec::PcmMulaw
                | Codec::PcmS8
                | Codec::PcmS8Planar
                | Codec::PcmS16Be
                | Codec::PcmS16BePlanar
                | Codec::PcmS16Le
                | Codec::PcmS16LePlanar
                | Codec::PcmS24Be
                | Codec::PcmS24BePlanar
                | Codec::PcmS24Le
                | Codec::PcmS24LePlanar
                | Codec::PcmS32Be
                | Codec::PcmS32BePlanar
                | Codec::PcmS32Le
                | Codec::PcmS32LePlanar
                | Codec::PcmU8
                | Codec::PcmU8Planar
                | Codec::PcmU16Be
                | Codec::PcmU16BePlanar
                | Codec::PcmU16Le
                | Codec::PcmU16LePlanar
                | Codec::PcmU24Be
                | Codec::PcmU24BePlanar
                | Codec::PcmU24Le
                | Codec::PcmU24LePlanar
                | Codec::PcmU32Be
                | Codec::PcmU32BePlanar
                | Codec::PcmU32Le
                | Codec::PcmU32LePlanar
                | Codec::Tta
                | Codec::WavPack
        )
    }

    #[must_use]
    pub fn is_lossless(&self) -> bool {
        matches!(
            self,
            Codec::Alac
                | Codec::Flac
                | Codec::MonkeysAudio
                | Codec::PcmF32Be
                | Codec::PcmF32BePlanar
                | Codec::PcmF32Le
                | Codec::PcmF32LePlanar
                | Codec::PcmF64Be
                | Codec::PcmF64BePlanar
                | Codec::PcmF64Le
                | Codec::PcmF64LePlanar
                | Codec::PcmS8
                | Codec::PcmS8Planar
                | Codec::PcmS16Be
                | Codec::PcmS16BePlanar
                | Codec::PcmS16Le
                | Codec::PcmS16LePlanar
                | Codec::PcmS24Be
                | Codec::PcmS24BePlanar
                | Codec::PcmS24Le
                | Codec::PcmS24LePlanar
                | Codec::PcmS32Be
                | Codec::PcmS32BePlanar
                | Codec::PcmS32Le
                | Codec::PcmS32LePlanar
                | Codec::PcmU8
                | Codec::PcmU8Planar
                | Codec::PcmU16Be
                | Codec::PcmU16BePlanar
                | Codec::PcmU16Le
                | Codec::PcmU16LePlanar
                | Codec::PcmU24Be
                | Codec::PcmU24BePlanar
                | Codec::PcmU24Le
                | Codec::PcmU24LePlanar
                | Codec::PcmU32Be
                | Codec::PcmU32BePlanar
                | Codec::PcmU32Le
                | Codec::PcmU32LePlanar
        )
    }

    #[must_use]
    pub fn is_flac(&self) -> bool {
        matches!(self, Self::Flac)
    }

    #[must_use]
    pub fn is_mp3(&self) -> bool {
        matches!(self, Self::Mp3)
    }

    #[must_use]
    pub fn is_opus(&self) -> bool {
        matches!(self, Self::Opus)
    }

    #[must_use]
    pub fn is_vorbis(&self) -> bool {
        matches!(self, Self::Vorbis)
    }

    #[must_use]
    pub(crate) fn is_ape(&self) -> bool {
        matches!(self, Self::MonkeysAudio)
    }
}

impl TryFrom<AudioCodecId> for Codec {
    type Error = CodecError;

    fn try_from(value: AudioCodecId) -> Result<Codec, CodecError> {
        let codec = match value {
            CODEC_ID_AAC => Codec::Aac,
            CODEC_ID_AC4 => Codec::Ac4,
            CODEC_ID_ADPCM_G722 => Codec::AdpcmG722,
            CODEC_ID_ADPCM_G726 => Codec::AdpcmG726,
            CODEC_ID_ADPCM_G726LE => Codec::AdpcmG726Le,
            CODEC_ID_ADPCM_IMA_QT => Codec::AdpcmImaQt,
            CODEC_ID_ADPCM_IMA_WAV => Codec::AdpcmImaWav,
            CODEC_ID_ADPCM_MS => Codec::AdpcmMs,
            CODEC_ID_ALAC => Codec::Alac,
            CODEC_ID_ATRAC1 => Codec::Atrac1,
            CODEC_ID_ATRAC3 => Codec::Atrac3,
            CODEC_ID_ATRAC9 => Codec::Atrac9,
            CODEC_ID_ATRAC3PLUS => Codec::Atrac3Plus,
            CODEC_ID_DCA => Codec::Dca,
            CODEC_ID_EAC3 => Codec::Eac3,
            CODEC_ID_FLAC => Codec::Flac,
            CODEC_ID_MONKEYS_AUDIO => Codec::MonkeysAudio,
            CODEC_ID_MP1 => Codec::Mp1,
            CODEC_ID_MP2 => Codec::Mp2,
            CODEC_ID_MP3 => Codec::Mp3,
            CODEC_ID_MUSEPACK => Codec::Musepack,
            CODEC_ID_OPUS => Codec::Opus,
            CODEC_ID_PCM_ALAW => Codec::PcmAlaw,
            CODEC_ID_PCM_F32BE => Codec::PcmF32Be,
            CODEC_ID_PCM_F32BE_PLANAR => Codec::PcmF32BePlanar,
            CODEC_ID_PCM_F32LE => Codec::PcmF32Le,
            CODEC_ID_PCM_F32LE_PLANAR => Codec::PcmF32LePlanar,
            CODEC_ID_PCM_F64BE => Codec::PcmF64Be,
            CODEC_ID_PCM_F64BE_PLANAR => Codec::PcmF64BePlanar,
            CODEC_ID_PCM_F64LE => Codec::PcmF64Le,
            CODEC_ID_PCM_F64LE_PLANAR => Codec::PcmF64LePlanar,
            CODEC_ID_PCM_MULAW => Codec::PcmMulaw,
            CODEC_ID_PCM_S8 => Codec::PcmS8,
            CODEC_ID_PCM_S8_PLANAR => Codec::PcmS8Planar,
            CODEC_ID_PCM_S16BE => Codec::PcmS16Be,
            CODEC_ID_PCM_S16BE_PLANAR => Codec::PcmS16BePlanar,
            CODEC_ID_PCM_S16LE => Codec::PcmS16Le,
            CODEC_ID_PCM_S16LE_PLANAR => Codec::PcmS16LePlanar,
            CODEC_ID_PCM_S24BE => Codec::PcmS24Be,
            CODEC_ID_PCM_S24BE_PLANAR => Codec::PcmS24BePlanar,
            CODEC_ID_PCM_S24LE => Codec::PcmS24Le,
            CODEC_ID_PCM_S24LE_PLANAR => Codec::PcmS24LePlanar,
            CODEC_ID_PCM_S32BE => Codec::PcmS32Be,
            CODEC_ID_PCM_S32BE_PLANAR => Codec::PcmS32BePlanar,
            CODEC_ID_PCM_S32LE => Codec::PcmS32Le,
            CODEC_ID_PCM_S32LE_PLANAR => Codec::PcmS32LePlanar,
            CODEC_ID_PCM_U8 => Codec::PcmU8,
            CODEC_ID_PCM_U8_PLANAR => Codec::PcmU8Planar,
            CODEC_ID_PCM_U16BE => Codec::PcmU16Be,
            CODEC_ID_PCM_U16BE_PLANAR => Codec::PcmU16BePlanar,
            CODEC_ID_PCM_U16LE => Codec::PcmU16Le,
            CODEC_ID_PCM_U16LE_PLANAR => Codec::PcmU16LePlanar,
            CODEC_ID_PCM_U24BE => Codec::PcmU24Be,
            CODEC_ID_PCM_U24BE_PLANAR => Codec::PcmU24BePlanar,
            CODEC_ID_PCM_U24LE => Codec::PcmU24Le,
            CODEC_ID_PCM_U24LE_PLANAR => Codec::PcmU24LePlanar,
            CODEC_ID_PCM_U32BE => Codec::PcmU32Be,
            CODEC_ID_PCM_U32BE_PLANAR => Codec::PcmU32BePlanar,
            CODEC_ID_PCM_U32LE => Codec::PcmU32Le,
            CODEC_ID_PCM_U32LE_PLANAR => Codec::PcmU32LePlanar,
            CODEC_ID_SPEEX => Codec::Speex,
            CODEC_ID_TTA => Codec::Tta,
            CODEC_ID_VORBIS => Codec::Vorbis,
            CODEC_ID_WAVPACK => Codec::WavPack,
            CODEC_ID_WMA => Codec::Wma,
            other => return Err(CodecError::Unknown(other.to_string())),
        };
        Ok(codec)
    }
}
