use std::collections::HashSet;

use cfg_expr::{
    Expression,
    Predicate,
    TargetPredicate,
    targets::{Arch, Family, Os, Triple, Vendor},
};
use toml_edit::{DocumentMut, Item};

use super::{Error, Manifest};

/// Filter rules for target-specific dependencies
///
/// Target-specififc dependencies that don't match either at least one of the specified target
/// architectures or any of the specified full target triples are stripped.
///
/// An "empty" filter removes *all* target-specific dependencies.
#[derive(Debug)]
#[non_exhaustive]
pub struct DependencyFilter {
    /// List of known target architectures
    pub target_arch: Vec<Arch>,
    /// List of known full target triples
    pub target_triple: Vec<Triple>,
}

impl DependencyFilter {
    /// Initialize an empty filter
    pub fn empty() -> Self {
        DependencyFilter {
            target_arch: Vec::new(),
            target_triple: Vec::new(),
        }
    }
}

#[derive(Debug, Default)]
pub struct Filtered {
    pub removed_targets: Vec<(String, Item)>,
}

impl Manifest {
    /// Rewrite manifest by applying a filter for dependencies
    pub fn filter_dependencies(&mut self, filter: &DependencyFilter) -> Result<Filtered, Error> {
        // collect names of optional dependencies with and without associated implicit features
        let (_, implicit_optional) = get_optional_features_explicit_implicit(&self.toml)?;

        let Some(target_item) = self.toml.get_mut("target") else {
            // no "target" table: nothing to do
            return Ok(Filtered::default());
        };

        let Some(targets) = target_item.as_table_mut() else {
            return Err(Error::manifest("Value at 'target' is not a table"));
        };

        // remove non-applicable target-specific dependencies
        let removed_targets = filter_targets(targets, filter);

        // collect names of removed dependencies and if they are optional
        let removed_with_optional = parse_removed_targets(&removed_targets)?;

        // collect names of all remaining dependencies and if they are optional
        let remaining_with_optional = get_dependencies(&self.toml)?;

        // collect names of optional dependencies for which all occurrences were dropped
        let dropped_with_optional = get_dropped_dependencies(&removed_with_optional, &remaining_with_optional);

        // remove references to dropped dependencies from feature dependencies
        strip_dropped_features(&mut self.toml, dropped_with_optional, &implicit_optional)?;

        Ok(Filtered { removed_targets })
    }
}

fn filter_targets(targets: &mut toml_edit::Table, filter: &DependencyFilter) -> Vec<(String, toml_edit::Item)> {
    let known_target_triples = get_known_target_triples();

    // remove non-applicable target-specific dependencies
    let mut removed_targets = Vec::new();
    targets.retain(|key, item| {
        // check if key is a valid target triple
        // and check if key is a supported target
        let maybe_triple = Triple::new(key.to_owned());
        if known_target_triples.contains(&maybe_triple) {
            if filter.target_triple.contains(&maybe_triple) {
                return true;
            } else {
                removed_targets.push((key.to_owned(), item.clone()));
                return false;
            }
        }

        // check if key is a valid cfg-expression
        // and check if it is applicable on any supported target
        match Expression::parse(key) {
            Ok(expr) => {
                if cfg_applicable(&expr, filter) {
                    true
                } else {
                    removed_targets.push((key.to_owned(), item.clone()));
                    false
                }
            },
            Err(_) => {
                log::debug!("Unknown target configuration: '{}'", key);
                removed_targets.push((key.to_owned(), item.clone()));
                false
            },
        }
    });

    removed_targets
}

