use std::collections::BTreeSet;
use std::ffi::{c_char, CStr};
use std::path::PathBuf;
use std::ptr::NonNull;
use std::sync::OnceLock;

use fontconfig_sys as fc;

pub fn cached_paths() -> &'static Option<BTreeSet<PathBuf>> {
    static DIRS: OnceLock<Option<BTreeSet<PathBuf>>> = OnceLock::new();

    DIRS.get_or_init(dirs)
}

fn dirs() -> Option<BTreeSet<PathBuf>> {
    unsafe {
        if fc::FcInit() != 1 {
            return None;
        }
        let config = NonNull::new(fc::FcConfigGetCurrent())?.as_ptr();

        let mut paths = cache_dirs(config)?;
        paths.append(&mut config_files_and_dirs(config)?);
        paths.append(&mut font_dirs(config)?);

        Some(paths)
    }
}

unsafe fn cache_dirs(config: *mut fc::FcConfig) -> Option<BTreeSet<PathBuf>> {
    str_list_to_set(fc::FcConfigGetCacheDirs(config))
}

unsafe fn config_files_and_dirs(config: *mut fc::FcConfig) -> Option<BTreeSet<PathBuf>> {
    let config_dirs = str_list_to_set(fc::FcConfigGetConfigDirs(config))?;
    let all_config_files = str_list_to_set(fc::FcConfigGetConfigFiles(config))?;

    let mut config_files_without_confd = all_config_files
        .into_iter()
        .filter(|x| {
            x.parent().map_or(false, |p| {
                !config_dirs.contains(p) && !config_dirs.contains(x)
            })
        })
        .collect();

    let mut all_paths = config_dirs;
    all_paths.append(&mut config_files_without_confd);
    Some(all_paths)
}

unsafe fn font_dirs(config: *mut fc::FcConfig) -> Option<BTreeSet<PathBuf>> {
    let font_dirs = str_list_to_set(fc::FcConfigGetFontDirs(config))?;

    Some(
        font_dirs
            .into_iter()
            .filter(|x| !x.starts_with("/usr/"))
            .collect(),
    )
}

unsafe fn str_list_to_set(list: *mut fc::FcStrList) -> Option<BTreeSet<PathBuf>> {
    let list = NonNull::new(list)?.as_ptr();
    let mut vec = BTreeSet::new();
    loop {
        let s = fc::FcStrListNext(list);
        if s.is_null() {
            break;
        } else if let Ok(cs) = CStr::from_ptr(s as *const c_char).to_str() {
            vec.insert(PathBuf::from(cs));
        } else {
            eprintln!("fontconfig: Invalid path");
        }
    }

    Some(vec)
}
