use std::{slice, vec};

use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::spanned::Spanned;

use usage::{
    self, IdentRefSet, IdentSet, LifetimeRefSet, LifetimeSet, UsesLifetimes, UsesTypeParams,
};
use {Error, FromField, FromVariant, Result};

/// A struct or enum body.
///
/// `V` is the type which receives any encountered variants, and `F` receives struct fields.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Data<V, F> {
    Enum(Vec<V>),
    Struct(Fields<F>),
}

impl<V, F> Data<V, F> {
    /// Creates an empty body of the same shape as the passed-in body.
    ///
    /// # Panics
    /// This function will panic if passed `syn::Data::Union`.
    pub fn empty_from(src: &syn::Data) -> Self {
        match *src {
            syn::Data::Enum(_) => Data::Enum(vec![]),
            syn::Data::Struct(ref vd) => Data::Struct(Fields::empty_from(&vd.fields)),
            syn::Data::Union(_) => panic!("Unions are not supported"),
        }
    }

    /// Creates an empty body of the same shape as the passed-in body.
    ///
    /// `darling` does not support unions; calling this function with a union body will return an error.
    pub fn try_empty_from(src: &syn::Data) -> Result<Self> {
        match *src {
            syn::Data::Enum(_) => Ok(Data::Enum(vec![])),
            syn::Data::Struct(ref vd) => Ok(Data::Struct(Fields::empty_from(&vd.fields))),
            // This deliberately doesn't set a span on the error message, as the error is most useful if
            // applied to the call site of the offending macro. Given that the message is very generic,
            // putting it on the union keyword ends up being confusing.
            syn::Data::Union(_) => Err(Error::custom("Unions are not supported")),
        }
    }

    /// Creates a new `Data<&'a V, &'a F>` instance from `Data<V, F>`.
    pub fn as_ref<'a>(&'a self) -> Data<&'a V, &'a F> {
        match *self {
            Data::Enum(ref variants) => Data::Enum(variants.iter().collect()),
            Data::Struct(ref data) => Data::Struct(data.as_ref()),
        }
    }

    /// Applies a function `V -> U` on enum variants, if this is an enum.
    pub fn map_enum_variants<T, U>(self, map: T) -> Data<U, F>
    where
        T: FnMut(V) -> U,
    {
        match self {
            Data::Enum(v) => Data::Enum(v.into_iter().map(map).collect()),
            Data::Struct(f) => Data::Struct(f),
        }
    }

    /// Applies a function `F -> U` on struct fields, if this is a struct.
    pub fn map_struct_fields<T, U>(self, map: T) -> Data<V, U>
    where
        T: FnMut(F) -> U,
    {
        match self {
            Data::Enum(v) => Data::Enum(v),
            Data::Struct(f) => Data::Struct(f.map(map)),
        }
    }

    /// Applies a function to the `Fields` if this is a struct.
    pub fn map_struct<T, U>(self, mut map: T) -> Data<V, U>
    where
        T: FnMut(Fields<F>) -> Fields<U>,
    {
        match self {
            Data::Enum(v) => Data::Enum(v),
            Data::Struct(f) => Data::Struct(map(f)),
        }
    }

    /// Consumes the `Data`, returning `Fields<F>` if it was a struct.
    pub fn take_struct(self) -> Option<Fields<F>> {
        match self {
            Data::Enum(_) => None,
            Data::Struct(f) => Some(f),
        }
    }

    /// Consumes the `Data`, returning `Vec<V>` if it was an enum.
    pub fn take_enum(self) -> Option<Vec<V>> {
        match self {
            Data::Enum(v) => Some(v),
            Data::Struct(_) => None,
        }
    }

    /// Returns `true` if this instance is `Data::Enum`.
    pub fn is_enum(&self) -> bool {
        match *self {
            Data::Enum(_) => true,
            Data::Struct(_) => false,
        }
    }

    /// Returns `true` if this instance is `Data::Struct`.
    pub fn is_struct(&self) -> bool {
        !self.is_enum()
    }
}

impl<V: FromVariant, F: FromField> Data<V, F> {
    /// Attempt to convert from a `syn::Data` instance.
    pub fn try_from(body: &syn::Data) -> Result<Self> {
        match *body {
            syn::Data::Enum(ref data) => {
                let mut items = Vec::with_capacity(data.variants.len());
                let mut errors = Vec::new();
                for v_result in data.variants.iter().map(FromVariant::from_variant) {
                    match v_result {
                        Ok(val) => items.push(val),
                        Err(err) => errors.push(err),
                    }
                }

                if !errors.is_empty() {
                    Err(Error::multiple(errors))
                } else {
                    Ok(Data::Enum(items))
                }
            }
            syn::Data::Struct(ref data) => Ok(Data::Struct(Fields::try_from(&data.fields)?)),
            // This deliberately doesn't set a span on the error message, as the error is most useful if
            // applied to the call site of the offending macro. Given that the message is very generic,
            // putting it on the union keyword ends up being confusing.
            syn::Data::Union(_) => Err(Error::custom("Unions are not supported")),
        }
    }
}

