Documentation
use super::*;

/// Represents an audio clip resource
pub enum AudioClipResource {
    /// Audio clip resource of `ogg` type
    Ogg(ffi::ResourceHandleRepr),
    /// Audio clip resource of `wav` type
    Wav(ffi::ResourceHandleRepr),
}

/// Represents an audio clip
///
/// Used with the `AudioSource` component to play audio

#[derive(Debug, Clone, PartialEq)]
pub struct AudioClip {
    data: WorldData,
}

#[allow(dead_code)]
impl AudioClip {
    /// Creates a new `AudioClip`. This creates a data object in the host that can be shared
    /// for multiple entities. Expects the data to be of 'ogg' type, otherwise invalid.
    pub fn new_ogg(data: &[u8]) -> Self {
        let data = WorldData::create(ffi::CreateDataType::AudioClipOgg, data);
        Self { data }
    }

    /// Creates a new `AudioClip`. This creates a data object in the host that can be shared
    /// for multiple entities. Expects the data to be of 'wav' type, otherwise invalid.
    pub fn new_wav(data: &[u8]) -> Self {
        let data = WorldData::create(ffi::CreateDataType::AudioClipWav, data);
        Self { data }
    }

    /// Creates a new `AudioClip`. This creates a data object in the host that can be shared
    /// for multiple entities. Will try to autodetect file format from the 4 first bytes in the data.
    pub fn new(data: &[u8]) -> Option<Self> {
        if data.len() < 4 {
            return None;
        }

        match &data[0..4] {
            b"OggS" => Some(Self::new_ogg(data)),
            b"RIFF" => Some(Self::new_wav(data)),
            _ => None,
        }
    }

    /// Creates a new `AudioClip` from a resource downloaded through the Resource Ark API
    ///
    /// This creates a data object in the host that can be shared for multiple
    /// entities.
    ///
    /// The provided resource must point to a loaded resource
    pub fn from_resource(resource: AudioClipResource) -> Self {
        let (data_type, data) = match resource {
            AudioClipResource::Ogg(handle) => (
                ffi::CreateDataType::AudioClipOggResource,
                ffi::v4::AudioClipResource { handle },
            ),
            AudioClipResource::Wav(handle) => (
                ffi::CreateDataType::AudioClipWavResource,
                ffi::v4::AudioClipResource { handle },
            ),
        };

        let data = WorldData::create_struct(data_type, &data);
        Self { data }
    }

    /// Creates the `AudioClip` from an audio module name and `data`.
    ///
    /// `module_dependency_name` is the module name, same as specified in module-dependencies in the Cargo.toml.
    pub fn from_audio_module(module_dependency_name: &'static str, data: &[u8]) -> Self {
        Self {
            data: WorldData::create_audio_module_data(module_dependency_name, data),
        }
    }

    /// Creates the `AudioClip` from an audio module name and `data` in a unsafe manner.
    ///
    /// `module_dependency_name` is the module name, same as specified in module-dependencies in the Cargo.toml.
    /// Takes a non-'static str as parameter.
    /// [`AudioClip::from_audio_module`] should be preferred over this.
    pub fn from_audio_module_dynamic(module_dependency_name: &str, data: &[u8]) -> Self {
        Self {
            data: WorldData::create_audio_module_data(module_dependency_name, data),
        }
    }

    /// Returns information about this `AudioClip`.
    pub fn info(&self) -> AudioClipInfo {
        self.data.retrieve_data(RetrieveDataType::Info)
    }

    /// Sets a debug name of this data object. Useful for debugging memory usage and leaks.
    pub fn set_debug_name(&self, name: &str) {
        self.data.set_debug_name(name);
    }
}

impl ValueConverterTrait<AudioClip> for ValueConverter {
    fn into_value(v: AudioClip) -> Value {
        <Self as ValueConverterTrait<WorldData>>::into_value(v.data)
    }
    fn from_value(v: &Value) -> AudioClip {
        AudioClip {
            data: <Self as ValueConverterTrait<WorldData>>::from_value(v),
        }
    }
}

/// `AudioSource` component.
///
/// Lets you emit audio from an entity.
/// Usually accessed through `entity.audio_source`().

pub struct AudioSource {
    id: Entity,
}

impl std::fmt::Debug for AudioSource {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AudioSource")
            .field("entity", &self.id.name())
            .finish_non_exhaustive()
    }
}

