use std::collections::HashMap;

use crate::{loading::AudioAssets, movement::MovementState, player::Unit};
use crate::{util::initiative_tracker::InitiativeTracker, GameState};
use bevy::prelude::*;
use bevy_kira_audio::{Audio, AudioChannel, AudioPlugin, AudioSource};

pub struct InternalAudioPlugin;
impl Plugin for InternalAudioPlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(AudioState::default())
            .add_event::<AttackSoundEvent>()
            .add_event::<CombatSoundEvent>()
            .add_event::<SetVolume>()
            .add_plugin(AudioPlugin)
            .add_system_set(SystemSet::on_enter(GameState::Menu).with_system(setup_menu_audio))
            .add_system_set(SystemSet::on_exit(GameState::Menu).with_system(stop_menu_audio))
            .add_system_set(SystemSet::on_enter(GameState::Playing).with_system(setup_audio_play))
            .add_system_set(
                SystemSet::on_update(GameState::Playing)
                    .with_system(control_flying_sound)
                    .with_system(play_attack_sound)
                    .with_system(play_combat_sound)
                    .with_system(set_volume),
            );
    }
}

// EVENTS
#[derive(Default)]
pub struct SetVolume(pub f32, pub String);

pub struct CombatSoundEvent(pub CombatSounds);

#[derive(Default, Clone, Copy)]
pub enum CombatSounds {
    #[default]
    EnterSlowMo,
    ExitSlowMo,
}

impl CombatSounds {
    pub fn channel(&self) -> AudioChannel {
        match self {
            CombatSounds::EnterSlowMo => AudioChannel::new("enter_slow_mo".to_owned()),
            CombatSounds::ExitSlowMo => AudioChannel::new("exit_slow_mo".to_owned()),
        }
    }

    pub fn audio_asset(&self, assets: &AudioAssets) -> Handle<AudioSource> {
        match self {
            CombatSounds::EnterSlowMo => assets.enter_slow_mo.clone(),
            CombatSounds::ExitSlowMo => assets.exit_slow_mo.clone(),
        }
    }
}

#[derive(Default)]
pub struct AttackSoundEvent(pub AttackSounds);

#[derive(Default, Clone, Copy)]
pub enum AttackSounds {
    #[default]
    Hit,
    Miss,
    Damage,
    Death,
}

impl AttackSounds {
    pub fn channel(&self) -> AudioChannel {
        match self {
            AttackSounds::Hit => AudioChannel::new("hit".to_owned()),
            AttackSounds::Miss => AudioChannel::new("miss".to_owned()),
            AttackSounds::Damage => AudioChannel::new("damage".to_owned()),
            AttackSounds::Death => AudioChannel::new("death".to_owned()),
        }
    }

    pub fn audio_asset(&self, assets: &AudioAssets) -> Handle<AudioSource> {
        match self {
            AttackSounds::Hit => assets.hit.clone(),
            AttackSounds::Miss => assets.miss.clone(),
            AttackSounds::Damage => assets.ouch.clone(),
            AttackSounds::Death => assets.death.clone(),
        }
    }
}

#[derive(Default)]
struct AudioState {
    pub channels: HashMap<AudioChannel, ChannelAudioState>,
}

struct ChannelAudioState {
    stopped: bool,
    paused: bool,
    loop_started: bool,
    volume: f32,
}

impl Default for ChannelAudioState {
    fn default() -> Self {
        ChannelAudioState {
            volume: 3.0,
            stopped: true,
            loop_started: false,
            paused: false,
        }
    }
}

fn setup_menu_audio(audio_assets: Res<AudioAssets>, audio: Res<Audio>) {
    let bgm = AudioChannel::new("bgm".to_owned());

    audio.play_looped_in_channel(audio_assets.e_synth_test.clone(), &bgm);
    audio.set_volume_in_channel(0.5, &bgm);
}

fn stop_menu_audio(audio: Res<Audio>) {
    let bgm = AudioChannel::new("bgm".to_owned());

    audio.stop_channel(&bgm);
    audio.set_volume_in_channel(0.5, &bgm);
}

fn setup_audio_play(
    mut audio_state: ResMut<AudioState>,
    audio_assets: Res<AudioAssets>,
    audio: Res<Audio>,
) {
    audio_state.channels.insert(
        AudioChannel::new("hit".to_owned()),
        ChannelAudioState::default(),
    );
    audio_state.channels.insert(
        AudioChannel::new("miss".to_owned()),
        ChannelAudioState::default(),
    );
    audio_state.channels.insert(
        AudioChannel::new("damage".to_owned()),
        ChannelAudioState::default(),
    );
    audio_state.channels.insert(
        AudioChannel::new("death".to_owned()),
        ChannelAudioState::default(),
    );

    audio_state.channels.insert(
        AudioChannel::new("enter_slow_mo".to_owned()),
        ChannelAudioState::default(),
    );
    audio_state.channels.insert(
        AudioChannel::new("exit_slow_mo".to_owned()),
        ChannelAudioState::default(),
    );

    let flying_channel = AudioChannel::new("flying".to_owned());
    let bgm = AudioChannel::new("bgm".to_owned());

    audio.play_looped_in_channel(audio_assets.high_elf_v0.clone(), &bgm);
    audio.set_volume_in_channel(0.5, &bgm);

    audio.play_looped_in_channel(audio_assets.flying.clone(), &flying_channel);
    audio.pause_channel(&flying_channel);

    audio_state
        .channels
        .insert(flying_channel, ChannelAudioState::default());
}

fn play_combat_sound(
    mut on_combat_sound: EventReader<CombatSoundEvent>,
    audio_assets: Res<AudioAssets>,
    audio: Res<Audio>,
) {
    for CombatSoundEvent(sound) in on_combat_sound.iter() {
        audio.play_in_channel(sound.audio_asset(&audio_assets), &sound.channel());
    }
}

fn play_attack_sound(
    mut atk_sound_evt: EventReader<AttackSoundEvent>,
    audio_assets: Res<AudioAssets>,
    audio: Res<Audio>,
) {
    for evt in atk_sound_evt.iter() {
        let atk_sound = evt.0;
        audio.play_in_channel(atk_sound.audio_asset(&audio_assets), &atk_sound.channel());
    }
}

fn set_volume(mut on_lower_bgm_volume: EventReader<SetVolume>, audio: Res<Audio>) {
    for SetVolume(vol, channel) in on_lower_bgm_volume.iter() {
        audio.set_volume_in_channel(*vol, &AudioChannel::new(channel.clone()));
    }
}

fn control_flying_sound(
    initiative_tracker: Res<InitiativeTracker>,
    is_moving_query: Query<(Entity, &MovementState), With<Unit>>,
    audio: Res<Audio>,
) {
    for (entity, movement_state) in is_moving_query.iter() {
        if !initiative_tracker.has_initiatives() || initiative_tracker.current_turn() != entity {
            continue;
        }

        if let MovementState::Move {
            is_moving: Some(_),
            path: _,
        } = &*movement_state
        {
            audio.resume_channel(&AudioChannel::new("flying".to_owned()));
        } else {
            audio.pause_channel(&AudioChannel::new("flying".to_owned()));
        }
    }
}
