use crate::error::{Error, Result};
use crate::manifest::Name;
use crate::Test;
use std::collections::BTreeMap as Map;
use std::path::PathBuf;

#[derive(Debug)]
pub(crate) struct ExpandedTest {
    pub name: Name,
    pub test: Test,
    pub error: Option<Error>,
    is_from_glob: bool,
}

pub(crate) fn expand_globs(tests: &[Test]) -> Vec<ExpandedTest> {
    let mut set = ExpandedTestSet::new();

    for test in tests {
        match test.path.to_str() {
            Some(utf8) if utf8.contains('*') => match glob(utf8) {
                Ok(paths) => {
                    let expected = test.expected;
                    for path in paths {
                        set.insert(Test { path, expected }, None, true);
                    }
                }
                Err(error) => set.insert(test.clone(), Some(error), false),
            },
            _ => set.insert(test.clone(), None, false),
        }
    }

    set.vec
}

struct ExpandedTestSet {
    vec: Vec<ExpandedTest>,
    path_to_index: Map<PathBuf, usize>,
}

impl ExpandedTestSet {
    fn new() -> Self {
        ExpandedTestSet {
            vec: Vec::new(),
            path_to_index: Map::new(),
        }
    }

    fn insert(&mut self, test: Test, error: Option<Error>, is_from_glob: bool) {
        if let Some(&i) = self.path_to_index.get(&test.path) {
            let prev = &mut self.vec[i];
            if prev.is_from_glob {
                prev.test.expected = test.expected;
                return;
            }
        }

        let index = self.vec.len();
        let name = Name(format!("trybuild{:03}", index));
        self.path_to_index.insert(test.path.clone(), index);
        self.vec.push(ExpandedTest {
            name,
            test,
            error,
            is_from_glob,
        });
    }
}

fn glob(pattern: &str) -> Result<Vec<PathBuf>> {
    let mut paths = glob::glob(pattern)?
        .map(|entry| entry.map_err(Error::from))
        .collect::<Result<Vec<PathBuf>>>()?;
    paths.sort();
    Ok(paths)
}
