//! A simple progress bar
//!
//! A single thread non-optimized progress bar
use std::io::{self, Write};

/// A Simple ASCII Progress Bar
pub(crate) struct ProgressReport<'a> {
    curr: f32,
    text: String,
    hidden: bool,

    len: usize,
    pos: u64,
    msg: Option<Box<dyn Fn() -> String + 'a>>,
}

impl<'a> ProgressReport<'a> {
    pub(crate) fn new(len: usize) -> ProgressReport<'a> {
        ProgressReport { curr: 0.0, text: String::new(), hidden: false, len, pos: 0, msg: None }
    }

    pub(crate) fn hidden() -> ProgressReport<'a> {
        ProgressReport { curr: 0.0, text: String::new(), hidden: true, len: 0, pos: 0, msg: None }
    }

    pub(crate) fn set_message(&mut self, msg: impl Fn() -> String + 'a) {
        if !self.hidden {
            self.msg = Some(Box::new(msg));
        }
        self.tick();
    }

    pub(crate) fn println<I: Into<String>>(&mut self, msg: I) {
        self.clear();
        println!("{}", msg.into());
        self.tick();
    }

    pub(crate) fn inc(&mut self, delta: u64) {
        self.pos += delta;
        if self.len == 0 {
            self.set_value(0.0)
        } else {
            self.set_value((self.pos as f32) / (self.len as f32))
        }
        self.tick();
    }

    pub(crate) fn finish_and_clear(&mut self) {
        self.clear();
    }

    pub(crate) fn tick(&mut self) {
        if self.hidden {
            return;
        }
        let percent = (self.curr * 100.0) as u32;
        let text = format!(
            "{}/{} {percent:3>}% {}",
            self.pos,
            self.len,
            self.msg.as_ref().map_or_else(String::new, |it| it())
        );
        self.update_text(&text);
    }

    fn update_text(&mut self, text: &str) {
        // Get length of common portion
        let mut common_prefix_length = 0;
        let common_length = usize::min(self.text.len(), text.len());

        while common_prefix_length < common_length
            && text.chars().nth(common_prefix_length).unwrap()
                == self.text.chars().nth(common_prefix_length).unwrap()
        {
            common_prefix_length += 1;
        }

        // Backtrack to the first differing character
        let mut output = String::new();
        output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length);
        // Output new suffix, using chars() iter to ensure unicode compatibility
        output.extend(text.chars().skip(common_prefix_length));

        // If the new text is shorter than the old one: delete overlapping characters
        if let Some(overlap_count) = self.text.len().checked_sub(text.len())
            && overlap_count > 0
        {
            output += &" ".repeat(overlap_count);
            output += &"\x08".repeat(overlap_count);
        }

        let _ = io::stdout().write(output.as_bytes());
        let _ = io::stdout().flush();
        text.clone_into(&mut self.text);
    }

    fn set_value(&mut self, value: f32) {
        self.curr = value.clamp(0.0, 1.0);
    }

    fn clear(&mut self) {
        if self.hidden {
            return;
        }

        // Fill all last text to space and return the cursor
        let spaces = " ".repeat(self.text.len());
        let backspaces = "\x08".repeat(self.text.len());
        print!("{backspaces}{spaces}{backspaces}");
        let _ = io::stdout().flush();

        self.text = String::new();
    }
}
