// Copyright 2018 Syn Developers
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use super::*;
use punctuated::Punctuated;

ast_struct! {
    /// Data structure sent to a `proc_macro_derive` macro.
    ///
    /// *This type is available if Syn is built with the `"derive"` feature.*
    pub struct DeriveInput {
        /// Attributes tagged on the whole struct or enum.
        pub attrs: Vec<Attribute>,

        /// Visibility of the struct or enum.
        pub vis: Visibility,

        /// Name of the struct or enum.
        pub ident: Ident,

        /// Generics required to complete the definition.
        pub generics: Generics,

        /// Data within the struct or enum.
        pub data: Data,
    }
}

ast_enum_of_structs! {
    /// The storage of a struct, enum or union data structure.
    ///
    /// *This type is available if Syn is built with the `"derive"` feature.*
    ///
    /// # Syntax tree enum
    ///
    /// This type is a [syntax tree enum].
    ///
    /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
    pub enum Data {
        /// A struct input to a `proc_macro_derive` macro.
        ///
        /// *This type is available if Syn is built with the `"derive"`
        /// feature.*
        pub Struct(DataStruct {
            pub struct_token: Token![struct],
            pub fields: Fields,
            pub semi_token: Option<Token![;]>,
        }),

        /// An enum input to a `proc_macro_derive` macro.
        ///
        /// *This type is available if Syn is built with the `"derive"`
        /// feature.*
        pub Enum(DataEnum {
            pub enum_token: Token![enum],
            pub brace_token: token::Brace,
            pub variants: Punctuated<Variant, Token![,]>,
        }),

        /// A tagged union input to a `proc_macro_derive` macro.
        ///
        /// *This type is available if Syn is built with the `"derive"`
        /// feature.*
        pub Union(DataUnion {
            pub union_token: Token![union],
            pub fields: FieldsNamed,
        }),
    }

    do_not_generate_to_tokens
}

#[cfg(feature = "parsing")]
pub mod parsing {
    use super::*;

    use synom::Synom;

    enum DeriveInputKind {
        Struct(Token![struct]),
        Enum(Token![enum]),
        Union(Token![union]),
    }

    impl Synom for DeriveInputKind {
        named!(parse -> Self, alt!(
            keyword!(struct) => { DeriveInputKind::Struct }
            |
            keyword!(enum) => { DeriveInputKind::Enum }
            |
            keyword!(union) => { DeriveInputKind::Union }
        ));
    }

    impl Synom for DeriveInput {
        named!(parse -> Self, do_parse!(
            attrs: many0!(Attribute::parse_outer) >>
            vis: syn!(Visibility) >>
            which: syn!(DeriveInputKind) >>
            id: syn!(Ident) >>
            generics: syn!(Generics) >>
            item: switch!(value!(which),
                DeriveInputKind::Struct(s) => map!(data_struct, move |(wh, fields, semi)| DeriveInput {
                    ident: id,
                    vis: vis,
                    attrs: attrs,
                    generics: Generics {
                        where_clause: wh,
                        ..generics
                    },
                    data: Data::Struct(DataStruct {
                        struct_token: s,
                        fields: fields,
                        semi_token: semi,
                    }),
                })
                |
                DeriveInputKind::Enum(e) => map!(data_enum, move |(wh, brace, variants)| DeriveInput {
                    ident: id,
                    vis: vis,
                    attrs: attrs,
                    generics: Generics {
                        where_clause: wh,
                        ..generics
                    },
                    data: Data::Enum(DataEnum {
                        variants: variants,
                        brace_token: brace,
                        enum_token: e,
                    }),
                })
                |
                DeriveInputKind::Union(u) => map!(data_union, move |(wh, fields)| DeriveInput {
                    ident: id,
                    vis: vis,
                    attrs: attrs,
                    generics: Generics {
                        where_clause: wh,
                        ..generics
                    },
                    data: Data::Union(DataUnion {
                        union_token: u,
                        fields: fields,
                    }),
                })
            ) >>
            (item)
        ));

        fn description() -> Option<&'static str> {
            Some("derive input")
        }
    }

    named!(data_struct -> (Option<WhereClause>, Fields, Option<Token![;]>), alt!(
        do_parse!(
            wh: option!(syn!(WhereClause)) >>
            fields: syn!(FieldsNamed) >>
            (wh, Fields::Named(fields), None)
        )
        |
        do_parse!(
            fields: syn!(FieldsUnnamed) >>
            wh: option!(syn!(WhereClause)) >>
            semi: punct!(;) >>
            (wh, Fields::Unnamed(fields), Some(semi))
        )
        |
        do_parse!(
            wh: option!(syn!(WhereClause)) >>
            semi: punct!(;) >>
            (wh, Fields::Unit, Some(semi))
        )
    ));

    named!(data_enum -> (Option<WhereClause>, token::Brace, Punctuated<Variant, Token![,]>), do_parse!(
        wh: option!(syn!(WhereClause)) >>
        data: braces!(Punctuated::parse_terminated) >>
        (wh, data.0, data.1)
    ));

    named!(data_union -> (Option<WhereClause>, FieldsNamed), tuple!(
        option!(syn!(WhereClause)),
        syn!(FieldsNamed),
    ));
}

#[cfg(feature = "printing")]
mod printing {
    use super::*;
    use attr::FilterAttrs;
    use proc_macro2::TokenStream;
    use quote::ToTokens;

    impl ToTokens for DeriveInput {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            for attr in self.attrs.outer() {
                attr.to_tokens(tokens);
            }
            self.vis.to_tokens(tokens);
            match self.data {
                Data::Struct(ref d) => d.struct_token.to_tokens(tokens),
                Data::Enum(ref d) => d.enum_token.to_tokens(tokens),
                Data::Union(ref d) => d.union_token.to_tokens(tokens),
            }
            self.ident.to_tokens(tokens);
            self.generics.to_tokens(tokens);
            match self.data {
                Data::Struct(ref data) => match data.fields {
                    Fields::Named(ref fields) => {
                        self.generics.where_clause.to_tokens(tokens);
                        fields.to_tokens(tokens);
                    }
                    Fields::Unnamed(ref fields) => {
                        fields.to_tokens(tokens);
                        self.generics.where_clause.to_tokens(tokens);
                        TokensOrDefault(&data.semi_token).to_tokens(tokens);
                    }
                    Fields::Unit => {
                        self.generics.where_clause.to_tokens(tokens);
                        TokensOrDefault(&data.semi_token).to_tokens(tokens);
                    }
                },
                Data::Enum(ref data) => {
                    self.generics.where_clause.to_tokens(tokens);
                    data.brace_token.surround(tokens, |tokens| {
                        data.variants.to_tokens(tokens);
                    });
                }
                Data::Union(ref data) => {
                    self.generics.where_clause.to_tokens(tokens);
                    data.fields.to_tokens(tokens);
                }
            }
        }
    }
}
