use lazy_static::lazy_static;

use std::env;
use std::error::Error as StdError;
use std::io::Write;
use std::time::Instant;

use atty;
use chrono::Duration;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

use errors::Error;
use site::Site;

lazy_static! {
    /// Termcolor color choice.
    /// We do not rely on ColorChoice::Auto behavior
    /// as the check is already performed by has_color.
    static ref COLOR_CHOICE: ColorChoice =
        if has_color() {
            ColorChoice::Always
        } else {
            ColorChoice::Never
        };
}

pub fn info(message: &str) {
    colorize(message, ColorSpec::new().set_bold(true));
}

pub fn warn(message: &str) {
    colorize(message, ColorSpec::new().set_bold(true).set_fg(Some(Color::Yellow)));
}

pub fn success(message: &str) {
    colorize(message, ColorSpec::new().set_bold(true).set_fg(Some(Color::Green)));
}

pub fn error(message: &str) {
    colorize(message, ColorSpec::new().set_bold(true).set_fg(Some(Color::Red)));
}

/// Print a colorized message to stdout
fn colorize(message: &str, color: &ColorSpec) {
    let mut stdout = StandardStream::stdout(*COLOR_CHOICE);
    stdout.set_color(color).unwrap();
    write!(&mut stdout, "{}", message).unwrap();
    stdout.set_color(&ColorSpec::new()).unwrap();
    writeln!(&mut stdout).unwrap();
}

/// Display in the console the number of pages/sections in the site, and number of images to process
pub fn notify_site_size(site: &Site) {
    let library = site.library.read().unwrap();
    println!(
        "-> Creating {} pages ({} orphan), {} sections, and processing {} images",
        library.pages().len(),
        site.get_number_orphan_pages(),
        library.sections().len() - 1, // -1 since we do not count the index as a section there
        site.num_img_ops(),
    );
}

/// Display in the console only the number of pages/sections in the site
pub fn check_site_summary(site: &Site) {
    let library = site.library.read().unwrap();
    let orphans = library.get_all_orphan_pages();
    println!(
        "-> Site content: {} pages ({} orphan), {} sections",
        library.pages().len(),
        orphans.len(),
        library.sections().len() - 1, // -1 since we do not count the index as a section there
    );

    for orphan in orphans {
        warn(&format!("Orphan page found: {}", orphan.path));
    }
}

/// Display a warning in the console if there are ignored pages in the site
pub fn warn_about_ignored_pages(site: &Site) {
    let library = site.library.read().unwrap();
    let ignored_pages: Vec<_> = library
        .sections_values()
        .iter()
        .flat_map(|s| s.ignored_pages.iter().map(|k| library.get_page_by_key(*k).file.path.clone()))
        .collect();

    if !ignored_pages.is_empty() {
        warn(&format!(
            "{} page(s) ignored (missing date or weight in a sorted section):",
            ignored_pages.len()
        ));
        for path in ignored_pages {
            warn(&format!("- {}", path.display()));
        }
    }
}

/// Print the time elapsed rounded to 1 decimal
pub fn report_elapsed_time(instant: Instant) {
    let duration_ms = Duration::from_std(instant.elapsed()).unwrap().num_milliseconds() as f64;

    if duration_ms < 1000.0 {
        success(&format!("Done in {}ms.\n", duration_ms));
    } else {
        let duration_sec = duration_ms / 1000.0;
        success(&format!("Done in {:.1}s.\n", ((duration_sec * 10.0).round() / 10.0)));
    }
}

/// Display an error message and the actual error(s)
pub fn unravel_errors(message: &str, error: &Error) {
    if !message.is_empty() {
        self::error(message);
    }
    self::error(&format!("Error: {}", error));
    let mut cause = error.source();
    while let Some(e) = cause {
        self::error(&format!("Reason: {}", e));
        cause = e.source();
    }
}

/// Check whether to output colors
fn has_color() -> bool {
    let use_colors = env::var("CLICOLOR").unwrap_or_else(|_| "1".to_string()) != "0"
        && env::var("NO_COLOR").is_err();
    let force_colors = env::var("CLICOLOR_FORCE").unwrap_or_else(|_| "0".to_string()) != "0";

    force_colors || use_colors && atty::is(atty::Stream::Stdout)
}