impl<V: UsesTypeParams, F: UsesTypeParams> UsesTypeParams for Data<V, F> {
    fn uses_type_params<'a>(
        &self,
        options: &usage::Options,
        type_set: &'a IdentSet,
    ) -> IdentRefSet<'a> {
        match *self {
            Data::Struct(ref v) => v.uses_type_params(options, type_set),
            Data::Enum(ref v) => v.uses_type_params(options, type_set),
        }
    }
}

impl<V: UsesLifetimes, F: UsesLifetimes> UsesLifetimes for Data<V, F> {
    fn uses_lifetimes<'a>(
        &self,
        options: &usage::Options,
        lifetimes: &'a LifetimeSet,
    ) -> LifetimeRefSet<'a> {
        match *self {
            Data::Struct(ref v) => v.uses_lifetimes(options, lifetimes),
            Data::Enum(ref v) => v.uses_lifetimes(options, lifetimes),
        }
    }
}

/// Equivalent to `syn::Fields`, but replaces the AST element with a generic.
#[derive(Debug, Clone)]
pub struct Fields<T> {
    pub style: Style,
    pub fields: Vec<T>,
    span: Option<Span>,
    __nonexhaustive: (),
}

impl<T> Fields<T> {
    /// Creates a new [`Fields`] struct.
    pub fn new(style: Style, fields: Vec<T>) -> Self {
        Self {
            style,
            fields,
            span: None,
            __nonexhaustive: (),
        }
    }

    /// Adds a [`Span`] to [`Fields`].
    pub fn with_span(mut self, span: Span) -> Self {
        if self.span.is_none() {
            self.span = Some(span);
        }
        self
    }

    pub fn empty_from(vd: &syn::Fields) -> Self {
        Self::new(vd.into(), Vec::new())
    }

    /// Splits the `Fields` into its style and fields for further processing.
    /// Returns an empty `Vec` for `Unit` data.
    pub fn split(self) -> (Style, Vec<T>) {
        (self.style, self.fields)
    }

    /// Returns true if this variant's data makes it a newtype.
    pub fn is_newtype(&self) -> bool {
        self.style == Style::Tuple && self.len() == 1
    }

    pub fn is_unit(&self) -> bool {
        self.style.is_unit()
    }

    pub fn is_tuple(&self) -> bool {
        self.style.is_tuple()
    }

    pub fn is_struct(&self) -> bool {
        self.style.is_struct()
    }

    pub fn as_ref<'a>(&'a self) -> Fields<&'a T> {
        Fields {
            style: self.style,
            fields: self.fields.iter().collect(),
            span: self.span,
            __nonexhaustive: (),
        }
    }

    pub fn map<F, U>(self, map: F) -> Fields<U>
    where
        F: FnMut(T) -> U,
    {
        Fields {
            style: self.style,
            fields: self.fields.into_iter().map(map).collect(),
            span: self.span,
            __nonexhaustive: (),
        }
    }

    pub fn iter(&self) -> slice::Iter<T> {
        self.fields.iter()
    }

    /// Returns the number of fields in the structure.
    pub fn len(&self) -> usize {
        self.fields.len()
    }

    /// Returns `true` if the `Fields` contains no fields.
    pub fn is_empty(&self) -> bool {
        self.fields.is_empty()
    }
}

impl<F: FromField> Fields<F> {
    pub fn try_from(fields: &syn::Fields) -> Result<Self> {
        let (items, errors) = {
            match &fields {
                syn::Fields::Named(fields) => {
                    let mut items = Vec::with_capacity(fields.named.len());
                    let mut errors = Vec::new();

                    for field in &fields.named {
                        match FromField::from_field(field) {
                            Ok(val) => items.push(val),
                            Err(err) => errors.push({
                                if let Some(ident) = &field.ident {
                                    err.at(ident)
                                } else {
                                    err
                                }
                            }),
                        }
                    }

                    (items, errors)
                }
                syn::Fields::Unnamed(fields) => {
                    let mut items = Vec::with_capacity(fields.unnamed.len());
                    let mut errors = Vec::new();

                    for field in &fields.unnamed {
                        match FromField::from_field(field) {
                            Ok(val) => items.push(val),
                            Err(err) => errors.push({
                                if let Some(ident) = &field.ident {
                                    err.at(ident)
                                } else {
                                    err
                                }
                            }),
                        }
                    }

                    (items, errors)
                }
                syn::Fields::Unit => (vec![], vec![]),
            }
        };

        if !errors.is_empty() {
            Err(Error::multiple(errors))
        } else {
            Ok(Self::new(fields.into(), items).with_span(fields.span()))
        }
    }
}

