mod cfg_transform;
mod rules;
pub use self::cfg_transform::simplified_expr;
use codegen_cfg::ast::*;
use std::collections::{BTreeSet, HashMap};
use std::fs;
use std::ops::Not;
use anyhow::Result;
use camino::{Utf8Path, Utf8PathBuf};
use log::error;
use regex::Regex;
use rust_utils::{default, map_collect_vec};
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
#[derive(Debug)]
struct SourceFile {
    mod_path: Utf8PathBuf,
    fs_path: Utf8PathBuf,
}
fn load_src_list(libc_repo_path: impl AsRef<Utf8Path>) -> Result<Vec<SourceFile>> {
    let mut ans = Vec::new();
    let src_dir = libc_repo_path.as_ref().join("src");
    let walk = WalkDir::new(&src_dir);
    for entry in walk {
        let entry = entry?;
        let fs_path: Utf8PathBuf = entry.path().to_owned().try_into()?;
        if fs_path.extension() != Some("rs") {
            continue;
        }
        let rel_path = fs_path.strip_prefix(&src_dir)?.to_owned();
        let mod_path = if rel_path.file_name() == Some("mod.rs") {
            rel_path.parent().unwrap().to_owned()
        } else {
            rel_path.with_extension("")
        };
        let ignored_tail = [
            "fixed_width_ints", "no_align",         "errno",            ];
        if ignored_tail.iter().copied().any(|s| mod_path.ends_with(s)) {
            continue;
        }
        let mod_path = {
            let promote = rules::PROMOTE_MODS;
            let mut components = mod_path.components().collect::<Vec<_>>();
            components.retain(|c| promote.contains(&c.as_str()).not());
            components.into_iter().collect::<Utf8PathBuf>()
        };
        ans.push(SourceFile { fs_path, mod_path });
    }
    ans.sort_by(|lhs, rhs| lhs.mod_path.cmp(&rhs.mod_path));
    Ok(ans)
}
fn load_item_names(src: &SourceFile) -> Result<Vec<String>> {
    let mut ans = Vec::new();
    let content = fs::read_to_string(&src.fs_path)?;
    let re = Regex::new(r"^\s*pub (type|const|struct|union|fn) ([A-Za-z0-9_]+)").unwrap();
    for line in content.lines() {
        if let Some(caps) = re.captures(line) {
            let item = caps.get(2).unwrap().as_str();
            ans.push(item.to_owned());
        }
    }
    ans.sort_unstable();
    Ok(ans)
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Item {
    pub name: String,
    pub mod_paths: BTreeSet<Utf8PathBuf>,
}
pub fn generate_item_list(libc_repo_path: impl AsRef<Utf8Path>) -> Result<Vec<Item>> {
    let mut map: HashMap<String, Item> = default();
    let src_list = load_src_list(libc_repo_path)?;
    for src in src_list {
        let item_list = load_item_names(&src)?;
        for name in item_list {
            let item = map.entry(name.clone()).or_insert_with(|| Item {
                name,
                mod_paths: default(),
            });
            item.mod_paths.insert(src.mod_path.clone());
        }
    }
    let mut list: Vec<_> = map.into_values().collect();
    list.sort_unstable_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
    Ok(list)
}
fn generate_cfg_expr(mod_path: &Utf8Path, item_name: &str) -> Expr {
    let match_all_rules = &*rules::MATCH_ALL;
    if let Some(expr) = match_all_rules.get(mod_path.as_str()) {
        return expr.clone();
    }
    let components: Vec<_> = mod_path.components().collect();
    let mut components: Vec<&str> = components.iter().map(|c| c.as_str()).collect();
    assert!(components.is_empty().not());
    if components.len() > 1 && components.first().copied() == Some("unix") {
        components.remove(0);
    }
    let match_component_rules = &*rules::MATCH_COMPONENT;
    let mut conds: Vec<Expr> = Vec::new();
    for &s in &components {
        if let Some(expr) = match_component_rules.get(s) {
            conds.push(expr.clone());
            continue;
        }
        error!("item_name: {item_name}");
        error!("component: {s}");
        error!("mod_path:  {mod_path}");
        unimplemented!("unknown component")
    }
    if conds.is_empty() {
        error!("item_name: {item_name}");
        error!("mod_path:  {mod_path}");
        unimplemented!("empty conditions")
    }
    expr(all(conds))
}
pub fn generate_item_cfg(item: &Item) -> Expr {
    let conds = map_collect_vec(&item.mod_paths, |mod_path| generate_cfg_expr(mod_path, &item.name));
    simplified_expr(any(conds))
}