fn parse_removed_targets(removed_targets: &[(String, toml_edit::Item)]) -> Result<Vec<(String, bool)>, Error> {
    let result = removed_targets
        .iter()
        .map(|(target_name, item)| {
            let Some(target) = item.as_table() else {
                return Err(Error::manifest(format!(
                    "Value at 'target.{}' is not a table",
                    target_name
                )));
            };

            let mut ret = Vec::new();

            // check dependencies
            if let Some(deps_item) = target.get("dependencies") {
                let Some(deps) = deps_item.as_table() else {
                    return Err(Error::manifest(format!(
                        "Value at 'target.{}.dependencies' is not a table",
                        target_name
                    )));
                };

                for (dep_name, dep_item) in deps {
                    // plain string
                    if let Some(_version) = dep_item.as_str() {
                        ret.push((String::from(dep_name), false));
                        continue;
                    }

                    // table
                    if let Some(dep) = dep_item.as_table() {
                        if let Some(optional_item) = dep.get("optional") {
                            let Some(optional) = optional_item.as_bool() else {
                                return Err(Error::manifest(format!(
                                    "Value at 'target.{}.dependencies[{}].optional' is not a boolean",
                                    target_name, dep_name,
                                )));
                            };
                            ret.push((String::from(dep_name), optional));
                        } else {
                            ret.push((String::from(dep_name), false));
                        }
                        continue;
                    }

                    // inline table
                    if let Some(dep) = dep_item.as_inline_table() {
                        if let Some(optional_item) = dep.get("optional") {
                            let Some(optional) = optional_item.as_bool() else {
                                return Err(Error::manifest(format!(
                                    "Value at 'target.{}.dependencies[{}].optional' is not a boolean",
                                    target_name, dep_name,
                                )));
                            };
                            ret.push((String::from(dep_name), optional));
                        } else {
                            ret.push((String::from(dep_name), false));
                        }
                        continue;
                    }

                    return Err(Error::manifest(format!(
                        "Value at 'target.{}.dependencies[{}]' is neither a table nor an inline table",
                        target_name, dep_name,
                    )));
                }
            }

            // check build-dependencies
            if let Some(deps_item) = target.get("build-dependencies") {
                let Some(deps) = deps_item.as_table() else {
                    return Err(Error::manifest(format!(
                        "Value at 'target.{}.build-dependencies' is not a table",
                        target_name
                    )));
                };

                for (dep_name, dep_item) in deps {
                    // plain string
                    if let Some(_version) = dep_item.as_str() {
                        ret.push((String::from(dep_name), false));
                        continue;
                    }

                    // table
                    if let Some(dep) = dep_item.as_table() {
                        if let Some(optional_item) = dep.get("optional") {
                            let Some(optional) = optional_item.as_bool() else {
                                return Err(Error::manifest(format!(
                                    "Value at 'target.{}.build-dependencies[{}].optional' is not a boolean",
                                    target_name, dep_name,
                                )));
                            };
                            ret.push((String::from(dep_name), optional));
                        } else {
                            ret.push((String::from(dep_name), false));
                        }
                        continue;
                    }

                    // inline table
                    if let Some(dep) = dep_item.as_inline_table() {
                        if let Some(optional_item) = dep.get("optional") {
                            let Some(optional) = optional_item.as_bool() else {
                                return Err(Error::manifest(format!(
                                    "Value at 'target.{}.build-dependencies[{}].optional' is not a boolean",
                                    target_name, dep_name,
                                )));
                            };
                            ret.push((String::from(dep_name), optional));
                        } else {
                            ret.push((String::from(dep_name), false));
                        }
                        continue;
                    }

                    return Err(Error::manifest(format!(
                        "Value at 'target.{}.build-dependencies[{}]' is neither a table nor an inline table",
                        target_name, dep_name,
                    )));
                }
            }

            // dev-dependencies cannot be optional or referenced by features
            Ok(ret)
        })
        .collect::<Result<Vec<Vec<(String, bool)>>, Error>>()?
        .into_iter()
        .flatten()
        .collect();

    Ok(result)
}

