// 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 proc_macro2::TokenStream;
use punctuated::Punctuated;
#[cfg(feature = "extra-traits")]
use std::hash::{Hash, Hasher};
#[cfg(feature = "extra-traits")]
use tt::TokenStreamHelper;

ast_enum_of_structs! {
    /// The possible types that a Rust value could have.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    ///
    /// # Syntax tree enum
    ///
    /// This type is a [syntax tree enum].
    ///
    /// [syntax tree enum]: enum.Expr.html#syntax-tree-enums
    pub enum Type {
        /// A dynamically sized slice type: `[T]`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Slice(TypeSlice {
            pub bracket_token: token::Bracket,
            pub elem: Box<Type>,
        }),

        /// A fixed size array type: `[T; n]`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Array(TypeArray {
            pub bracket_token: token::Bracket,
            pub elem: Box<Type>,
            pub semi_token: Token![;],
            pub len: Expr,
        }),

        /// A raw pointer type: `*const T` or `*mut T`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Ptr(TypePtr {
            pub star_token: Token![*],
            pub const_token: Option<Token![const]>,
            pub mutability: Option<Token![mut]>,
            pub elem: Box<Type>,
        }),

        /// A reference type: `&'a T` or `&'a mut T`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Reference(TypeReference {
            pub and_token: Token![&],
            pub lifetime: Option<Lifetime>,
            pub mutability: Option<Token![mut]>,
            pub elem: Box<Type>,
        }),

        /// A bare function type: `fn(usize) -> bool`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub BareFn(TypeBareFn {
            pub unsafety: Option<Token![unsafe]>,
            pub abi: Option<Abi>,
            pub fn_token: Token![fn],
            pub lifetimes: Option<BoundLifetimes>,
            pub paren_token: token::Paren,
            pub inputs: Punctuated<BareFnArg, Token![,]>,
            pub variadic: Option<Token![...]>,
            pub output: ReturnType,
        }),

        /// The never type: `!`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Never(TypeNever {
            pub bang_token: Token![!],
        }),

        /// A tuple type: `(A, B, C, String)`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Tuple(TypeTuple {
            pub paren_token: token::Paren,
            pub elems: Punctuated<Type, Token![,]>,
        }),

        /// A path like `std::slice::Iter`, optionally qualified with a
        /// self-type as in `<Vec<T> as SomeTrait>::Associated`.
        ///
        /// Type arguments are stored in the Path itself.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Path(TypePath {
            pub qself: Option<QSelf>,
            pub path: Path,
        }),

        /// A trait object type `Bound1 + Bound2 + Bound3` where `Bound` is a
        /// trait or a lifetime.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub TraitObject(TypeTraitObject {
            pub dyn_token: Option<Token![dyn]>,
            pub bounds: Punctuated<TypeParamBound, Token![+]>,
        }),

        /// An `impl Bound1 + Bound2 + Bound3` type where `Bound` is a trait or
        /// a lifetime.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub ImplTrait(TypeImplTrait {
            pub impl_token: Token![impl],
            pub bounds: Punctuated<TypeParamBound, Token![+]>,
        }),

        /// A parenthesized type equivalent to the inner type.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Paren(TypeParen {
            pub paren_token: token::Paren,
            pub elem: Box<Type>,
        }),

        /// A type contained within invisible delimiters.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Group(TypeGroup {
            pub group_token: token::Group,
            pub elem: Box<Type>,
        }),

        /// Indication that a type should be inferred by the compiler: `_`.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Infer(TypeInfer {
            pub underscore_token: Token![_],
        }),

        /// A macro in the type position.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Macro(TypeMacro {
            pub mac: Macro,
        }),

        /// Tokens in type position not interpreted by Syn.
        ///
        /// *This type is available if Syn is built with the `"derive"` or
        /// `"full"` feature.*
        pub Verbatim(TypeVerbatim #manual_extra_traits {
            pub tts: TokenStream,
        }),
    }
}

#[cfg(feature = "extra-traits")]
impl Eq for TypeVerbatim {}

#[cfg(feature = "extra-traits")]
impl PartialEq for TypeVerbatim {
    fn eq(&self, other: &Self) -> bool {
        TokenStreamHelper(&self.tts) == TokenStreamHelper(&other.tts)
    }
}

#[cfg(feature = "extra-traits")]
impl Hash for TypeVerbatim {
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        TokenStreamHelper(&self.tts).hash(state);
    }
}

ast_struct! {
    /// The binary interface of a function: `extern "C"`.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    pub struct Abi {
        pub extern_token: Token![extern],
        pub name: Option<LitStr>,
    }
}

