use proc_macro2;
use syn;
use syn::spanned::Spanned;

/// Represent the `derivative` attributes on the input type (`struct`/`enum`).
#[derive(Debug, Default)]
pub struct Input {
    /// Whether `Clone` is present and its specific attributes.
    pub clone: Option<InputClone>,
    /// Whether `Copy` is present and its specific attributes.
    pub copy: Option<InputCopy>,
    /// Whether `Debug` is present and its specific attributes.
    pub debug: Option<InputDebug>,
    /// Whether `Default` is present and its specific attributes.
    pub default: Option<InputDefault>,
    /// Whether `Eq` is present and its specific attributes.
    pub eq: Option<InputEq>,
    /// Whether `Hash` is present and its specific attributes.
    pub hash: Option<InputHash>,
    /// Whether `PartialEq` is present and its specific attributes.
    pub partial_eq: Option<InputPartialEq>,
    /// Whether `PartialOrd` is present and its specific attributes.
    pub partial_ord: Option<InputPartialOrd>,
    /// Whether `Ord` is present and its specific attributes.
    pub ord: Option<InputOrd>,
}

#[derive(Debug, Default)]
/// Represent the `derivative` attributes on a field.
pub struct Field {
    /// The parameters for `Clone`.
    clone: FieldClone,
    /// The parameters for `Copy`.
    copy_bound: Option<Vec<syn::WherePredicate>>,
    /// The parameters for `Debug`.
    debug: FieldDebug,
    /// The parameters for `Default`.
    default: FieldDefault,
    /// The parameters for `Eq`.
    eq_bound: Option<Vec<syn::WherePredicate>>,
    /// The parameters for `Hash`.
    hash: FieldHash,
    /// The parameters for `PartialEq`.
    partial_eq: FieldPartialEq,
    /// The parameters for `PartialOrd`.
    partial_ord: FieldPartialOrd,
    /// The parameters for `Ord`.
    ord: FieldOrd,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Clone(…))` attributes on an input.
pub struct InputClone {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// Whether the implementation should have an explicit `clone_from`.
    pub clone_from: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Clone(…))` attributes on an input.
pub struct InputCopy {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Debug(…))` attributes on an input.
pub struct InputDebug {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// Whether the type is marked `transparent`.
    pub transparent: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Default(…))` attributes on an input.
pub struct InputDefault {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// Whether the type is marked with `new`.
    pub new: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Eq(…))` attributes on an input.
pub struct InputEq {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Hash(…))` attributes on an input.
pub struct InputHash {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
}

#[derive(Debug, Default)]
/// Represent the `derivative(PartialEq(…))` attributes on an input.
pub struct InputPartialEq {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
}

#[derive(Debug, Default)]
/// Represent the `derivative(PartialOrd(…))` attributes on an input.
pub struct InputPartialOrd {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// Allow `derivative(PartialOrd)` on enums:
    on_enum: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Ord(…))` attributes on an input.
pub struct InputOrd {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// Allow `derivative(Ord)` on enums:
    on_enum: bool,
}

#[derive(Debug, Default)]
/// Represents the `derivative(Clone(…))` attributes on a field.
pub struct FieldClone {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The `clone_with` attribute if present and the path to the cloning function.
    clone_with: Option<syn::Path>,
}

#[derive(Debug, Default)]
/// Represents the `derivative(Debug(…))` attributes on a field.
pub struct FieldDebug {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The `format_with` attribute if present and the path to the formatting function.
    format_with: Option<syn::Path>,
    /// Whether the field is to be ignored from output.
    ignore: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Default(…))` attributes on a field.
pub struct FieldDefault {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The default value for the field if present.
    pub value: Option<proc_macro2::TokenStream>,
}