fn get_dependencies(toml: &DocumentMut) -> Result<HashSet<(String, bool)>, Error> {
    let Some(target_item) = toml.get("target") else {
        return Ok(HashSet::new());
    };

    let Some(targets) = target_item.as_table() else {
        return Err(Error::manifest("Value at 'target' is not a table"));
    };

    let mut remaining_with_optional = HashSet::new();

    for (target_name, item) in targets.iter() {
        let Some(target) = item.as_table() else {
            return Err(Error::manifest(format!(
                "Value at 'target.{}' is not a table",
                target_name
            )));
        };

        if let Some(deps_item) = target.get("dependencies") {
            let Some(deps) = deps_item.as_table() else {
                return Err(Error::manifest(format!(
                    "Value at 'target.{}.dependencies' is not a table",
                    target_name
                )));
            };

            for (dep_name, dep_item) in deps {
                // plain string
                if let Some(_version) = dep_item.as_str() {
                    remaining_with_optional.insert((String::from(dep_name), false));
                    continue;
                }

                // table
                if let Some(dep) = dep_item.as_table() {
                    if let Some(optional_item) = dep.get("optional") {
                        let Some(optional) = optional_item.as_bool() else {
                            return Err(Error::manifest(format!(
                                "Value at 'target.{}.dependencies[{}].optional' is not a boolean",
                                target_name, dep_name,
                            )));
                        };
                        remaining_with_optional.insert((String::from(dep_name), optional));
                    } else {
                        remaining_with_optional.insert((String::from(dep_name), false));
                    }
                    continue;
                }

                // inline table
                if let Some(dep) = dep_item.as_inline_table() {
                    if let Some(optional_item) = dep.get("optional") {
                        let Some(optional) = optional_item.as_bool() else {
                            return Err(Error::manifest(format!(
                                "Value at 'target.{}.dependencies[{}].optional' is not a boolean",
                                target_name, dep_name,
                            )));
                        };
                        remaining_with_optional.insert((String::from(dep_name), optional));
                    } else {
                        remaining_with_optional.insert((String::from(dep_name), false));
                    }
                    continue;
                }

                return Err(Error::manifest(format!(
                    "Value at 'target.{}.dependencies[{}]' is neither a table nor an inline table",
                    target_name, dep_name,
                )));
            }
        }

        if let Some(deps_item) = target.get("build-dependencies") {
            let Some(deps) = deps_item.as_table() else {
                return Err(Error::manifest(format!(
                    "Value at 'target.{}.build-dependencies' is not a table",
                    target_name
                )));
            };

            for (dep_name, dep_item) in deps {
                // plain string
                if let Some(_version) = dep_item.as_str() {
                    remaining_with_optional.insert((String::from(dep_name), false));
                    continue;
                }

                // table
                if let Some(dep) = dep_item.as_table() {
                    if let Some(optional_item) = dep.get("optional") {
                        let Some(optional) = optional_item.as_bool() else {
                            return Err(Error::manifest(format!(
                                "Value at 'target.{}.build-dependencies[{}].optional' is not a boolean",
                                target_name, dep_name,
                            )));
                        };
                        remaining_with_optional.insert((String::from(dep_name), optional));
                    } else {
                        remaining_with_optional.insert((String::from(dep_name), false));
                    }
                    continue;
                }

                // inline table
                if let Some(dep) = dep_item.as_inline_table() {
                    if let Some(optional_item) = dep.get("optional") {
                        let Some(optional) = optional_item.as_bool() else {
                            return Err(Error::manifest(format!(
                                "Value at 'target.{}.dependencies[{}].optional' is not a boolean",
                                target_name, dep_name,
                            )));
                        };
                        remaining_with_optional.insert((String::from(dep_name), optional));
                    } else {
                        remaining_with_optional.insert((String::from(dep_name), false));
                    }
                    continue;
                }

                return Err(Error::manifest(format!(
                    "Value at 'target.{}.dependencies[{}]' is neither a table nor an inline table",
                    target_name, dep_name,
                )));
            }
        }
    }

    if let Some(deps_item) = toml.get("dependencies") {
        let Some(deps) = deps_item.as_table() else {
            return Err(Error::manifest("Value at 'dependencies' is not a table"));
        };

        for (dep_name, dep_item) in deps {
            // plain string
            if let Some(_version) = dep_item.as_str() {
                remaining_with_optional.insert((String::from(dep_name), false));
                continue;
            }

            // table
            if let Some(dep) = dep_item.as_table() {
                if let Some(optional_item) = dep.get("optional") {
                    let Some(optional) = optional_item.as_bool() else {
                        return Err(Error::manifest(format!(
                            "Value at 'dependencies[{}].optional' is not a boolean",
                            dep_name,
                        )));
                    };
                    remaining_with_optional.insert((String::from(dep_name), optional));
                } else {
                    remaining_with_optional.insert((String::from(dep_name), false));
                }
                continue;
            }

            // inline table
            if let Some(dep) = dep_item.as_inline_table() {
                if let Some(optional_item) = dep.get("optional") {
                    let Some(optional) = optional_item.as_bool() else {
                        return Err(Error::manifest(format!(
                            "Value at 'dependencies[{}].optional' is not a boolean",
                            dep_name,
                        )));
                    };
                    remaining_with_optional.insert((String::from(dep_name), optional));
                } else {
                    remaining_with_optional.insert((String::from(dep_name), false));
                }
                continue;
            }

            return Err(Error::manifest(format!(
                "Value at 'dependencies[{}]' is neither a table nor an inline table",
                dep_name,
            )));
        }
    }

    if let Some(deps_item) = toml.get("build-dependencies") {
        let Some(deps) = deps_item.as_table() else {
            return Err(Error::manifest("Value at 'build-dependencies' is not a table"));
        };

        for (dep_name, dep_item) in deps {
            // plain string
            if let Some(_version) = dep_item.as_str() {
                remaining_with_optional.insert((String::from(dep_name), false));
                continue;
            }

            // table
            if let Some(dep) = dep_item.as_table() {
                if let Some(optional_item) = dep.get("optional") {
                    let Some(optional) = optional_item.as_bool() else {
                        return Err(Error::manifest(format!(
                            "Value at 'build-dependencies[{}].optional' is not a boolean",
                            dep_name,
                        )));
                    };
                    remaining_with_optional.insert((String::from(dep_name), optional));
                } else {
                    remaining_with_optional.insert((String::from(dep_name), false));
                }
                continue;
            }

            // inline table
            if let Some(dep) = dep_item.as_inline_table() {
                if let Some(optional_item) = dep.get("optional") {
                    let Some(optional) = optional_item.as_bool() else {
                        return Err(Error::manifest(format!(
                            "Value at 'build-dependencies[{}].optional' is not a boolean",
                            dep_name,
                        )));
                    };
                    remaining_with_optional.insert((String::from(dep_name), optional));
                } else {
                    remaining_with_optional.insert((String::from(dep_name), false));
                }
                continue;
            }

            return Err(Error::manifest(format!(
                "Value at 'build-dependencies[{}]' is neither a table nor an inline table",
                dep_name,
            )));
        }
    }

    Ok(remaining_with_optional)
}

