use bevy::asset::AsyncReadExt;
use std::str::Utf8Error;
use thiserror::Error;

use bevy::{
    asset::{io::Reader, AssetLoader, LoadContext},
    reflect::{TypePath, TypeUuid},
    utils::BoxedFuture,
};

use crate::prelude::*;

pub(crate) struct CreditsPlugin;

impl Plugin for CreditsPlugin {
    fn build(&self, app: &mut App) {
        app.register_asset_loader(CreditsTextLoader)
            .init_asset::<CreditsText>()
            .add_systems(Startup, init_credits_ui)
            .add_systems(
                Update,
                (
                    update_credits.run_if(on_event::<AssetEvent<CreditsText>>()),
                    menu::exit_to_menu.run_if(in_state(GameState::Credits)),
                ),
            )
            .add_systems(OnEnter(GameState::Credits), activate::<Credits>)
            .add_systems(OnExit(GameState::Credits), deactivate::<Credits>);
    }
}

#[derive(Debug, TypeUuid, TypePath, Asset)]
#[uuid = "43df4a09-b5f0-4619-9223-8cf67dc9e844"]
pub struct CreditsText {
    sections: Vec<TextSection>,
}

#[derive(Debug, Error)]
enum CreditsError {
    #[error("Failed to read file")]
    IO(#[from] std::io::Error),
    #[error("Failed to decode file")]
    Decode(#[from] Utf8Error),
}

#[derive(Default)]
struct CreditsTextLoader;

impl AssetLoader for CreditsTextLoader {
    type Asset = CreditsText;
    type Settings = ();
    type Error = CreditsError;

    fn load<'a>(
        &'a self,
        reader: &'a mut Reader,
        _settings: &'a Self::Settings,
        _load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
        Box::pin(async move {
            let mut bytes = Vec::new();
            reader.read_to_end(&mut bytes).await?;
            let result = parse_credits(bytes.as_slice())?;
            Ok(result)
        })
    }

    fn extensions(&self) -> &[&str] {
        &["credits.txt"]
    }
}

fn parse_credits(bytes: &[u8]) -> Result<CreditsText, Utf8Error> {
    let s = std::str::from_utf8(bytes)?;
    let sections: Vec<TextSection> = s
        .split('\n')
        .filter(|l| l.len() > 0)
        .map(|l| TextSection {
            value: String::from(l),
            ..default()
        })
        .collect();

    Ok(CreditsText { sections })
}

#[derive(Debug, Component)]
struct Credits;

fn init_credits_ui(mut commands: Commands, server: Res<AssetServer>) {
    commands
        .spawn((
            Credits,
            NodeBundle {
                style: Style {
                    width: Val::Percent(100.0),
                    height: Val::Percent(100.0),
                    justify_content: JustifyContent::Center,
                    align_items: AlignItems::Center,
                    position_type: PositionType::Absolute,
                    ..default()
                },
                visibility: Visibility::Hidden,
                ..default()
            },
        ))
        .with_children(|parent| {
            let handle: Handle<CreditsText> = server.load("martian-chess.credits.txt");

            parent.spawn((
                handle.clone(),
                TextBundle {
                    text: Text {
                        alignment: TextAlignment::Center,
                        sections: vec![],
                        ..default()
                    },
                    background_color: Color::BLACK.with_a(0.5).into(),
                    ..default()
                },
            ));
        });
}

fn update_credits(
    mut reader: EventReader<AssetEvent<CreditsText>>,
    credits_texts: Res<Assets<CreditsText>>,
    mut query: Query<(&mut Text, &Handle<CreditsText>)>,
) {
    reader.read().for_each(|event| match event {
        AssetEvent::Added { id }
        | AssetEvent::LoadedWithDependencies { id }
        | AssetEvent::Modified { id } => {
            query
                .iter_mut()
                .filter(|(_, this_handle)| *id == (*this_handle).id())
                .for_each(|(mut text, this_handle)| {
                    if let Some(credits_text) = credits_texts.get(this_handle) {
                        text.sections = credits_text.sections.clone();
                    }
                });
        }
        AssetEvent::Removed { .. } => {
            warn!("Removed Credit resource... we don't handle that...");
        }
    })
}
