use core::{
    ecs::Entity,
    prefab::{Prefab, PrefabError, PrefabProxy},
    state::StateToken,
    Ignite, Scalar,
};
use serde::{Deserialize, Serialize};
use std::{
    borrow::Cow,
    collections::HashMap,
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
};

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum AudioSourceDirtyMode {
    None,
    Param,
    All,
}

impl Default for AudioSourceDirtyMode {
    fn default() -> Self {
        Self::None
    }
}

impl From<u8> for AudioSourceDirtyMode {
    fn from(value: u8) -> Self {
        match value {
            1 => Self::Param,
            2 => Self::All,
            _ => Self::None,
        }
    }
}

impl From<AudioSourceDirtyMode> for u8 {
    fn from(v: AudioSourceDirtyMode) -> Self {
        match v {
            AudioSourceDirtyMode::Param => 1,
            AudioSourceDirtyMode::All => 2,
            _ => 0,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioSourceConfig {
    pub audio: Cow<'static, str>,
    #[serde(default)]
    pub streaming: bool,
    #[serde(default)]
    pub looped: bool,
    #[serde(default = "AudioSourceConfig::default_playback_rate")]
    pub playback_rate: Scalar,
    #[serde(default = "AudioSourceConfig::default_volume")]
    pub volume: Scalar,
    #[serde(default)]
    pub play: bool,
}

impl AudioSourceConfig {
    fn default_playback_rate() -> Scalar {
        1.0
    }

    fn default_volume() -> Scalar {
        1.0
    }

    pub fn new(audio: Cow<'static, str>) -> Self {
        Self {
            audio,
            streaming: false,
            looped: false,
            playback_rate: 1.0,
            volume: 1.0,
            play: false,
        }
    }

    pub fn audio(mut self, value: Cow<'static, str>) -> Self {
        self.audio = value;
        self
    }

    pub fn streaming(mut self, value: bool) -> Self {
        self.streaming = value;
        self
    }

    pub fn looped(mut self, value: bool) -> Self {
        self.looped = value;
        self
    }

    pub fn playback_rate(mut self, value: Scalar) -> Self {
        self.playback_rate = value;
        self
    }

    pub fn volume(mut self, value: Scalar) -> Self {
        self.volume = value;
        self
    }

    pub fn play(mut self, value: bool) -> Self {
        self.play = value;
        self
    }
}

impl Prefab for AudioSourceConfig {}

#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
pub struct AudioSource {
    audio: Cow<'static, str>,
    #[serde(default)]
    streaming: bool,
    #[serde(default)]
    looped: bool,
    #[serde(default)]
    playback_rate: Scalar,
    #[serde(default)]
    volume: Scalar,
    #[serde(default)]
    play: bool,
    #[serde(default)]
    pub(crate) current_time: Option<Scalar>,
    #[serde(skip)]
    #[ignite(ignore)]
    pub(crate) ready: Arc<AtomicBool>,
    #[serde(skip)]
    #[ignite(ignore)]
    pub(crate) dirty: AudioSourceDirtyMode,
}

impl Default for AudioSource {
    fn default() -> Self {
        Self {
            audio: "".into(),
            streaming: false,
            looped: false,
            playback_rate: 1.0,
            volume: 1.0,
            play: false,
            current_time: None,
            ready: Arc::new(AtomicBool::new(false)),
            dirty: AudioSourceDirtyMode::None,
        }
    }
}

impl From<AudioSourceConfig> for AudioSource {
    fn from(config: AudioSourceConfig) -> Self {
        Self::new_complex(
            config.audio,
            config.streaming,
            config.looped,
            config.playback_rate,
            config.volume,
            config.play,
        )
    }
}

impl AudioSource {
    pub fn new(audio: Cow<'static, str>, streaming: bool) -> Self {
        Self {
            audio,
            streaming,
            looped: false,
            playback_rate: 1.0,
            volume: 1.0,
            play: false,
            current_time: None,
            ready: Arc::new(AtomicBool::new(false)),
            dirty: AudioSourceDirtyMode::All,
        }
    }

    pub fn new_play(audio: Cow<'static, str>, streaming: bool, play: bool) -> Self {
        Self {
            audio,
            streaming,
            looped: false,
            playback_rate: 1.0,
            volume: 1.0,
            play,
            current_time: None,
            ready: Arc::new(AtomicBool::new(false)),
            dirty: AudioSourceDirtyMode::All,
        }
    }

    pub fn new_complex(
        audio: Cow<'static, str>,
        streaming: bool,
        looped: bool,
        playback_rate: Scalar,
        volume: Scalar,
        play: bool,
    ) -> Self {
        Self {
            audio,
            streaming,
            looped,
            playback_rate,
            volume,
            play,
            current_time: None,
            ready: Arc::new(AtomicBool::new(false)),
            dirty: AudioSourceDirtyMode::All,
        }
    }

    pub fn audio(&self) -> &str {
        &self.audio
    }

    pub fn streaming(&self) -> bool {
        self.streaming
    }

    pub fn looped(&self) -> bool {
        self.looped
    }

    pub fn set_looped(&mut self, looped: bool) {
        self.looped = looped;
        self.dirty = {
            let o: u8 = self.dirty.into();
            let n: u8 = AudioSourceDirtyMode::Param.into();
            o.max(n).into()
        };
    }

    pub fn playback_rate(&self) -> Scalar {
        self.playback_rate
    }

    pub fn set_playback_rate(&mut self, playback_rate: Scalar) {
        self.playback_rate = playback_rate;
        self.dirty = {
            let o: u8 = self.dirty.into();
            let n: u8 = AudioSourceDirtyMode::Param.into();
            o.max(n).into()
        };
    }

    pub fn volume(&self) -> Scalar {
        self.volume
    }

    pub fn set_volume(&mut self, volume: Scalar) {
        self.volume = volume;
        self.dirty = {
            let o: u8 = self.dirty.into();
            let n: u8 = AudioSourceDirtyMode::Param.into();
            o.max(n).into()
        };
    }

    pub fn current_time(&self) -> Option<Scalar> {
        self.current_time
    }

    pub fn must_play(&self) -> bool {
        self.play
    }

    pub fn play(&mut self) {
        self.play = true;
        self.dirty = {
            let o: u8 = self.dirty.into();
            let n: u8 = AudioSourceDirtyMode::All.into();
            o.max(n).into()
        };
    }

    pub fn stop(&mut self) {
        self.play = false;
        self.dirty = {
            let o: u8 = self.dirty.into();
            let n: u8 = AudioSourceDirtyMode::All.into();
            o.max(n).into()
        };
    }

    pub fn is_ready(&self) -> bool {
        self.ready.load(Ordering::Relaxed)
    }
}

pub type AudioSourcePrefabProxy = AudioSourceConfig;

impl PrefabProxy<AudioSourcePrefabProxy> for AudioSource {
    fn from_proxy_with_extras(
        proxy: AudioSourcePrefabProxy,
        _: &HashMap<String, Entity>,
        _: StateToken,
    ) -> Result<Self, PrefabError> {
        Ok(proxy.into())
    }
}