fn get_optional_features_explicit_implicit(toml: &DocumentMut) -> Result<(HashSet<String>, HashSet<String>), Error> {
    let dependencies = get_dependencies(toml)?;

    let optional_dependencies: HashSet<String> = dependencies
        .into_iter()
        .filter(|(_, optional)| *optional)
        .map(|(dep_name, _)| dep_name)
        .collect();

    let mut feature_names: HashSet<String> = HashSet::new();
    if let Some(features_item) = toml.get("features") {
        let Some(features) = features_item.as_table() else {
            return Err(Error::manifest("Value at 'features' is not a table"));
        };

        for (feature_name, _) in features.iter() {
            feature_names.insert(feature_name.to_string());
        }
    }

    let mut explicit_optional: HashSet<String> = HashSet::new();
    if let Some(features_item) = toml.get("features") {
        let Some(features) = features_item.as_table() else {
            return Err(Error::manifest("Value at 'features' is not a table"));
        };

        for (feature_name, feature_item) in features.iter() {
            let Some(feature_deps_array) = feature_item.as_array() else {
                return Err(Error::manifest(format!(
                    "Value at 'features[{}]' is not an array",
                    feature_name
                )));
            };

            for (index, feature_dep) in feature_deps_array.iter().enumerate() {
                let Some(feature_dep_name) = feature_dep.as_str() else {
                    return Err(Error::manifest(format!(
                        "Value at 'features[{}][{}]' is not an array",
                        feature_name, index
                    )));
                };

                if let Some(dep) = feature_dep_name.strip_prefix("dep:") {
                    explicit_optional.insert(String::from(dep));
                }
            }
        }
    }

    let implicit_optional: HashSet<String> = optional_dependencies
        .iter()
        .filter(|dep| !explicit_optional.contains(*dep))
        .cloned()
        .collect();

    Ok((explicit_optional, implicit_optional))
}

fn get_dropped_dependencies<'a>(
    removed_with_optional: &'a [(String, bool)],
    remaining_with_optional: &HashSet<(String, bool)>,
) -> Vec<&'a (String, bool)> {
    removed_with_optional
        .iter()
        .filter(|(dep_name, _optional)| {
            !remaining_with_optional.contains(&(dep_name.to_owned(), true))
                && !remaining_with_optional.contains(&(dep_name.to_owned(), false))
        })
        .collect()
}

