use std::string::ToString;

use space_crush_common::{misc::LogResult, resources::Global};
use specs::{prelude::*, Entities, ReadExpect, System};

use crate::{
    misc::{Text, TextCache, TextManager},
    resources::InputState,
    Error,
};

/* Summary */

pub struct Summary {
    cache: TextCache,
    text: Text,
    fps: usize,
    resolution: (u32, u32),
    mouse_pos: (f32, f32),
    entity_count: usize,
}

impl Summary {
    pub fn new(text_manager: &TextManager) -> Result<Self, Error> {
        let cache = text_manager.create_cache()?;
        let text = cache
            .new_text()
            .scale(12.0)
            .font("resources/fonts/DroidSansMono.ttf")
            .color(0.7, 0.7, 0.7, 1.0)
            .position(5.0, 5.0)
            .text(format!("Space Crush v{}\n", env!("CARGO_PKG_VERSION")))
            .text("\nfps:        ")
            .text("-")
            .text("\nresolution: ")
            .text("1280 | 720")
            .text("\nmouse_pos:  ")
            .text("0.00 | 0.00")
            .text("\nentities:   ")
            .text("0")
            .build()?;

        Ok(Self {
            cache,
            text,
            fps: 0,
            resolution: (0, 0),
            mouse_pos: (0.0, 0.0),
            entity_count: 0,
        })
    }
}

#[derive(SystemData)]
pub struct SummaryData<'a> {
    entities: Entities<'a>,
    global: ReadExpect<'a, Global>,
    input_state: ReadExpect<'a, InputState>,
}

impl<'a> System<'a> for Summary {
    type SystemData = SummaryData<'a>;

    fn run(&mut self, data: Self::SystemData) {
        let SummaryData {
            entities,
            global,
            input_state,
        } = data;

        let entity_count = entities.par_join().count();

        let guard = self.cache.begin_update();
        update_text(
            &mut self.text,
            2,
            &mut self.fps,
            &global.fps,
            ToString::to_string,
        );
        update_text(
            &mut self.text,
            4,
            &mut self.resolution,
            &input_state.resolution,
            |(w, h)| format!("{} | {}", w, h),
        );
        update_text(
            &mut self.text,
            6,
            &mut self.mouse_pos,
            &input_state.mouse_pos,
            |(x, y)| format!("{:.2} | {:.2}", x, y),
        );
        update_text(
            &mut self.text,
            8,
            &mut self.entity_count,
            &entity_count,
            |x| format!("{}", x),
        );
        drop(guard);

        self.text.render(true);
    }
}

fn update_text<T, F>(text: &mut Text, pos: usize, old: &mut T, new: &T, update: F)
where
    T: PartialEq + Copy,
    F: FnOnce(&T) -> String,
{
    if old != new {
        *old = *new;
        let s = update(old);
        text.update(pos, s).error("Unable to update debug text");
    }
}