impl<T: ToTokens> ToTokens for Fields<T> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let fields = &self.fields;
        // An unknown Span should be `Span::call_site()`;
        // https://docs.rs/syn/1.0.12/syn/spanned/trait.Spanned.html#tymethod.span
        let span = self.span.unwrap_or_else(Span::call_site);

        match self.style {
            Style::Struct => {
                let trailing_comma = {
                    if fields.is_empty() {
                        quote!()
                    } else {
                        quote!(,)
                    }
                };

                tokens.extend(quote_spanned![span => { #(#fields),* #trailing_comma }]);
            }
            Style::Tuple => {
                tokens.extend(quote_spanned![span => ( #(#fields),* )]);
            }
            Style::Unit => {}
        }
    }
}

impl<T: PartialEq> PartialEq for Fields<T> {
    fn eq(&self, other: &Self) -> bool {
        self.style == other.style && self.fields == other.fields
    }
}

impl<T: Eq> Eq for Fields<T> {}

impl<T> IntoIterator for Fields<T> {
    type Item = T;
    type IntoIter = vec::IntoIter<T>;

    fn into_iter(self) -> Self::IntoIter {
        self.fields.into_iter()
    }
}

impl<T> From<Style> for Fields<T> {
    fn from(style: Style) -> Self {
        Self::new(style, Vec::new())
    }
}

impl<T, U: Into<Vec<T>>> From<(Style, U)> for Fields<T> {
    fn from((style, fields): (Style, U)) -> Self {
        style.with_fields(fields)
    }
}

impl<T: UsesTypeParams> UsesTypeParams for Fields<T> {
    fn uses_type_params<'a>(
        &self,
        options: &usage::Options,
        type_set: &'a IdentSet,
    ) -> IdentRefSet<'a> {
        self.fields.uses_type_params(options, type_set)
    }
}

impl<T: UsesLifetimes> UsesLifetimes for Fields<T> {
    fn uses_lifetimes<'a>(
        &self,
        options: &usage::Options,
        lifetimes: &'a LifetimeSet,
    ) -> LifetimeRefSet<'a> {
        self.fields.uses_lifetimes(options, lifetimes)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Style {
    Tuple,
    Struct,
    Unit,
}

impl Style {
    pub fn is_unit(self) -> bool {
        self == Style::Unit
    }

    pub fn is_tuple(self) -> bool {
        self == Style::Tuple
    }

    pub fn is_struct(self) -> bool {
        self == Style::Struct
    }

    /// Creates a new `Fields` of the specified style with the passed-in fields.
    fn with_fields<T, U: Into<Vec<T>>>(self, fields: U) -> Fields<T> {
        Fields::new(self, fields.into())
    }
}

impl From<syn::Fields> for Style {
    fn from(vd: syn::Fields) -> Self {
        (&vd).into()
    }
}

impl<'a> From<&'a syn::Fields> for Style {
    fn from(vd: &syn::Fields) -> Self {
        match *vd {
            syn::Fields::Named(_) => Style::Struct,
            syn::Fields::Unnamed(_) => Style::Tuple,
            syn::Fields::Unit => Style::Unit,
        }
    }
}

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

    // it is not possible to directly convert a TokenStream into syn::Fields, so you have
    // to convert the TokenStream into DeriveInput first and then pass the syn::Fields to
    // Fields::try_from.
    fn token_stream_to_fields(input: TokenStream) -> Fields<syn::Field> {
        Fields::try_from(&{
            if let syn::Data::Struct(s) =
                syn::parse2::<syn::DeriveInput>(input.clone()).unwrap().data
            {
                s.fields
            } else {
                panic!();
            }
        })
        .unwrap()
    }

    #[test]
    fn test_style_eq() {
        // `Fields` implements `Eq` manually, so it has to be ensured, that all fields of `Fields`
        // implement `Eq`, this test would fail, if someone accidentally removed the Eq
        // implementation from `Style`.
        struct _AssertEq
        where
            Style: Eq;
    }

    #[test]
    fn test_fields_to_tokens_struct() {
        let reference = quote!(
            {
                executable: String,
                args: Vec<String>,
                env: Vec<String>,
                index: usize,
                optional: Option<String>,
                current_dir: String,
            }
        );
        let input = quote!(
            struct ExampleTest #reference
        );

        let fields = token_stream_to_fields(input);

        let mut result = quote!();
        fields.to_tokens(&mut result);
        assert_eq!(result.to_string(), reference.to_string());
    }

    #[test]
    fn test_fields_to_tokens_tuple() {
        let reference = quote!((u64, usize, &'a T));
        let input = quote!(
            struct ExampleTest #reference;
        );

        let fields = token_stream_to_fields(input);

        let mut result = quote!();
        fields.to_tokens(&mut result);
        assert_eq!(result.to_string(), reference.to_string());
    }
}