ast_struct! {
    /// An argument in a function type: the `usize` in `fn(usize) -> bool`.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    pub struct BareFnArg {
        pub name: Option<(BareFnArgName, Token![:])>,
        pub ty: Type,
    }
}

ast_enum! {
    /// Name of an argument in a function type: the `n` in `fn(n: usize)`.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    pub enum BareFnArgName {
        /// Argument given a name.
        Named(Ident),
        /// Argument not given a name, matched with `_`.
        Wild(Token![_]),
    }
}

ast_enum! {
    /// Return type of a function signature.
    ///
    /// *This type is available if Syn is built with the `"derive"` or `"full"`
    /// feature.*
    pub enum ReturnType {
        /// Return type is not specified.
        ///
        /// Functions default to `()` and closures default to type inference.
        Default,
        /// A particular type is returned.
        Type(Token![->], Box<Type>),
    }
}

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

    impl Synom for Type {
        named!(parse -> Self, call!(ambig_ty, true));

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

    impl Type {
        /// In some positions, types may not contain the `+` character, to
        /// disambiguate them. For example in the expression `1 as T`, T may not
        /// contain a `+` character.
        ///
        /// This parser does not allow a `+`, while the default parser does.
        named!(pub without_plus -> Self, call!(ambig_ty, false));
    }

    named!(ambig_ty(allow_plus: bool) -> Type, alt!(
        syn!(TypeGroup) => { Type::Group }
        |
        // must be before TypeTuple
        call!(TypeParen::parse, allow_plus) => { Type::Paren }
        |
        // must be before TypePath
        syn!(TypeMacro) => { Type::Macro }
        |
        // must be before TypePath
        syn!(TypeBareFn) => { Type::BareFn }
        |
        // must be before TypeTraitObject
        call!(TypePath::parse, allow_plus) => { Type::Path }
        |
        // Don't try parsing more than one trait bound if we aren't allowing it.
        // must be before TypeTuple
        call!(TypeTraitObject::parse, allow_plus) => { Type::TraitObject }
        |
        syn!(TypeSlice) => { Type::Slice }
        |
        syn!(TypeArray) => { Type::Array }
        |
        syn!(TypePtr) => { Type::Ptr }
        |
        syn!(TypeReference) => { Type::Reference }
        |
        syn!(TypeNever) => { Type::Never }
        |
        syn!(TypeTuple) => { Type::Tuple }
        |
        syn!(TypeImplTrait) => { Type::ImplTrait }
        |
        syn!(TypeInfer) => { Type::Infer }
    ));

    impl Synom for TypeSlice {
        named!(parse -> Self, map!(
            brackets!(syn!(Type)),
            |(b, ty)| TypeSlice {
                elem: Box::new(ty),
                bracket_token: b,
            }
        ));

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

    impl Synom for TypeArray {
        named!(parse -> Self, map!(
            brackets!(do_parse!(
                elem: syn!(Type) >>
                    semi: punct!(;) >>
                    len: syn!(Expr) >>
                    (elem, semi, len)
            )),
            |(brackets, (elem, semi, len))| {
                TypeArray {
                    elem: Box::new(elem),
                    len: len,
                    bracket_token: brackets,
                    semi_token: semi,
                }
            }
        ));

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

    impl Synom for TypePtr {
        named!(parse -> Self, do_parse!(
            star: punct!(*) >>
            mutability: alt!(
                keyword!(const) => { |c| (None, Some(c)) }
                |
                keyword!(mut) => { |m| (Some(m), None) }
            ) >>
            target: call!(Type::without_plus) >>
            (TypePtr {
                const_token: mutability.1,
                star_token: star,
                mutability: mutability.0,
                elem: Box::new(target),
            })
        ));

        fn description() -> Option<&'static str> {
            Some("raw pointer type")
        }
    }

    impl Synom for TypeReference {
        named!(parse -> Self, do_parse!(
            amp: punct!(&) >>
            life: option!(syn!(Lifetime)) >>
            mutability: option!(keyword!(mut)) >>
            // & binds tighter than +, so we don't allow + here.
            target: call!(Type::without_plus) >>
            (TypeReference {
                lifetime: life,
                mutability: mutability,
                elem: Box::new(target),
                and_token: amp,
            })
        ));

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

    impl Synom for TypeBareFn {
        named!(parse -> Self, do_parse!(
            lifetimes: option!(syn!(BoundLifetimes)) >>
            unsafety: option!(keyword!(unsafe)) >>
            abi: option!(syn!(Abi)) >>
            fn_: keyword!(fn) >>
            parens: parens!(do_parse!(
                inputs: call!(Punctuated::parse_terminated) >>
                variadic: option!(cond_reduce!(inputs.empty_or_trailing(), punct!(...))) >>
                (inputs, variadic)
            )) >>
            output: call!(ReturnType::without_plus) >>
            (TypeBareFn {
                unsafety: unsafety,
                abi: abi,
                lifetimes: lifetimes,
                output: output,
                variadic: (parens.1).1,
                fn_token: fn_,
                paren_token: parens.0,
                inputs: (parens.1).0,
            })
        ));

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

    impl Synom for TypeNever {
        named!(parse -> Self, map!(
            punct!(!),
            |b| TypeNever { bang_token: b }
        ));

        fn description() -> Option<&'static str> {
            Some("never type: `!`")
        }
    }

    impl Synom for TypeInfer {
        named!(parse -> Self, map!(
            punct!(_),
            |u| TypeInfer { underscore_token: u }
        ));

        fn description() -> Option<&'static str> {
            Some("inferred type: `_`")
        }
    }

    impl Synom for TypeTuple {
        named!(parse -> Self, do_parse!(
            data: parens!(Punctuated::parse_terminated) >>
            (TypeTuple {
                paren_token: data.0,
                elems: data.1,
            })
        ));

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

    impl Synom for TypeMacro {
        named!(parse -> Self, map!(syn!(Macro), |mac| TypeMacro { mac: mac }));

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

    impl Synom for TypePath {
        named!(parse -> Self, call!(Self::parse, false));

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

    impl TypePath {
        named!(parse(allow_plus: bool) -> Self, do_parse!(
            qpath: qpath >>
            parenthesized: option!(cond_reduce!(
                qpath.1.segments.last().unwrap().value().arguments.is_empty(),
                syn!(ParenthesizedGenericArguments)
            )) >>
            cond!(allow_plus, not!(punct!(+))) >>
            ({
                let (qself, mut path) = qpath;
                if let Some(parenthesized) = parenthesized {
                    let parenthesized = PathArguments::Parenthesized(parenthesized);
                    path.segments.last_mut().unwrap().value_mut().arguments = parenthesized;
                }
                TypePath { qself: qself, path: path }
            })
        ));
    }

    impl ReturnType {
        named!(pub without_plus -> Self, call!(Self::parse, false));
        named!(parse(allow_plus: bool) -> Self, alt!(
            do_parse!(
                arrow: punct!(->) >>
                ty: call!(ambig_ty, allow_plus) >>
                (ReturnType::Type(arrow, Box::new(ty)))
            )
            |
            epsilon!() => { |_| ReturnType::Default }
        ));
    }

    impl Synom for ReturnType {
        named!(parse -> Self, call!(Self::parse, true));

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

    impl Synom for TypeTraitObject {
        named!(parse -> Self, call!(Self::parse, true));

        fn description() -> Option<&'static str> {
            Some("trait object type")
        }
    }

    fn at_least_one_type(bounds: &Punctuated<TypeParamBound, Token![+]>) -> bool {
        for bound in bounds {
            if let TypeParamBound::Trait(_) = *bound {
                return true;
            }
        }
        false
    }

    impl TypeTraitObject {
        named!(pub without_plus -> Self, call!(Self::parse, false));

        // Only allow multiple trait references if allow_plus is true.
        named!(parse(allow_plus: bool) -> Self, do_parse!(
            dyn_token: option!(keyword!(dyn)) >>
            bounds: alt!(
                cond_reduce!(allow_plus, Punctuated::parse_terminated_nonempty)
                |
                syn!(TypeParamBound) => {|x| {
                    let mut bounds = Punctuated::new();
                    bounds.push_value(x);
                    bounds
                }}
            ) >>
            // Just lifetimes like `'a + 'b` is not a TraitObject.
            cond_reduce!(at_least_one_type(&bounds)) >>
            (TypeTraitObject {
                dyn_token: dyn_token,
                bounds: bounds,
            })
        ));
    }

    impl Synom for TypeImplTrait {
        named!(parse -> Self, do_parse!(
            impl_: keyword!(impl) >>
            // NOTE: rust-lang/rust#34511 includes discussion about whether or
            // not + should be allowed in ImplTrait directly without ().
            elem: call!(Punctuated::parse_terminated_nonempty) >>
            (TypeImplTrait {
                impl_token: impl_,
                bounds: elem,
            })
        ));

        fn description() -> Option<&'static str> {
            Some("`impl Trait` type")
        }
    }

    impl Synom for TypeGroup {
        named!(parse -> Self, do_parse!(
            data: grouped!(syn!(Type)) >>
            (TypeGroup {
                group_token: data.0,
                elem: Box::new(data.1),
            })
        ));

        fn description() -> Option<&'static str> {
            Some("type surrounded by invisible delimiters")
        }
    }

    impl Synom for TypeParen {
        named!(parse -> Self, call!(Self::parse, false));

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

    impl TypeParen {
        named!(parse(allow_plus: bool) -> Self, do_parse!(
            data: parens!(syn!(Type)) >>
            cond!(allow_plus, not!(punct!(+))) >>
            (TypeParen {
                paren_token: data.0,
                elem: Box::new(data.1),
            })
        ));
    }

    impl Synom for BareFnArg {
        named!(parse -> Self, do_parse!(
            name: option!(do_parse!(
                name: syn!(BareFnArgName) >>
                not!(punct!(::)) >>
                colon: punct!(:) >>
                (name, colon)
            )) >>
            ty: syn!(Type) >>
            (BareFnArg {
                name: name,
                ty: ty,
            })
        ));

        fn description() -> Option<&'static str> {
            Some("function type argument")
        }
    }

    impl Synom for BareFnArgName {
        named!(parse -> Self, alt!(
            map!(syn!(Ident), BareFnArgName::Named)
            |
            map!(punct!(_), BareFnArgName::Wild)
        ));

        fn description() -> Option<&'static str> {
            Some("function argument name")
        }
    }

    impl Synom for Abi {
        named!(parse -> Self, do_parse!(
            extern_: keyword!(extern) >>
            name: option!(syn!(LitStr)) >>
            (Abi {
                extern_token: extern_,
                name: name,
            })
        ));

        fn description() -> Option<&'static str> {
            Some("`extern` ABI qualifier")
        }
    }
}

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

    impl ToTokens for TypeSlice {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.bracket_token.surround(tokens, |tokens| {
                self.elem.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for TypeArray {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.bracket_token.surround(tokens, |tokens| {
                self.elem.to_tokens(tokens);
                self.semi_token.to_tokens(tokens);
                self.len.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for TypePtr {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.star_token.to_tokens(tokens);
            match self.mutability {
                Some(ref tok) => tok.to_tokens(tokens),
                None => {
                    TokensOrDefault(&self.const_token).to_tokens(tokens);
                }
            }
            self.elem.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeReference {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.and_token.to_tokens(tokens);
            self.lifetime.to_tokens(tokens);
            self.mutability.to_tokens(tokens);
            self.elem.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeBareFn {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.lifetimes.to_tokens(tokens);
            self.unsafety.to_tokens(tokens);
            self.abi.to_tokens(tokens);
            self.fn_token.to_tokens(tokens);
            self.paren_token.surround(tokens, |tokens| {
                self.inputs.to_tokens(tokens);
                if let Some(ref variadic) = self.variadic {
                    if !self.inputs.empty_or_trailing() {
                        let span = variadic.0[0];
                        <Token![,]>::new(span).to_tokens(tokens);
                    }
                    variadic.to_tokens(tokens);
                }
            });
            self.output.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeNever {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.bang_token.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeTuple {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.paren_token.surround(tokens, |tokens| {
                self.elems.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for TypePath {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            PathTokens(&self.qself, &self.path).to_tokens(tokens);
        }
    }

    impl ToTokens for TypeTraitObject {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.dyn_token.to_tokens(tokens);
            self.bounds.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeImplTrait {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.impl_token.to_tokens(tokens);
            self.bounds.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeGroup {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.group_token.surround(tokens, |tokens| {
                self.elem.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for TypeParen {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.paren_token.surround(tokens, |tokens| {
                self.elem.to_tokens(tokens);
            });
        }
    }

    impl ToTokens for TypeInfer {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.underscore_token.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeMacro {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.mac.to_tokens(tokens);
        }
    }

    impl ToTokens for TypeVerbatim {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.tts.to_tokens(tokens);
        }
    }

    impl ToTokens for ReturnType {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            match *self {
                ReturnType::Default => {}
                ReturnType::Type(ref arrow, ref ty) => {
                    arrow.to_tokens(tokens);
                    ty.to_tokens(tokens);
                }
            }
        }
    }

    impl ToTokens for BareFnArg {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            if let Some((ref name, ref colon)) = self.name {
                name.to_tokens(tokens);
                colon.to_tokens(tokens);
            }
            self.ty.to_tokens(tokens);
        }
    }

    impl ToTokens for BareFnArgName {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            match *self {
                BareFnArgName::Named(ref t) => t.to_tokens(tokens),
                BareFnArgName::Wild(ref t) => t.to_tokens(tokens),
            }
        }
    }

    impl ToTokens for Abi {
        fn to_tokens(&self, tokens: &mut TokenStream) {
            self.extern_token.to_tokens(tokens);
            self.name.to_tokens(tokens);
        }
    }
}