impl AudioSource {
    impl_world_accessor!(
        /// Returns a `ValueAccessor` for setting dynamic data that is passed to a module.
        /// Data is expected to be of type "Binary"
        ///
        /// Used to set/get the dynamic data.
        /// Sets which audio clip is attached to the AudioSource component of this entity (assuming it has one).
        AudioSource,
        DynamicModuleData,
        BinaryData,
        dynamic_module_data,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the audio clip from which the AudioSource component should play audio.
        ///
        /// Used to set/get the audio clip.
        /// Sets which audio clip is attached to the AudioSource component of this entity (assuming it has one).
        AudioSource,
        AudioClip,
        AudioClip,
        clip,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Controls the volume of the audio source
        AudioSource,
        Volume,
        f32,
        volume,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Controls what sample rate to read a clip with. In other words the speed at which the clip is played.
        AudioSource,
        Rate,
        f32,
        rate,
        ValueAccessorReadWriteAnimate
    );

    impl_world_accessor!(
        /// Controls whether the playing clip will loop
        AudioSource,
        Looping,
        bool,
        looping,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Will center the sound at the head position.
        AudioSource,
        Force2d,
        bool,
        force_2d,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// If set to false will not respond to `play()` until the currently playing sound has finished.
        AudioSource,
        AllowInterruption,
        bool,
        allow_interruption,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// If set to false, will not stop audio when the owning entity gets destroyed. Will only work for non-looping sounds.
        AudioSource,
        StopOnDestroy,
        bool,
        stop_on_destroy,
        ValueAccessorReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the set of player ids this audio source should be played for.
        ///
        /// Used to set/get the player id set.
        /// Sets which player id set is attached to the render component of this entity (assuming it has one).
        AudioSource,
        PlayerIdSet,
        PlayerIdSet,
        player_id_set,
        ValueAccessorDataReadWrite
    );

    impl_world_accessor!(
        /// Returns a `ValueAccessor` for the `AudioSource` "is playing" state.
        ///
        /// Used to get whether audio is playing or not. It will be set to `false` if the sound has reached to its end, unless the
        /// `AudioSource` is set to be looping. If the `AudioSource` is set to be looping it will only be `false`
        /// if `stop` has been called.
        AudioSource,
        IsPlaying,
        bool,
        is_playing,
        ValueAccessorRead
    );

    /// Starts playing audio from this audio source.
    /// Will stop audio currently playing on this audio source unless `allow_interruption` is set to false.
    ///
    /// This acts as if a speaker connected to a tape player is attached to the entity.
    pub fn play(&self) {
        World::set_entity_value(
            self.id,
            ffi::ComponentType::AudioSource,
            ffi::AudioSource::NextOperation.into(),
            &Value::from_i64(ffi::AudioSourceOperation::Start as i64),
        );
    }

    /// Stops playing audio from this audio source.
    pub fn stop(&self) {
        World::set_entity_value(
            self.id,
            ffi::ComponentType::AudioSource,
            ffi::AudioSource::NextOperation.into(),
            &Value::from_i64(ffi::AudioSourceOperation::Stop as i64),
        );
    }

    fn play_one_shot_internal(
        position: Option<Vec3>,
        clip: &AudioClip,
        volume: f32,
        rate: f32,
    ) -> Entity {
        let audio_source_entity = Entity::create_empty("audio_one_shot");

        if let Some(position) = position {
            audio_source_entity
                .add::<Transform>()
                .position()
                .set(position);
        }

        let audio_source = audio_source_entity.add::<AudioSource>();
        audio_source.clip().set(clip.clone());
        audio_source.volume().set(volume);
        audio_source.rate().set(rate);
        audio_source.play();

        // We mark this to not stop when it gets destroyed and then schedule it for destruction.
        audio_source.stop_on_destroy().set(false);
        audio_source_entity.schedule_for_destruction();

        audio_source_entity
    }

    /// Starts playing audio at a specified world position, or attached to the listener's world position if `None`.
    /// After it's started there's no way to stop or modify that audio signal. It will play the clip
    /// once at the location. Looping is not supported for this.
    ///
    /// This acts as if a speaker connected to a tape player is either left at the specified world position
    /// or attached to the listener (2d).
    ///
    /// `position` - Sets the position of the audio source. If `None` it will be playing at the position of the listener.
    /// `clip` - Sets the audio clip of the audio source for this one shot.
    /// `volume` - Sets the volume of this one shot
    /// `rate` - Sets the sample rate at which the clip is read with. In other words the speed at which the clip is played.
    pub fn play_one_shot(position: Option<Vec3>, clip: &AudioClip, volume: f32, rate: f32) {
        let _ = Self::play_one_shot_internal(position, clip, volume, rate);
    }
}

impl_world_component!(AudioSource);