pub type Sample = i16;
pub type PcmBuf = Vec<Sample>;

pub mod g722;
pub mod g729;
#[cfg(feature = "opus")]
pub mod opus;
pub mod pcma;
pub mod pcmu;
pub mod resampler;
pub mod telephone_event;
pub use resampler::{Resampler, resample};

#[derive(Debug, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub enum CodecType {
    PCMU,
    PCMA,
    G722,
    G729,
    #[cfg(feature = "opus")]
    Opus,
    TelephoneEvent,
}

pub trait Decoder: Send + Sync {
    /// Decode encoded audio data into PCM samples
    fn decode(&mut self, data: &[u8]) -> PcmBuf;

    /// Get the sample rate of the decoded audio
    fn sample_rate(&self) -> u32;

    /// Get the number of channels
    fn channels(&self) -> u16;
}

pub trait Encoder: Send + Sync {
    /// Encode PCM samples into codec-specific format
    fn encode(&mut self, samples: &[Sample]) -> Vec<u8>;

    /// Get the sample rate expected for input samples
    fn sample_rate(&self) -> u32;

    /// Get the number of channels expected for input
    fn channels(&self) -> u16;
}

pub fn create_decoder(codec: CodecType) -> Box<dyn Decoder> {
    match codec {
        CodecType::PCMU => Box::new(pcmu::PcmuDecoder::new()),
        CodecType::PCMA => Box::new(pcma::PcmaDecoder::new()),
        CodecType::G722 => Box::new(g722::G722Decoder::new()),
        CodecType::G729 => Box::new(g729::G729Decoder::new()),
        #[cfg(feature = "opus")]
        CodecType::Opus => Box::new(opus::OpusDecoder::new_default()),
        CodecType::TelephoneEvent => Box::new(telephone_event::TelephoneEventDecoder::new()),
    }
}

pub fn create_encoder(codec: CodecType) -> Box<dyn Encoder> {
    match codec {
        CodecType::PCMU => Box::new(pcmu::PcmuEncoder::new()),
        CodecType::PCMA => Box::new(pcma::PcmaEncoder::new()),
        CodecType::G722 => Box::new(g722::G722Encoder::new()),
        CodecType::G729 => Box::new(g729::G729Encoder::new()),
        #[cfg(feature = "opus")]
        CodecType::Opus => Box::new(opus::OpusEncoder::new_default()),
        CodecType::TelephoneEvent => Box::new(telephone_event::TelephoneEventEncoder::new()),
    }
}

#[cfg(feature = "opus")]
pub fn create_opus_encoder(
    sample_rate: u32,
    channels: u16,
    application: opus::OpusApplication,
) -> Box<dyn Encoder> {
    Box::new(opus::OpusEncoder::new_with_application(
        sample_rate,
        channels,
        application,
    ))
}

#[cfg(feature = "opus")]
pub fn create_opus_decoder(sample_rate: u32, channels: u16) -> Box<dyn Decoder> {
    Box::new(opus::OpusDecoder::new(sample_rate, channels))
}

impl CodecType {
    pub fn mime_type(&self) -> &str {
        match self {
            CodecType::PCMU => "audio/PCMU",
            CodecType::PCMA => "audio/PCMA",
            CodecType::G722 => "audio/G722",
            CodecType::G729 => "audio/G729",
            #[cfg(feature = "opus")]
            CodecType::Opus => "audio/opus",
            CodecType::TelephoneEvent => "audio/telephone-event",
        }
    }
    pub fn rtpmap(&self) -> &str {
        match self {
            CodecType::PCMU => "PCMU/8000",
            CodecType::PCMA => "PCMA/8000",
            CodecType::G722 => "G722/8000",
            CodecType::G729 => "G729/8000",
            #[cfg(feature = "opus")]
            CodecType::Opus => "opus/48000/2",
            CodecType::TelephoneEvent => "telephone-event/8000",
        }
    }
    pub fn fmtp(&self) -> Option<&str> {
        match self {
            CodecType::PCMU => None,
            CodecType::PCMA => None,
            CodecType::G722 => None,
            CodecType::G729 => None,
            #[cfg(feature = "opus")]
            CodecType::Opus => Some("minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1"),
            CodecType::TelephoneEvent => Some("0-16"),
        }
    }

