// We have some long regex literals, so:
// ignore-tidy-linelength

extern crate rustc_serialize;
extern crate docopt;
use docopt::Docopt;
extern crate walkdir;
use std::{path, fs, io};
use std::io::{BufRead, Write};

fn main () {
    let args: Args = Docopt::new(USAGE)
    .and_then(|d| d.decode())
    .unwrap_or_else(|e| e.exit());

    let src_dir = &path::Path::new(&args.arg_src_dir);
    let found_errs = walkdir::WalkDir::new(src_dir)
        .min_depth(1)
        .into_iter()
        .map(|entry| {
            match entry {
                Ok(entry) => entry,
                Err(err) => {
                    eprintln!("{:?}", err);
                    std::process::exit(911)
                },
            }
        })
        .map(|entry| {
            let path = entry.path();
            if is_file_of_interest(path) {
                let err_vec = lint_file(path);
                for err in &err_vec {
                    match *err {
                        LintingError::LineOfInterest(line_num, ref line) =>
                            eprintln!("{}:{}\t{}", path.display(), line_num, line),
                        LintingError::UnableToOpenFile =>
                            eprintln!("Unable to open {}.", path.display()),
                    }
                }
                !err_vec.is_empty()
            } else {
                false
            }
        })
        .collect::<Vec<_>>()
        .iter()
        .any(|result| *result);

    if found_errs {
        std::process::exit(1)
    } else {
        std::process::exit(0)
    }
}

const USAGE: &'static str = "
counter
Usage:
  lfp <src-dir>
  lfp (-h | --help)
Options:
  -h --help         Show this screen.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_src_dir: String,
}

fn lint_file(path: &path::Path) -> Vec<LintingError> {
    match fs::File::open(path) {
        Ok(file) => lint_lines(io::BufReader::new(&file).lines()),
        Err(_) => vec![LintingError::UnableToOpenFile],
    }
}

fn lint_lines<I>(lines: I) -> Vec<LintingError>
    where I: Iterator<Item=io::Result<String>> {
    lines
        .enumerate()
        .map(|(line_num, line)| {
            let raw_line = line.unwrap();
            if is_line_of_interest(&raw_line) {
                Err(LintingError::LineOfInterest(line_num, raw_line))
            } else {
                Ok(())
            }
        })
        .filter(|result| result.is_err())
        .map(|result| result.unwrap_err())
        .collect()
}

fn is_file_of_interest(path: &path::Path) -> bool {
    path.extension()
        .map_or(false, |ext| ext == "md")
}

fn is_line_of_interest(line: &str) -> bool {
    !line.split_whitespace()
        .filter(|sub_string|
            sub_string.contains("file://") &&
            !sub_string.contains("file:///projects/")
        )
        .collect::<Vec<_>>()
        .is_empty()
}

#[derive(Debug)]
enum LintingError {
    UnableToOpenFile,
    LineOfInterest(usize, String)
}

#[cfg(test)]
mod tests {

    use std::path;

    #[test]
    fn lint_file_returns_a_vec_with_errs_when_lines_of_interest_are_found() {
        let string = r#"
        $ cargo run
               Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
                 Running `target/guessing_game`
            Guess the number!
            The secret number is: 61
            Please input your guess.
            10
            You guessed: 10
            Too small!
            Please input your guess.
            99
            You guessed: 99
            Too big!
            Please input your guess.
            foo
            Please input your guess.
            61
            You guessed: 61
            You win!
            $ cargo run
               Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
                 Running `target/debug/guessing_game`
            Guess the number!
            The secret number is: 7
            Please input your guess.
            4
            You guessed: 4
            $ cargo run
                 Running `target/debug/guessing_game`
            Guess the number!
            The secret number is: 83
            Please input your guess.
            5
            $ cargo run
               Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)
                 Running `target/debug/guessing_game`
            Hello, world!
        "#;

        let raw_lines = string.to_string();
        let lines = raw_lines.lines().map(|line| {
            Ok(line.to_string())
        });

        let result_vec = super::lint_lines(lines);

        assert!(!result_vec.is_empty());
        assert_eq!(3, result_vec.len());
    }

    #[test]
    fn lint_file_returns_an_empty_vec_when_no_lines_of_interest_are_found() {
        let string = r#"
            $ cargo run
               Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
                 Running `target/guessing_game`
            Guess the number!
            The secret number is: 61
            Please input your guess.
            10
            You guessed: 10
            Too small!
            Please input your guess.
            99
            You guessed: 99
            Too big!
            Please input your guess.
            foo
            Please input your guess.
            61
            You guessed: 61
            You win!
        "#;

        let raw_lines = string.to_string();
        let lines = raw_lines.lines().map(|line| {
            Ok(line.to_string())
        });

        let result_vec = super::lint_lines(lines);

        assert!(result_vec.is_empty());
    }

    #[test]
    fn is_file_of_interest_returns_false_when_the_path_is_a_directory() {
        let uninteresting_fn = "src/img";

        assert!(!super::is_file_of_interest(path::Path::new(uninteresting_fn)));
    }

    #[test]
    fn is_file_of_interest_returns_false_when_the_filename_does_not_have_the_md_extension() {
        let uninteresting_fn = "src/img/foo1.png";

        assert!(!super::is_file_of_interest(path::Path::new(uninteresting_fn)));
    }

    #[test]
    fn is_file_of_interest_returns_true_when_the_filename_has_the_md_extension() {
        let interesting_fn = "src/ch01-00-introduction.md";

        assert!(super::is_file_of_interest(path::Path::new(interesting_fn)));
    }

    #[test]
    fn is_line_of_interest_does_not_report_a_line_if_the_line_contains_a_file_url_which_is_directly_followed_by_the_project_path() {
        let sample_line = "Compiling guessing_game v0.1.0 (file:///projects/guessing_game)";

        assert!(!super::is_line_of_interest(sample_line));
    }

    #[test]
    fn is_line_of_interest_reports_a_line_if_the_line_contains_a_file_url_which_is_not_directly_followed_by_the_project_path() {
        let sample_line = "Compiling guessing_game v0.1.0 (file:///home/you/projects/guessing_game)";

        assert!(super::is_line_of_interest(sample_line));
    }
}
