// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// All files in the project carrying such notice may not be copied, modified, or distributed
// except according to those terms.

use std::collections::HashMap;
use std::fmt::Debug;
use std::fs::read_dir;
use std::io::{self, Write};
use std::path::Path;

use utils::{get_between_quotes, read_file};

const TO_IGNORE: &'static [&'static str] = &["src/um/sspi.rs"];

fn check_if_in_build<P: Debug>(
    path: &P,
    include: &str,
    entries: &[String],
    errors: &mut u32
) -> bool {
    for entry in entries {
        if &include == &entry {
            return true
        }
    }
    writeln!(&mut io::stderr(), "{:?}: include not found: \"{}\"\n", path, include).unwrap();
    *errors += 1;
    false
}

fn should_be_ignored(path: &Path) -> bool {
    let path_str = &path.to_str().unwrap().replace("\\", "/");
    TO_IGNORE.iter().find(|x| *x == path_str).is_some()
}

fn check_file_deps<P: AsRef<Path>>(
    p: P,
    files_deps: &mut HashMap<String, Vec<String>>,
    errors: &mut u32,
    level: u32
) {
    let r_p = p.as_ref();
    if should_be_ignored(&r_p) {
        return
    }
    let filename = if level < 2 {
        r_p.file_name().unwrap().to_str().unwrap().to_owned()
    } else {
        let values: Vec<String> = r_p.iter().skip(1)
            .map(|x| x.to_str().unwrap().to_owned()).collect();
        values[values.len() - 2..].join("-")
    };
    let mut found: Vec<String> = Vec::new();
    {
        if let Some(entries) = files_deps.get_mut(&filename) {
            let file_content = read_file(r_p);
            for line in file_content.lines() {
                if !line.starts_with("use ") && !line.starts_with("pub use ") {
                    continue
                }
                let include: Vec<&str> = line.split("::").skip(1).collect();
                if include.len() < 2 || include[0].starts_with('{') {
                    continue
                }
                let include = if include.len() > 2 {
                    include[..include.len() - 1].join("-").to_owned()
                } else {
                    include[0].to_owned()
                };
                if check_if_in_build(&r_p, &include, &entries, errors)
                    && found.iter().find(|x| **x == include).is_none() {
                    found.push(include);
                }
            }
            if found.len() != entries.len() {
                for found in found {
                    if let Some(pos) = entries.iter().position(|x| **x == found) {
                        entries.remove(pos);
                    }
                }
                if !entries.is_empty() {
                    writeln!(&mut io::stderr(), "{}: include not used: {:?}", filename, entries)
                        .expect("stderr::write failed");
                    *errors += 1;
                }
            }
        } else if level > 0 && filename != "mod.rs" && !filename.ends_with("-mod.rs") {
            writeln!(&mut io::stderr(), "\"{}\" not found in build.rs",
                     p.as_ref().to_str().unwrap()).unwrap();
            *errors += 1;
        }
    }
    files_deps.remove(&filename);
}

fn read_dirs<P: AsRef<Path>>(
    dir: P,
    files_deps: &mut HashMap<String, Vec<String>>,
    errors: &mut u32,
    level: u32
) {
    for entry in read_dir(dir).expect("read_dir failed") {
        let entry = entry.expect("entry failed");
        let path = entry.path();
        if path.is_dir() {
            read_dirs(path, files_deps, errors, level + 1);
        } else {
            check_file_deps(path, files_deps, errors, level);
        }
    }
}

#[test]
fn check_imports() {
    let content = read_file("build.rs");
    let mut inside = false;
    let mut files_deps: HashMap<String, Vec<String>> = HashMap::new();
    for line in content.lines() {
        let line = line.trim_left();
        if !inside && line.starts_with("const DATA: ") {
            inside = true;
        } else if inside == true {
            let line = line.trim_left();
            if line.starts_with("//") {
                continue;
            } else if !line.starts_with("(\"") {
                break;
            }
            let parts: Vec<&str> = line.split("&[").collect();
            files_deps.insert(format!("{}.rs", get_between_quotes(parts[0])),
                              parts[1].split(',')
                                      .map(|x| get_between_quotes(x).to_owned())
                                      .filter(|x| !x.is_empty())
                                      .collect());
        }
    }
    let mut errors = 0;
    read_dirs("src", &mut files_deps, &mut errors, 0);
    assert!(errors == 0, "Missing or extra declaration(s) found");
    let keys: Vec<&String> = files_deps.keys().collect();
    if !keys.is_empty() {
        writeln!(&mut io::stderr(), "file(s) not found: {:?}\n", keys).unwrap();
        panic!();
    }
}