    pub fn clock_rate(&self) -> u32 {
        match self {
            CodecType::PCMU => 8000,
            CodecType::PCMA => 8000,
            CodecType::G722 => 8000,
            CodecType::G729 => 8000,
            #[cfg(feature = "opus")]
            CodecType::Opus => 48000,
            CodecType::TelephoneEvent => 8000,
        }
    }

    pub fn channels(&self) -> u16 {
        match self {
            #[cfg(feature = "opus")]
            CodecType::Opus => 2,
            _ => 1,
        }
    }

    pub fn payload_type(&self) -> u8 {
        match self {
            CodecType::PCMU => 0,
            CodecType::PCMA => 8,
            CodecType::G722 => 9,
            CodecType::G729 => 18,
            #[cfg(feature = "opus")]
            CodecType::Opus => 111,
            CodecType::TelephoneEvent => 101,
        }
    }
    pub fn samplerate(&self) -> u32 {
        match self {
            CodecType::PCMU => 8000,
            CodecType::PCMA => 8000,
            CodecType::G722 => 16000,
            CodecType::G729 => 8000,
            #[cfg(feature = "opus")]
            CodecType::Opus => 48000,
            CodecType::TelephoneEvent => 8000,
        }
    }
    pub fn is_audio(&self) -> bool {
        match self {
            CodecType::PCMU | CodecType::PCMA | CodecType::G722 => true,
            CodecType::G729 => true,
            #[cfg(feature = "opus")]
            CodecType::Opus => true,
            _ => false,
        }
    }

    pub fn is_dynamic(&self) -> bool {
        match self {
            #[cfg(feature = "opus")]
            CodecType::Opus => true,
            CodecType::TelephoneEvent => true,
            _ => false,
        }
    }
}

impl TryFrom<u8> for CodecType {
    type Error = anyhow::Error;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(CodecType::PCMU),
            8 => Ok(CodecType::PCMA),
            9 => Ok(CodecType::G722),
            18 => Ok(CodecType::G729), // Static payload type
            // Dynamic payload type shoulw get from the rtpmap in sdp offer, leave this for backward compatibility
            101 => Ok(CodecType::TelephoneEvent),
            #[cfg(feature = "opus")]
            111 => Ok(CodecType::Opus), // Dynamic payload type
            _ => Err(anyhow::anyhow!("Invalid codec type: {}", value)),
        }
    }
}

impl TryFrom<&str> for CodecType {
    type Error = anyhow::Error;

    fn try_from(name: &str) -> Result<Self, Self::Error> {
        match name.to_lowercase().as_str() {
            "pcmu" | "ulaw" => Ok(CodecType::PCMU),
            "pcma" | "alaw" => Ok(CodecType::PCMA),
            "g722" => Ok(CodecType::G722),
            "g729" => Ok(CodecType::G729),
            #[cfg(feature = "opus")]
            "opus" => Ok(CodecType::Opus),
            "telephone-event" => Ok(CodecType::TelephoneEvent),
            _ => Err(anyhow::anyhow!("Invalid codec name: {}", name)),
        }
    }
}

#[cfg(target_endian = "little")]
pub fn samples_to_bytes(samples: &[Sample]) -> Vec<u8> {
    unsafe {
        std::slice::from_raw_parts(
            samples.as_ptr() as *const u8,
            samples.len() * std::mem::size_of::<Sample>(),
        )
        .to_vec()
    }
}

#[cfg(target_endian = "big")]
pub fn samples_to_bytes(samples: &[Sample]) -> Vec<u8> {
    samples.iter().flat_map(|s| s.to_le_bytes()).collect()
}

#[cfg(target_endian = "little")]
pub fn bytes_to_samples(u8_data: &[u8]) -> PcmBuf {
    unsafe {
        std::slice::from_raw_parts(
            u8_data.as_ptr() as *const Sample,
            u8_data.len() / std::mem::size_of::<Sample>(),
        )
        .to_vec()
    }
}
#[cfg(target_endian = "big")]
pub fn bytes_to_samples(u8_data: &[u8]) -> PcmBuf {
    u8_data
        .chunks(2)
        .map(|chunk| (chunk[0] as i16) | ((chunk[1] as i16) << 8))
        .collect()
}
