use console::Style;
use indicatif::{ProgressBar, ProgressStyle};
use std::borrow::Cow;
use std::sync::{Arc, Mutex};
use std::thread::{self, JoinHandle};
use std::time;

pub const PROGRESS_BARS_STYLE: &str =
    "{prefix:>12.bold.cyan} {percent:03}% [{bar:25}] {pos}/{len}: {msg} ({elapsed} ago, {eta} left)";

pub struct Spinner {
    pub spinner: ProgressBar,
    pub finished: Arc<Mutex<bool>>,
    pub thread: JoinHandle<()>,
}

impl Spinner {
    pub fn start(verb: &'static str, message: &str) -> Self {
        let spinner = ProgressBar::new(0).with_style(
            ProgressStyle::with_template(&format_log_msg_cyan(
                verb,
                &(message.to_owned() + "  {spinner:.cyan}"),
            ))
            .unwrap(),
        );
        spinner.tick();

        let thread_spinner = spinner.clone();
        let finished = Arc::new(Mutex::new(false));
        let thread_finished = Arc::clone(&finished);
        let spinner_thread = thread::spawn(move || {
            while !*thread_finished.lock().unwrap() {
                thread_spinner.tick();
                thread::sleep(time::Duration::from_millis(100));
            }
            thread_spinner.finish_and_clear();
        });

        Self {
            spinner: spinner.clone(),
            finished,
            thread: spinner_thread,
        }
    }

    pub fn end(self, message: &str) {
        self.spinner.finish_and_clear();
        *self.finished.lock().unwrap() = true;
        self.thread.join().unwrap();
        println!("{}", message);
    }
}

pub fn setup_progress_bar(total: u64, verb: &'static str) -> ProgressBar {
    indicatif::ProgressBar::new(total)
        .with_prefix(verb)
        .with_style(
            indicatif::ProgressStyle::with_template(PROGRESS_BARS_STYLE)
                .unwrap()
                .progress_chars("=> "),
        )
}

pub trait Log {
    fn log(&self, verb: &'static str, message: &str);
}

pub fn format_log_msg(verb: &'static str, message: &str) -> String {
    let style = Style::new().bold().green();
    format!("{} {}", style.apply_to(format!("{verb:>12}")), message)
}

pub fn format_log_msg_cyan(verb: &'static str, message: &str) -> String {
    let style = Style::new().bold().cyan();
    format!("{} {}", style.apply_to(format!("{verb:>12}")), message)
}

impl Log for ProgressBar {
    fn log(&self, verb: &'static str, message: &str) {
        self.println(format_log_msg(verb, message));
    }
}

impl Log for Option<&ProgressBar> {
    fn log(&self, verb: &'static str, message: &str) {
        if let Some(pb) = self {
            pb.println(format_log_msg(verb, message));
        }
    }
}

pub trait MaybeProgressBar<'a> {
    fn set_message(&'a self, message: impl Into<Cow<'static, str>>);
    fn inc(&'a self, n: u64);
    fn println(&'a self, message: impl AsRef<str>);
}

impl<'a> MaybeProgressBar<'a> for Option<&'a ProgressBar> {
    fn set_message(&'a self, message: impl Into<Cow<'static, str>>) {
        if let Some(pb) = self {
            pb.set_message(message);
        }
    }

    fn inc(&'a self, n: u64) {
        if let Some(pb) = self {
            pb.inc(n);
        }
    }

    fn println(&'a self, message: impl AsRef<str>) {
        if let Some(pb) = self {
            pb.println(message);
        }
    }
}
