use crate::app_state::AppState;
use bevy::{
    asset::{AssetServer, Assets, Handle},
    audio::{Audio, AudioSource, PlaybackSettings},
    ecs::{
        schedule::State,
        system::{Commands, Res, ResMut},
    },
    math::Vec2,
    sprite::TextureAtlas,
};
use fxhash::FxHashMap;
use lewton::inside_ogg::OggStreamReader;
use std::io::Cursor;

pub struct SparksAtlas(pub Handle<TextureAtlas>);

#[derive(Default)]
pub struct AudioServerInternal {
    server: FxHashMap<&'static str, Handle<AudioSource>>,
}

impl AudioServerInternal {
    pub fn get<P: AsRef<str>>(&self, path: P) -> Option<Handle<AudioSource>> {
        self.server.get(path.as_ref()).map(Clone::clone)
    }
}

pub fn load_setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut atlases: ResMut<Assets<TextureAtlas>>,
) {
    // Load assets.
    let sparks_th = asset_server.load("sparks.png");
    let sparks_atlas =
        TextureAtlas::from_grid(sparks_th, Vec2::new(27.0, 29.0), 7, 1);
    let sparks_ah = atlases.add(sparks_atlas);
    commands.insert_resource(SparksAtlas(sparks_ah));

    // This server is later populated in `decode_audio`.
    commands.insert_resource(AudioServerInternal::default());
}

pub fn decode_audio(
    asset_server: Res<AssetServer>,
    mut sources: ResMut<Assets<AudioSource>>,
    mut asi: ResMut<AudioServerInternal>,
    audio: Res<Audio>,
    mut app_state: ResMut<State<AppState>>,
) {
    let mut all_loaded = true;

    for path in [
        "bevy_test.ogg",
        "clang.ogg",
        "clang1.ogg",
        "wood_strike.ogg",
        "wood_strike1.ogg",
    ] {
        if !asi.server.contains_key(path) {
            let source_handle: Handle<AudioSource> = asset_server.load(path);

            if let Some(source) = sources.get(source_handle.clone()) {
                let mut ogg_vorbis_rdr =
                    OggStreamReader::new(Cursor::new(&source.bytes))
                        .expect("Malformed Ogg Vorbis file");
                let mut wav_data = Vec::new();
                let wav_cursor = Cursor::new(&mut wav_data);
                let mut wav_writer = hound::WavWriter::new(
                    wav_cursor,
                    hound::WavSpec {
                        channels: u16::from(
                            ogg_vorbis_rdr.ident_hdr.audio_channels,
                        ),
                        sample_rate: ogg_vorbis_rdr
                            .ident_hdr
                            .audio_sample_rate,
                        bits_per_sample: 16,
                        sample_format: hound::SampleFormat::Int,
                    },
                )
                .unwrap();

                while let Some(pkt) = ogg_vorbis_rdr
                    .read_dec_packet_itl()
                    .expect("Malformed Ogg Vorbis file")
                {
                    let mut wav_i16_writer =
                        wav_writer.get_i16_writer(pkt.len() as u32);
                    for samp in pkt {
                        wav_i16_writer.write_sample(samp);
                    }
                    wav_i16_writer.flush().unwrap();
                }

                wav_writer.flush().unwrap();
                wav_writer.finalize().unwrap();

                let wav_handle = sources.add(AudioSource {
                    bytes: wav_data.into(),
                });
                asi.server.insert(path, wav_handle.clone());

                sources.remove(source_handle);

                if path == "bevy_test.ogg" {
                    // Start playing music.
                    audio.play_with_settings(
                        wav_handle,
                        PlaybackSettings {
                            repeat: true,
                            volume: 0.875,
                            ..Default::default()
                        },
                    );
                }
            } else {
                all_loaded = false;
            }
        }
    }

    if all_loaded {
        app_state.set(AppState::Play).unwrap();
    }
}
