use std::io::Write;
use std::sync::{Arc, Mutex};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

const WIN_DEF_BG: Option<Color> = Some(Color::Black);
const WIN_DEF_FG: Option<Color> = Some(Color::White);

pub struct ColorTerm {
    stdout: Mutex<StandardStream>,
    color_spec: Mutex<ColorSpec>,
}

impl ColorTerm {
    fn new() -> ColorTerm {
        let mut color_spec = ColorSpec::new();
        if cfg!(target_os = "windows") {
            color_spec.set_bg(WIN_DEF_BG).set_fg(WIN_DEF_FG);
        }
        ColorTerm {
            stdout: Mutex::new(StandardStream::stdout(ColorChoice::Always)),
            color_spec: Mutex::new(color_spec),
        }
    }

    pub fn cout<T: AsRef<str>>(&self, text: T, color_spec: &ColorSpec) {
        let mut stdout = self.stdout.lock().unwrap();
        stdout.set_color(color_spec).expect("Set color fail");
        write!(stdout, "{}", text.as_ref()).expect("Write color output failed");
        stdout.set_color(&*self.color_spec.lock().unwrap()).expect(
            "Set color fail",
        );
    }

    pub fn out<T: AsRef<str>>(&self, text: T) {
        write!(self.stdout.lock().unwrap(), "{}", text.as_ref()).expect("Write color output failed");
    }

    pub fn set_default_color(&self, fg: Option<Option<Color>>, bg: Option<Option<Color>>, bold: Option<bool>) {
        let mut spec = self.color_spec.lock().unwrap();
        if let Some(fg) = fg {
            let fg = if cfg!(target_os = "windows") && fg.is_none() {
                WIN_DEF_FG
            } else {
                fg
            };
            spec.set_fg(fg);
        }
        if let Some(bg) = bg {
            let bg = if cfg!(target_os = "windows") && bg.is_none() {
                WIN_DEF_BG
            } else {
                bg
            };
            spec.set_bg(bg);
        }
        if let Some(bold) = bold {
            spec.set_bold(bold);
        }
        self.stdout.lock().unwrap().set_color(&*spec).expect(
            "set color fail",
        );
    }
}

lazy_static! {
    pub static ref TERM: Arc<ColorTerm> = Arc::new(ColorTerm::new());
}

lazy_static! {
    pub static ref VULNER_SPEC: ColorSpec = {
        let mut spec = ColorSpec::new();
        spec.set_fg(Some(Color::Red)).set_bold(true);
        spec
    };
    pub static ref NO_VULNER_SPEC: ColorSpec = {
        let mut spec = ColorSpec::new();
        spec.set_fg(Some(Color::Green)).set_bold(true);
        spec
    };
    pub static ref ERR_SPEC: ColorSpec = {
        let mut spec = ColorSpec::new();
        spec.set_bg(Some(Color::Magenta));
        spec
    };
}