fn strip_dropped_features(
    toml: &mut DocumentMut,
    dropped_with_optional: Vec<&(String, bool)>,
    implicit_optional: &HashSet<String>,
) -> Result<(), Error> {
    let mut dropped_features: HashSet<String> = HashSet::new();

    if let Some(features_item) = toml.get_mut("features") {
        let Some(features) = features_item.as_table_mut() else {
            return Err(Error::manifest("Value at 'features' is not a table"));
        };

        for (feature_name, feature_item) in features.iter_mut() {
            let Some(feature_deps_array) = feature_item.as_array_mut() else {
                return Err(Error::manifest(format!(
                    "Value at 'features[{}]' is not an array",
                    feature_name
                )));
            };

            let feature_deps_len = feature_deps_array.len();
            let mut removed_feat_dep = HashSet::new();

            for (index, feature_dep) in feature_deps_array.iter().enumerate() {
                let Some(feature_dep_name) = feature_dep.as_str() else {
                    return Err(Error::manifest(format!(
                        "Value at 'features[{}][{}]' is not an array",
                        feature_name, index
                    )));
                };

                if implicit_optional.contains(feature_dep_name)
                    && dropped_with_optional.contains(&&(String::from(feature_dep_name), true))
                {
                    removed_feat_dep.insert(String::from(feature_dep_name));
                }

                if let Some(dep) = feature_dep_name.strip_prefix("dep:") {
                    if dropped_with_optional.contains(&&(String::from(dep), true)) {
                        removed_feat_dep.insert(String::from(feature_dep_name));

                        if feature_name == dep && feature_deps_len == 1 {
                            dropped_features.insert(feature_name.to_string());
                        }
                    }
                    continue;
                }

                if let Some((dep, _feat)) = feature_dep_name.split_once("?/") {
                    if dropped_with_optional.contains(&&(String::from(dep), true)) {
                        removed_feat_dep.insert(String::from(feature_dep_name));
                    }
                    continue;
                }

                if let Some((dep, _feat)) = feature_dep_name.split_once('/') {
                    if dropped_with_optional.contains(&&(String::from(dep), true))
                        || dropped_with_optional.contains(&&(String::from(dep), false))
                    {
                        removed_feat_dep.insert(String::from(feature_dep_name));
                    }
                    continue;
                }
            }

            feature_deps_array.retain(|dep| {
                if let Some(name) = dep.as_str() {
                    !removed_feat_dep.contains(name)
                } else {
                    log::debug!(
                        "Feature '{}' dependency '{}' is not a string and was ignored",
                        feature_name,
                        dep
                    );
                    true
                }
            });
        }

        features.retain(|key, _value| !dropped_features.contains(key));
    }

    Ok(())
}

fn get_known_target_triples() -> HashSet<Triple> {
    cfg_expr::targets::ALL_BUILTINS
        .iter()
        .map(|t| t.triple.clone())
        .collect()
}

fn cfg_applicable(expression: &Expression, filter: &DependencyFilter) -> bool {
    if expression.original() == "any()" {
        // empty "any()" predicates are used as "hacks" to improve cargo
        // dependency resolution and need to be kept
        return true;
    }

    filter
        .target_arch
        .iter()
        .any(|target_arch| expression.eval(|pred| cfg_evaluate(pred, target_arch)))
}

