//! FIXME discuss about which errors to print and when to panic

use std::path::Path;

const SEPARATOR_START: &str = "# BEGIN GENERATED FEATURES\n";
const SEPARATOR_END: &str = "# END GENERATED FEATURES\n";
const HELP: &str = "# Generated by stm32-gen-features. DO NOT EDIT.\n";

/// Get the list of all the chips and their supported cores
///
/// Print errors to `stderr` when something is returned by the glob but is not in the returned
/// [`Vec`]
///
/// This function is slow because all the yaml files are parsed.
pub fn chip_names_and_cores() -> Vec<(String, Vec<String>)> {
    glob::glob("../stm32-data/data/chips/*.json")
        .unwrap()
        .filter_map(|entry| entry.map_err(|e| eprintln!("{:?}", e)).ok())
        .filter_map(|entry| {
            if let Some(name) = entry.file_stem().and_then(|stem| stem.to_str()) {
                Some((name.to_lowercase(), chip_cores(&entry)))
            } else {
                eprintln!("{:?} is not a regular file", entry);
                None
            }
        })
        .collect()
}

/// Get the list of the cores of a chip by its associated file
///
/// # Panic
/// Panics if the file does not exist or if it contains yaml syntax errors.
/// Panics if "cores" is not an array.
fn chip_cores(path: &Path) -> Vec<String> {
    let file_contents = std::fs::read_to_string(path).unwrap();
    let doc = &yaml_rust::YamlLoader::load_from_str(&file_contents).unwrap()[0];
    doc["cores"]
        .as_vec()
        .unwrap_or_else(|| panic!("{:?}:[cores] is not an array", path))
        .iter()
        .enumerate()
        .map(|(i, core)| {
            core["name"]
                .as_str()
                .unwrap_or_else(|| panic!("{:?}:[cores][{}][name] is not a string", path, i))
                .to_owned()
        })
        .collect()
}

/// Generate data needed in `../embassy-stm32/Cargo.toml`
///
/// Print errors to `stderr` when something is returned by the glob but is not in the returned
/// [`Vec`]
///
/// # Panic
/// Panics if a file contains yaml syntax errors or if a value does not have a consistent type
pub fn embassy_stm32_needed_data(names_and_cores: &[(String, Vec<String>)]) -> String {
    let mut result = String::new();
    for (chip_name, cores) in names_and_cores {
        if cores.len() > 1 {
            for core_name in cores.iter() {
                result += &format!(
                    "{chip}-{core} = [ \"stm32-metapac/{chip}-{core}\" ]\n",
                    chip = chip_name,
                    core = core_name
                );
            }
        } else {
            result += &format!("{chip} = [ \"stm32-metapac/{chip}\" ]\n", chip = chip_name);
        }
    }
    result
}

/// Generate data needed in `../stm32-metapac/Cargo.toml`
///
/// Print errors to `stderr` when something is returned by the glob but is not in the returned
/// [`Vec`]
///
/// # Panic
/// Panics if a file contains yaml syntax errors or if a value does not have a consistent type
pub fn stm32_metapac_needed_data(names_and_cores: &[(String, Vec<String>)]) -> String {
    let mut result = String::new();
    for (chip_name, cores) in names_and_cores {
        if cores.len() > 1 {
            for core_name in cores {
                result += &format!("{}-{} = []\n", chip_name, core_name);
            }
        } else {
            result += &format!("{} = []\n", chip_name);
        }
    }
    result
}

/// Get contents before and after generated contents
///
/// # Panic
/// Panics when a separator cound not be not found
fn split_cargo_toml_contents(contents: &str) -> (&str, &str) {
    let (before, remainder) = contents
        .split_once(SEPARATOR_START)
        .unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_START));
    let (_, after) = remainder
        .split_once(SEPARATOR_END)
        .unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_END));

    (before, after)
}

/// Generates new contents for Cargo.toml
///
/// # Panic
/// Panics when a separator cound not be not found
pub fn generate_cargo_toml_file(previous_text: &str, new_contents: &str) -> String {
    let (before, after) = split_cargo_toml_contents(previous_text);
    before.to_owned() + SEPARATOR_START + HELP + new_contents + SEPARATOR_END + after
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[ignore]
    fn stm32f407vg_yaml_file_exists() {
        assert!(chip_names_and_cores()
            .as_slice()
            .into_iter()
            .any(|(name, _)| { name == "stm32f407vg" }))
    }

    #[test]
    fn keeps_text_around_separators() {
        let initial = "\
before
# BEGIN GENERATED FEATURES
# END GENERATED FEATURES
after
";

        let expected = "\
before
# BEGIN GENERATED FEATURES
# Generated by stm32-gen-features. DO NOT EDIT.
a = [\"b\"]
# END GENERATED FEATURES
after
";

        let new_contents = String::from("a = [\"b\"]\n");
        assert_eq!(generate_cargo_toml_file(initial, &new_contents), expected);
    }

    #[test]
    #[should_panic]
    fn does_not_generate_if_separators_are_missing() {
        let initial = "\
before
# END GENERATED FEATURES
after
";

        let new_contents = String::from("a = [\"b\"]\n");
        generate_cargo_toml_file(initial, &new_contents);
    }
}