#[derive(Debug, Default)]
/// Represents the `derivative(Hash(…))` attributes on a field.
pub struct FieldHash {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The `hash_with` attribute if present and the path to the hashing function.
    hash_with: Option<syn::Path>,
    /// Whether the field is to be ignored when hashing.
    ignore: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(PartialEq(…))` attributes on a field.
pub struct FieldPartialEq {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The `compare_with` attribute if present and the path to the comparison function.
    compare_with: Option<syn::Path>,
    /// Whether the field is to be ignored when comparing.
    ignore: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(PartialOrd(…))` attributes on a field.
pub struct FieldPartialOrd {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The `compare_with` attribute if present and the path to the comparison function.
    compare_with: Option<syn::Path>,
    /// Whether the field is to be ignored when comparing.
    ignore: bool,
}

#[derive(Debug, Default)]
/// Represent the `derivative(Ord(…))` attributes on a field.
pub struct FieldOrd {
    /// The `bound` attribute if present and the corresponding bounds.
    bounds: Option<Vec<syn::WherePredicate>>,
    /// The `compare_with` attribute if present and the path to the comparison function.
    compare_with: Option<syn::Path>,
    /// Whether the field is to be ignored when comparing.
    ignore: bool,
}

macro_rules! for_all_attr {
    ($errors:ident; for ($name:ident, $value:ident) in $attrs:expr; $($body:tt)*) => {
        for meta_items in $attrs.iter() {
            let meta_items = derivative_attribute(meta_items.parse_meta(), $errors);
            if let Some(meta_items) = meta_items {
                for meta_item in meta_items.iter() {
                    let meta_item = read_items(meta_item, $errors);
                    let MetaItem($name, $value) = try!(meta_item);
                    match $name.to_string().as_ref() {
                        $($body)*
                    }
                }
            }
        }
    };
}

macro_rules! match_attributes {
    ($errors:ident for $trait:expr; let Some($name:ident) = $unwrapped:expr; for $value:ident in $values:expr; $($body:tt)* ) => {
        let mut $name = $unwrapped.take().unwrap_or_default();

        match_attributes! {
            $errors for $trait;
            for $value in $values;
            $($body)*
        }

        $unwrapped = Some($name);
    };

    ($errors:ident for $trait:expr; for $value:ident in $values:expr; $($body:tt)* ) => {
        for (name, $value) in $values {
            match name {
                Some(ident) => {
                    match ident.to_string().as_ref() {
                        $($body)*
                        unknown => {
                            let message = format!("Unknown attribute `{}` for trait `{}`", unknown, $trait);
                            $errors.extend(quote_spanned! {ident.span()=>
                                compile_error!(#message);
                            });
                        }
                    }
                }
                None => {
                    let value = $value.expect("Expected value to be passed");
                    match value.value().as_ref() {
                        $($body)*
                        unknown => {
                            let message = format!("Unknown attribute `{}` for trait `{}`", unknown, $trait);
                            let span = value.span();
                            $errors.extend(quote_spanned! {span=>
                                compile_error!(#message);
                            });
                        }
                    }
                }
            }
        }
    };
}

impl Input {
    /// Parse the `derivative` attributes on a type.
    #[allow(clippy::cognitive_complexity)] // mostly macros
    pub fn from_ast(
        attrs: &[syn::Attribute],
        errors: &mut proc_macro2::TokenStream,
    ) -> Result<Input, ()> {
        let mut input = Input::default();

        for_all_attr! {
            errors;
            for (name, values) in attrs;
            "Clone" => {
                match_attributes! {
                    errors for "Clone";
                    let Some(clone) = input.clone;
                    for value in values;
                    "bound" => parse_bound(&mut clone.bounds, value, errors),
                    "clone_from" => {
                        clone.clone_from = parse_boolean_meta_item(value, true, "clone_from", errors);
                    }
                }
            }
            "Copy" => {
                match_attributes! {
                    errors for "Copy";
                    let Some(copy) = input.copy;
                    for value in values;
                    "bound" => parse_bound(&mut copy.bounds, value, errors),
                }
            }
            "Debug" => {
                match_attributes! {
                    errors for "Debug";
                    let Some(debug) = input.debug;
                    for value in values;
                    "bound" => parse_bound(&mut debug.bounds, value, errors),
                    "transparent" => {
                        debug.transparent = parse_boolean_meta_item(value, true, "transparent", errors);
                    }
                }
            }
            "Default" => {
                match_attributes! {
                    errors for "Default";
                    let Some(default) = input.default;
                    for value in values;
                    "bound" => parse_bound(&mut default.bounds, value, errors),
                    "new" => {
                        default.new = parse_boolean_meta_item(value, true, "new", errors);
                    }
                }
            }
            "Eq" => {
                match_attributes! {
                    errors for "Eq";
                    let Some(eq) = input.eq;
                    for value in values;
                    "bound" => parse_bound(&mut eq.bounds, value, errors),
                }
            }
            "Hash" => {
                match_attributes! {
                    errors for "Hash";
                    let Some(hash) = input.hash;
                    for value in values;
                    "bound" => parse_bound(&mut hash.bounds, value, errors),
                }
            }
            "PartialEq" => {
                match_attributes! {
                    errors for "PartialEq";
                    let Some(partial_eq) = input.partial_eq;
                    for value in values;
                    "bound" => parse_bound(&mut partial_eq.bounds, value, errors),
                    "feature_allow_slow_enum" => (), // backward compatibility, now unnecessary
                }
            }
            "PartialOrd" => {
                match_attributes! {
                    errors for "PartialOrd";
                    let Some(partial_ord) = input.partial_ord;
                    for value in values;
                    "bound" => parse_bound(&mut partial_ord.bounds, value, errors),
                    "feature_allow_slow_enum" => {
                        partial_ord.on_enum = parse_boolean_meta_item(value, true, "feature_allow_slow_enum", errors);
                    }
                }
            }
            "Ord" => {
                match_attributes! {
                    errors for "Ord";
                    let Some(ord) = input.ord;
                    for value in values;
                    "bound" => parse_bound(&mut ord.bounds, value, errors),
                    "feature_allow_slow_enum" => {
                        ord.on_enum = parse_boolean_meta_item(value, true, "feature_allow_slow_enum", errors);
                    }
                }
            }
            unknown => {
                let message = format!("deriving `{}` is not supported by derivative", unknown);
                errors.extend(quote_spanned! {name.span()=>
                    compile_error!(#message);
                });
            }
        }

        Ok(input)
    }

    pub fn clone_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.clone
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn clone_from(&self) -> bool {
        self.clone.as_ref().map_or(false, |d| d.clone_from)
    }

    pub fn copy_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.copy
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn debug_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.debug
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn debug_transparent(&self) -> bool {
        self.debug.as_ref().map_or(false, |d| d.transparent)
    }

    pub fn default_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.default
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn eq_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.eq
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn hash_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.hash
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn partial_eq_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.partial_eq
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn partial_ord_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.partial_ord
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn ord_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.ord
            .as_ref()
            .and_then(|d| d.bounds.as_ref().map(Vec::as_slice))
    }

    pub fn partial_ord_on_enum(&self) -> bool {
        self.partial_ord.as_ref().map_or(false, |d| d.on_enum)
    }

    pub fn ord_on_enum(&self) -> bool {
        self.ord.as_ref().map_or(false, |d| d.on_enum)
    }
}

impl Field {
    /// Parse the `derivative` attributes on a type.
    #[allow(clippy::cognitive_complexity)] // mostly macros
    pub fn from_ast(
        field: &syn::Field,
        errors: &mut proc_macro2::TokenStream,
    ) -> Result<Field, ()> {
        let mut out = Field::default();

        for_all_attr! {
            errors;
            for (name, values) in field.attrs;
            "Clone" => {
                match_attributes! {
                    errors for "Clone";
                    for value in values;
                    "bound" => parse_bound(&mut out.clone.bounds, value, errors),
                    "clone_with" => {
                        let path = value.expect("`clone_with` needs a value");
                        out.clone.clone_with = parse_str_lit(&path, errors).ok();
                    }
                }
            }
            "Debug" => {
                match_attributes! {
                    errors for "Debug";
                    for value in values;
                    "bound" => parse_bound(&mut out.debug.bounds, value, errors),
                    "format_with" => {
                        let path = value.expect("`format_with` needs a value");
                        out.debug.format_with = parse_str_lit(&path, errors).ok();
                    }
                    "ignore" => {
                        out.debug.ignore = parse_boolean_meta_item(value, true, "ignore", errors);
                    }
                }
            }
            "Default" => {
                match_attributes! {
                    errors for "Default";
                    for value in values;
                    "bound" => parse_bound(&mut out.default.bounds, value, errors),
                    "value" => {
                        let value = value.expect("`value` needs a value");
                        out.default.value = parse_str_lit(&value, errors).ok();
                    }
                }
            }
            "Eq" => {
                match_attributes! {
                    errors for "Eq";
                    for value in values;
                    "bound" => parse_bound(&mut out.eq_bound, value, errors),
                }
            }
            "Hash" => {
                match_attributes! {
                    errors for "Hash";
                    for value in values;
                    "bound" => parse_bound(&mut out.hash.bounds, value, errors),
                    "hash_with" => {
                        let path = value.expect("`hash_with` needs a value");
                        out.hash.hash_with = parse_str_lit(&path, errors).ok();
                    }
                    "ignore" => {
                        out.hash.ignore = parse_boolean_meta_item(value, true, "ignore", errors);
                    }
                }
            }
            "PartialEq" => {
                match_attributes! {
                    errors for "PartialEq";
                    for value in values;
                    "bound" => parse_bound(&mut out.partial_eq.bounds, value, errors),
                    "compare_with" => {
                        let path = value.expect("`compare_with` needs a value");
                        out.partial_eq.compare_with = parse_str_lit(&path, errors).ok();
                    }
                    "ignore" => {
                        out.partial_eq.ignore = parse_boolean_meta_item(value, true, "ignore", errors);
                    }
                }
            }
            "PartialOrd" => {
                match_attributes! {
                    errors for "PartialOrd";
                    for value in values;
                    "bound" => parse_bound(&mut out.partial_ord.bounds, value, errors),
                    "compare_with" => {
                        let path = value.expect("`compare_with` needs a value");
                        out.partial_ord.compare_with = parse_str_lit(&path, errors).ok();
                    }
                    "ignore" => {
                        out.partial_ord.ignore = parse_boolean_meta_item(value, true, "ignore", errors);
                    }
                }
            }
            "Ord" => {
                match_attributes! {
                    errors for "Ord";
                    for value in values;
                    "bound" => parse_bound(&mut out.ord.bounds, value, errors),
                    "compare_with" => {
                        let path = value.expect("`compare_with` needs a value");
                        out.ord.compare_with = parse_str_lit(&path, errors).ok();
                    }
                    "ignore" => {
                        out.ord.ignore = parse_boolean_meta_item(value, true, "ignore", errors);
                    }
                }
            }
            unknown => {
                let message = format!("deriving `{}` is not supported by derivative", unknown);
                errors.extend(quote_spanned! {name.span()=>
                    compile_error!(#message);
                });
            }
        }

        Ok(out)
    }

    pub fn clone_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.clone.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn clone_with(&self) -> Option<&syn::Path> {
        self.clone.clone_with.as_ref()
    }

    pub fn copy_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.copy_bound.as_ref().map(Vec::as_slice)
    }

    pub fn debug_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.debug.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn debug_format_with(&self) -> Option<&syn::Path> {
        self.debug.format_with.as_ref()
    }

    pub fn ignore_debug(&self) -> bool {
        self.debug.ignore
    }

    pub fn ignore_hash(&self) -> bool {
        self.hash.ignore
    }

    pub fn default_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.default.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn default_value(&self) -> Option<&proc_macro2::TokenStream> {
        self.default.value.as_ref()
    }

    pub fn eq_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.eq_bound.as_ref().map(Vec::as_slice)
    }

    pub fn hash_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.hash.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn hash_with(&self) -> Option<&syn::Path> {
        self.hash.hash_with.as_ref()
    }

    pub fn partial_eq_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.partial_eq.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn partial_ord_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.partial_ord.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn ord_bound(&self) -> Option<&[syn::WherePredicate]> {
        self.ord.bounds.as_ref().map(Vec::as_slice)
    }

    pub fn partial_eq_compare_with(&self) -> Option<&syn::Path> {
        self.partial_eq.compare_with.as_ref()
    }

    pub fn partial_ord_compare_with(&self) -> Option<&syn::Path> {
        self.partial_ord.compare_with.as_ref()
    }

    pub fn ord_compare_with(&self) -> Option<&syn::Path> {
        self.ord.compare_with.as_ref()
    }

    pub fn ignore_partial_eq(&self) -> bool {
        self.partial_eq.ignore
    }

    pub fn ignore_partial_ord(&self) -> bool {
        self.partial_ord.ignore
    }

    pub fn ignore_ord(&self) -> bool {
        self.ord.ignore
    }
}

/// Represent an attribute.
///
/// We only have a limited set of possible attributes:
///
/// * `#[derivative(Debug)]` is represented as `(Debug, [])`;
/// * `#[derivative(Debug="foo")]` is represented as `(Debug, [(None, Some("foo"))])`;
/// * `#[derivative(Debug(foo="bar")]` is represented as `(Debug, [(Some(foo), Some("bar"))])`.
struct MetaItem<'a>(
    &'a syn::Ident,
    Vec<(Option<&'a syn::Ident>, Option<&'a syn::LitStr>)>,
);

/// Parse an arbitrary item for our limited `MetaItem` subset.
fn read_items<'a>(item: &'a syn::NestedMeta, errors: &mut proc_macro2::TokenStream) -> Result<MetaItem<'a>, ()> {
    let item = match *item {
        syn::NestedMeta::Meta(ref item) => item,
        syn::NestedMeta::Lit(ref lit) => {
            errors.extend(quote_spanned! {lit.span()=>
                compile_error!("expected meta-item but found literal");
            });

            return Err(());
        }
    };
    match *item {
        syn::Meta::Path(ref path) => match path.get_ident() {
            Some(name) => Ok(MetaItem(name, Vec::new())),
            None => {
                errors.extend(quote_spanned! {path.span()=>
                    compile_error!("expected derivative attribute to be a string, but found a path");
                });

                Err(())
            }
        },
        syn::Meta::List(syn::MetaList {
            ref path,
            nested: ref values,
            ..
        }) => {
            let values = values
                .iter()
                .map(|value| {
                    if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
                        ref path,
                        lit: ref value,
                        ..
                    })) = *value
                    {
                        let (name, value) = ensure_str_lit(&path, &value, errors)?;

                        Ok((Some(name), Some(value)))
                    } else {
                        errors.extend(quote_spanned! {value.span()=>
                            compile_error!("expected named value");
                        });

                        Err(())
                    }
                })
                .collect::<Result<_, _>>()?;

            let name = match path.get_ident() {
                Some(name) => name,
                None => {
                    errors.extend(quote_spanned! {path.span()=>
                        compile_error!("expected derivative attribute to be a string, but found a path");
                    });

                    return Err(());
                }
            };

            Ok(MetaItem(name, values))
        }
        syn::Meta::NameValue(syn::MetaNameValue {
            ref path,
            lit: ref value,
            ..
        }) => {
            let (name, value) = ensure_str_lit(&path, &value, errors)?;

            Ok(MetaItem(name, vec![(None, Some(value))]))
        }
    }
}

