///
/// TODO: Custom Asset: FmodEventMapper
///
use crate::prelude::*;
use bevy::prelude::*;
use bevy_fmod::prelude::AudioSource;
use bevy_fmod::prelude::*;

pub(crate) struct AudioPlugin;

impl Plugin for AudioPlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<AudioEvent>();

        app.add_plugins(FmodPlugin {
            audio_banks_paths: &[
                "./assets/audio/Martian Chess Audio/Build/Desktop/Master.bank",
                "./assets/audio/Martian Chess Audio/Build/Desktop/Master.strings.bank",
                "./assets/audio/Martian Chess Audio/Build/Desktop/Music.bank",
                "./assets/audio/Martian Chess Audio/Build/Desktop/SFX.bank",
            ],
        });

        app.init_resource::<AudioVolume>();
        app.add_systems(OnEnter(GameState::Menu), play_background);
        app.add_systems(
            Update,
            (
                play_audio.run_if(any_component_added::<AudioSource>),
                audio_trigger.run_if(on_event::<AudioEvent>()),
                control_volume.run_if(resource_changed::<AudioVolume>()),
                toggle_volume
                    .run_if(|keys: Res<Input<KeyCode>>| -> bool { keys.just_pressed(KeyCode::M) }),
            ),
        );
    }
}

#[derive(Event, Debug, PartialEq, Component, Clone)]
pub enum AudioEvent {
    MainMusic,
    _StopMainMusic,
    MenuHover,
    MenuSelect,
    PickUp,
    PutDown,
    Idle,
    StopIdle,
    Invalid,
}

#[derive(Resource, Debug)]
pub struct AudioVolume(f32);

impl Default for AudioVolume {
    fn default() -> Self {
        AudioVolume(1.0)
    }
}

fn play_background(mut events: EventWriter<AudioEvent>) {
    events.send(AudioEvent::MainMusic);
}

fn play_audio(mut events: Query<&AudioSource, Added<AudioSource>>) {
    events.iter_mut().for_each(|aud| aud.play());
}

fn audio_trigger(
    mut events: EventReader<AudioEvent>,
    sources: Query<(Entity, &AudioSource, &AudioEvent)>,
    studio: Res<FmodStudio>,
    mut commands: Commands,
    server: Res<AssetServer>,
    tweaks: Res<Assets<tweak::Tweaks>>,
    display_state: Res<State<DisplayState>>,
    vol: Res<AudioVolume>,
) {
    let tweak = tweaks
        .get(&server.load("martian.tweak.toml"))
        .expect("Load tweaks");
    let state = display_state.get();
    events.read().for_each(|event| {
        let aud = match event {
            AudioEvent::MainMusic | AudioEvent::_StopMainMusic => {
                tweak.get::<String>("audio_music_main").unwrap()
            }
            AudioEvent::MenuHover => tweak.get::<String>("audio_menu_hover").unwrap(),
            AudioEvent::MenuSelect => tweak.get::<String>("audio_menu_select").unwrap(),
            AudioEvent::PickUp => tweak.get::<String>("audio_display3d_pick_up").unwrap(),
            AudioEvent::PutDown => tweak.get::<String>("audio_display3d_put_down").unwrap(),
            AudioEvent::Idle | AudioEvent::StopIdle => {
                tweak.get::<String>("audio_display3d_idle").unwrap()
            }
            AudioEvent::Invalid => tweak.get::<String>("audio_display3d_invalid").unwrap(),
        };
        // There is an event, play an audio
        if !aud.is_empty() {
            let event_str = format!("event:{}", aud);
            if let Ok(event_description) = studio.0.get_event(event_str.as_str()) {
                // We are stopping a playing event
                match event {
                    // Find and stop playing instances of idle audio
                    AudioEvent::StopIdle => {
                        sources
                            .iter()
                            .filter(|(_, _, source_event)| **source_event == AudioEvent::Idle)
                            .for_each(|(entity, source, _)| {
                                info!("Stopping audio {}", event_str);
                                source.stop();
                                commands.entity(entity).despawn_recursive();
                            });
                    }
                    // Find and stop playing instances of main music
                    AudioEvent::_StopMainMusic => {
                        sources
                            .iter()
                            .filter(|(_, _, source_event)| **source_event == AudioEvent::MainMusic)
                            .for_each(|(entity, source, _)| {
                                info!("Stopping audio {}", event_str);
                                source.stop();
                                commands.entity(entity).despawn_recursive();
                            });
                    }
                    // we are playing a sound
                    _ => {
                        info!("Playing audio {}", event_str);
                        let audio_source = AudioSource::new(event_description);
                        audio_source.set_volume(vol.0);
                        // TODO: Can we batch spawn all sounds at startup? Then Start/Stop/Reset at runtime?
                        commands.spawn((audio_source, event.clone()));
                    }
                }
            } else {
                warn!("No music set for {:?} in {:?}", event, state);
            }
        }
    });
}

fn toggle_volume(mut vol: ResMut<AudioVolume>) {
    if vol.0 > 0.0 {
        vol.0 = 0.0;
    } else {
        vol.0 = 1.0;
    }
}

fn control_volume(vol: Res<AudioVolume>, query: Query<&AudioSource>) {
    query
        .iter()
        .for_each(|aud_src| aud_src.set_volume((*vol).0));
}