fn cfg_evaluate(predicate: &Predicate, target_arch: &Arch) -> bool {
    use Predicate::*;

    match predicate {
        Target(target) => {
            use TargetPredicate as TP;
            match target {
                TP::Arch(arch) => {
                    let e = arch == target_arch;
                    log::trace!("eval: target_arch = {} (for {}) => {}", arch, target_arch, e);
                    e
                },
                TP::Endian(endian) => {
                    log::trace!("eval: target_endian = {:?} => true", endian);
                    true
                },
                TP::Env(env) => {
                    let e = ["", "gnu"].contains(&env.as_str());
                    log::trace!("eval: target_env = {} => {}", env, e);
                    e
                },
                TP::Family(family) => {
                    let e = family == &Family::unix;
                    log::trace!("eval: target_family = {} => {}", family, e);
                    e
                },
                TP::HasAtomic(atomic) => {
                    log::trace!("eval: target_has_atomic = {} => true", atomic);
                    true
                },
                TP::Os(os) => {
                    let e = os == &Os::linux;
                    log::trace!("eval: target_os = {} => {}", os, e);
                    e
                },
                TP::PointerWidth(pointer_width) => {
                    log::trace!("eval: target_pointer_width = {} => true", pointer_width);
                    true
                },
                TP::Vendor(vendor) => {
                    let e = vendor == &Vendor::unknown;
                    log::trace!("eval: target_vendor = {} => {}", vendor, e);
                    e
                },
                // ignore predicates that are not supported in Cargo.toml
                TP::Panic(panic) => {
                    log::trace!("eval: panic = {} => false", panic);
                    false
                },
                // ignore unstable attributes
                TP::Abi(_) => true,
            }
        },
        TargetFeature(_) => true,
        // ignore unknown bare predicates
        Flag(flag) => {
            log::trace!("eval: {} => false", flag);
            false
        },
        // ignore predicates that are not supported in Cargo.toml
        Test => {
            log::trace!("eval: test => false");
            false
        },
        DebugAssertions => {
            log::trace!("eval: debug_assertions => false");
            false
        },
        ProcMacro => {
            log::trace!("eval: proc_macro => false");
            false
        },
        Feature(feature) => {
            log::trace!("eval: feature = {} => false", feature);
            false
        },
        KeyValue { key, val } => {
            log::trace!("eval: {} = {} => false", key, val);
            false
        },
    }
}

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

    #[test]
    fn test_cfg_applicable() {
        #[rustfmt::skip]
        let test_cases = [
            ("cfg(any())", true),
            ("cfg(target_os = \"macos\")", false),
            ("cfg(any(foo, bar))", false),
            ("cfg(all(unix, target_pointer_width = \"16\"))", true),
            ("cfg(not(foo))", true),
            ("cfg(unix)", true),
            ("cfg(not(unix))", false),
            ("cfg(windows)", false),
            ("cfg(miri)", false),
            ("cfg(linux)", false),
            ("cfg(not(windows))", true),
            ("cfg(any(unix, windows))", true),
            ("cfg(any(windows, unix))", true),
            ("cfg(any(windows, windows, windows))", false),
            ("cfg(target_os = \"linux\")", true),
            ("cfg(any(target_os = \"linux\"))", true),
            ("cfg(all(target_os = \"linux\"))", true),
            ("cfg(any(target_os = \"linux\", target_os = \"macos\"))", true),
            ("cfg(any(target_pointer_width = \"16\", target_pointer_width = \"32\", target_pointer_width = \"64\"))", true),
            ("cfg(any(target_os = \"android\", target_os = \"linux\"))", true),
            // from rustix 0.36.8
            ("cfg(all(criterion, not(any(target_os = \"emscripten\", target_os = \"wasi\"))))", false),
            ("cfg(all(any(target_os = \"android\", target_os = \"linux\"), any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"), all(target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"powerpc64\", target_arch = \"riscv64\", target_arch = \"mips\", target_arch = \"mips64\"))))))))", true),
            ("cfg(all(not(rustix_use_libc), not(miri), target_os = \"linux\", any(target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"), all(target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"powerpc64\", target_arch = \"riscv64\", target_arch = \"mips\", target_arch = \"mips64\")))))", true),
            ("cfg(any(rustix_use_libc, miri, not(all(target_os = \"linux\", any(target_arch = \"x86\", all(target_arch = \"x86_64\", target_pointer_width = \"64\"), all(target_endian = \"little\", any(target_arch = \"arm\", all(target_arch = \"aarch64\", target_pointer_width = \"64\"), target_arch = \"powerpc64\", target_arch = \"riscv64\", target_arch = \"mips\", target_arch = \"mips64\")))))))", true),
            ("cfg(any(target_os = \"android\", target_os = \"linux\"))", true),
            // from tokio 1.21.2
            ("cfg(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), not(target_os = \"wasi\")))", false),
            ("cfg(loom)", false),
            ("cfg(not(all(any(target_arch = \"wasm32\", target_arch = \"wasm64\"), target_os = \"unknown\")))", true),
            ("cfg(not(any(target_arch = \"wasm32\", target_arch = \"wasm64\")))", true),
            ("cfg(target_os = \"freebsd\")", false),
            ("cfg(tokio_unstable)", false),
        ];

        // default configuration for Fedora
        const TARGET_TRIPLES: [Triple; 7] = [
            Triple::new_const("x86_64-unknown-linux-gnu"),
            Triple::new_const("i686-unknown-linux-gnu"),
            Triple::new_const("aarch64-unknown-linux-gnu"),
            Triple::new_const("armv7-unknown-linux-gnueabihf"),
            Triple::new_const("powerpc64le-unknown-linux-gnu"),
            Triple::new_const("s390x-unknown-linux-gnu"),
            Triple::new_const("riscv64gc-unknown-linux-gnu"),
        ];

        let filter = DependencyFilter {
            target_arch: vec![
                Arch::x86_64,
                Arch::x86,
                Arch::aarch64,
                Arch::arm,
                Arch::powerpc64,
                Arch::s390x,
                Arch::riscv64,
            ],
            target_triple: TARGET_TRIPLES.into(),
        };

        for (expr, expected) in test_cases {
            let cfg = Expression::parse(expr).unwrap();
            assert_eq!(cfg_applicable(&cfg, &filter), expected, "{}", expr);
        }
    }
}