/// Filter the `derivative` items from an attribute.
fn derivative_attribute(
    meta: syn::parse::Result<syn::Meta>,
    errors: &mut proc_macro2::TokenStream,
) -> Option<syn::punctuated::Punctuated<syn::NestedMeta, syn::token::Comma>> {
    match meta {
        Ok(syn::Meta::List(syn::MetaList {
            path, nested: mis, ..
        })) => {
            if path
                .get_ident()
                .map_or(false, |ident| ident == "derivative")
            {
                Some(mis)
            } else {
                None
            }
        }
        Ok(_) => None,
        Err(e) => {
            let message = format!("invalid attribute: {}", e);
            errors.extend(quote_spanned! {e.span()=>
                compile_error!(#message);
            });

            None
        }
    }
}

/// Parse an item value as a boolean. Accepted values are the string literal `"true"` and
/// `"false"`. The `default` parameter specifies what the value of the boolean is when only its
/// name is specified (eg. `Debug="ignore"` is equivalent to `Debug(ignore="true")`). The `name`
/// parameter is used for error reporting.
fn parse_boolean_meta_item(
    item: Option<&syn::LitStr>,
    default: bool,
    name: &str,
    errors: &mut proc_macro2::TokenStream,
) -> bool {
    if let Some(item) = item.as_ref() {
        match item.value().as_ref() {
            "true" => true,
            "false" => false,
            val => {
                if val == name {
                    true
                } else {
                    let message = format!(
                        r#"expected `"true"` or `"false"` for `{}`, got `{}`"#,
                        name, val
                    );
                    errors.extend(quote_spanned! {item.span()=>
                        compile_error!(#message);
                    });

                    default
                }
            }
        }
    } else {
        default
    }
}

/// Parse a `bound` item.
fn parse_bound(
    opt_bounds: &mut Option<Vec<syn::WherePredicate>>,
    value: Option<&syn::LitStr>,
    errors: &mut proc_macro2::TokenStream,
) {
    let bound = value.expect("`bound` needs a value");
    let bound_value = bound.value();

    *opt_bounds = if !bound_value.is_empty() {
        let where_string = syn::LitStr::new(&format!("where {}", bound_value), bound.span());

        let bounds = parse_str_lit::<syn::WhereClause>(&where_string, errors)
            .map(|wh| wh.predicates.into_iter().collect());

        match bounds {
            Ok(bounds) => Some(bounds),
            Err(_) => {
                errors.extend(quote_spanned! {where_string.span()=>
                    compile_error!("could not parse bound");
                });

                None
            }
        }
    } else {
        Some(vec![])
    };
}

fn parse_str_lit<T>(value: &syn::LitStr, errors: &mut proc_macro2::TokenStream) -> Result<T, ()>
where
    T: syn::parse::Parse,
{
    match value.parse() {
        Ok(value) => Ok(value),
        Err(e) => {
            let message = format!("could not parse string literal: {}", e);
            errors.extend(quote_spanned! {value.span()=>
                compile_error!(#message);
            });
            Err(())
        }
    }
}

fn ensure_str_lit<'a>(
    attr_path: &'a syn::Path,
    lit: &'a syn::Lit,
    errors: &mut proc_macro2::TokenStream,
) -> Result<(&'a syn::Ident, &'a syn::LitStr), ()> {
    let attr_name = match attr_path.get_ident() {
        Some(attr_name) => attr_name,
        None => {
            errors.extend(quote_spanned! {attr_path.span()=>
                compile_error!("expected derivative attribute to be a string, but found a path");
            });
            return Err(());
        }
    };

    if let syn::Lit::Str(ref lit) = *lit {
        Ok((attr_name, lit))
    } else {
        let message = format!(
            "expected derivative {} attribute to be a string: `{} = \"...\"`",
            attr_name, attr_name
        );
        errors.extend(quote_spanned! {lit.span()=>
            compile_error!(#message);
        });
        Err(())
    }
}
