use std::time::Duration;

///
/// Animated Text
///
/// The goal of this code is to get a rudimentary "Text Animation" system in place.
///
/// The use cases are:
/// * Typing Text; ala RPG dialogs like Pokemon
///
/// Eventually adding more would be cool, like movement animations, but those get really
/// complicated really fast...
///
use bevy::prelude::*;

pub struct AnimatedTextPlugin;

impl Plugin for AnimatedTextPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Update, (manage_texts, animate_texts));
    }
}

#[derive(Bundle, Default)]
pub struct AnimatedTextBundle {
    pub text_bundle: TextBundle,
    pub animated_text: AnimatedText,
}

/// Animated Text Marker
/// Use this to filter out entities managed by Text Animation systems
#[derive(Component, Default, Debug)]
pub struct AnimatedText {
    animation_type: Option<TextAnimationType>,
    animation_status: TextAnimationStatus,
    animation_duration: Option<Duration>,
}

/// Animated Text component
///
/// Handles all of the logistics of running text animation
impl AnimatedText {
    pub fn new(animation_type: TextAnimationType) -> Self {
        AnimatedText {
            animation_type: Some(animation_type),
            ..default()
        }
    }

    pub fn play(&mut self) {
        self.animation_status = TextAnimationStatus::Playing;
        self.animation_duration = match self.animation_type {
            None => None,
            Some(TextAnimationType::Typing(seconds)) => Some(Duration::from_secs_f32(seconds)),
        };
    }

    pub fn stop(&mut self) {
        self.animation_status = TextAnimationStatus::Stopped;
        self.animation_duration = None;
    }

    pub fn toggle(&mut self) {
        use TextAnimationStatus::*;
        self.animation_status = match self.animation_status {
            Playing => Stopped,
            Stopped => Playing,
        }
    }
}

#[derive(Debug)]
pub enum TextAnimationType {
    Typing(f32), // Typing text out for duration
}

#[derive(Default, Debug)]
enum TextAnimationStatus {
    Playing,
    #[default]
    Stopped,
}

/// Manage individual text entities
///
/// Animated text entities need to conform to different shapes for functional reasons.
/// For example if each letter needs to be a different color, each TextSection must be a single
/// character rather than a word or paragraph.
///
/// Any time a text is updated we need to ensure it conforms to the needs of it's animation
///
/// FIXME: Only update according to animation type
fn manage_texts(mut texts: Query<&mut Text, (Changed<Text>, With<AnimatedText>)>) {
    // Check if any Text entities are "dirty"
    let dirty = texts
        .iter()
        .any(|text| text.sections.iter().any(|section| section.value.len() > 1));
    // For each text
    //   For each section
    //     If section length > 1
    //       Break into length-1 strings
    if dirty {
        texts.iter_mut().for_each(|mut text| {
            // Replace the existing sections with broken down sections
            // Each Text Section is a single character
            // On it's own this should not cause the text to look different
            // But means we can modify each text section individually in color
            text.sections = text
                .sections
                .iter()
                .map(|section| {
                    section.value.chars().map(|c| TextSection {
                        value: c.into(),
                        style: section.style.clone(),
                    })
                })
                .flatten()
                .collect();
        });
    }
}

fn animate_texts(mut query: Query<(&mut Text, &mut AnimatedText)>, time: Res<Time>) {
    for (mut text, mut animated_text) in query.iter_mut() {
        match animated_text.animation_status {
            TextAnimationStatus::Stopped => (),
            TextAnimationStatus::Playing => match animated_text.animation_type {
                None => (),
                Some(TextAnimationType::Typing(seconds)) => {
                    animated_text.animation_duration = match animated_text.animation_duration {
                        None | Some(Duration::ZERO) => {
                            // Set sections to alpha 0 before animation begins
                            text.sections.iter_mut().for_each(|section| {
                                section.style.color.set_a(0.0);
                            });
                            // We have just (re)started the animation, so set the duration to full
                            Some(Duration::from_secs_f32(seconds))
                        }
                        // FIXME: Why can't mutate inner on animation_duration?
                        Some(inner) => {
                            {
                                // how far into the animation are we?
                                let percentage = 1.0 - (inner.as_secs_f32() / seconds);

                                // Find the total number of characters to be processed
                                // let len_total = text
                                //     .sections
                                //     .iter()
                                //     .fold(0, |acc, curr| acc + curr.value.len());
                                // Backup version:
                                let len_total = text.sections.len();

                                // Find the farthest character into the string to show
                                let target = (len_total as f32 * percentage) as usize;

                                // Assign all segments an alpha of 0 or 1.
                                // TODO: Incremental updates only: Start at target, work backward
                                // until you get to one with alpha 1 and then break
                                text.sections
                                    .iter_mut()
                                    .take(target)
                                    .rev()
                                    .take_while(|section| section.style.color.a() != 1.0)
                                    .for_each(|section| {
                                        section.style.color.set_a(1.0);
                                    });
                            }

                            // We are continuing the animation, so decrement the remaining duration
                            Some(inner.saturating_sub(time.delta()))
                        }
                    }
                }
            },
        }
    }
}
