use proc_macro2::TokenStream;

use quote::quote;
use syn::{self, Data, Fields, Ident, Variant};

use crate::util;

#[derive(Debug, Default)]
struct TransparentAttributes {
    transparent: Option<bool>,
    name: Option<String>,
    description: Option<String>,
}

impl syn::parse::Parse for TransparentAttributes {
    fn parse(input: syn::parse::ParseStream) -> syn::parse::Result<Self> {
        let mut output = Self {
            transparent: None,
            name: None,
            description: None,
        };

        while !input.is_empty() {
            let ident: syn::Ident = input.parse()?;
            match ident.to_string().as_str() {
                "name" => {
                    input.parse::<syn::Token![=]>()?;
                    let val = input.parse::<syn::LitStr>()?;
                    output.name = Some(val.value());
                }
                "description" => {
                    input.parse::<syn::Token![=]>()?;
                    let val = input.parse::<syn::LitStr>()?;
                    output.description = Some(val.value());
                }
                "transparent" => {
                    output.transparent = Some(true);
                }
                other => {
                    return Err(input.error(format!("Unknown attribute: {}", other)));
                }
            }
            if input.lookahead1().peek(syn::Token![,]) {
                input.parse::<syn::Token![,]>()?;
            }
        }

        Ok(output)
    }
}

impl TransparentAttributes {
    fn from_attrs(attrs: &Vec<syn::Attribute>) -> syn::parse::Result<Self> {
        match util::find_graphql_attr(attrs) {
            Some(attr) => {
                let mut parsed: TransparentAttributes = attr.parse_args()?;
                if parsed.description.is_none() {
                    parsed.description = util::get_doc_comment(attrs);
                }
                Ok(parsed)
            }
            None => Ok(Default::default()),
        }
    }
}

pub fn impl_scalar_value(ast: &syn::DeriveInput, is_internal: bool) -> TokenStream {
    let ident = &ast.ident;

    match ast.data {
        Data::Enum(ref enum_data) => impl_scalar_enum(ident, enum_data, is_internal),
        Data::Struct(ref struct_data) => impl_scalar_struct(ast, struct_data, is_internal),
        Data::Union(_) => {
            panic!("#[derive(GraphQLScalarValue)] may not be applied to unions");
        }
    }
}

fn impl_scalar_struct(
    ast: &syn::DeriveInput,
    data: &syn::DataStruct,
    is_internal: bool,
) -> TokenStream {
    let field = match data.fields {
        syn::Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => {
            fields.unnamed.first().unwrap()
        }
        _ => {
            panic!("#[derive(GraphQLScalarValue)] may only be applied to enums or tuple structs with a single field");
        }
    };
    let ident = &ast.ident;
    let attrs = match TransparentAttributes::from_attrs(&ast.attrs) {
        Ok(attrs) => attrs,
        Err(e) => {
            panic!("Invalid #[graphql] attribute: {}", e);
        }
    };
    let inner_ty = &field.ty;
    let name = attrs.name.unwrap_or(ident.to_string());

    let crate_name = if is_internal {
        quote!(crate)
    } else {
        quote!(juniper)
    };

    let description = match attrs.description {
        Some(val) => quote!( .description( #val ) ),
        None => quote!(),
    };

    quote!(
        impl<S> #crate_name::GraphQLType<S> for #ident
        where
            S: #crate_name::ScalarValue,
            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
        {
            type Context = ();
            type TypeInfo = ();

            fn name(_: &Self::TypeInfo) -> Option<&str> {
                Some(#name)
            }

            fn meta<'r>(
                info: &Self::TypeInfo,
                registry: &mut #crate_name::Registry<'r, S>,
            ) -> #crate_name::meta::MetaType<'r, S>
            where
                for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
                S: 'r,
            {
                registry.build_scalar_type::<Self>(info)
                    #description
                    .into_meta()
            }

            fn resolve(
                &self,
                info: &(),
                selection: Option<&[#crate_name::Selection<S>]>,
                executor: &#crate_name::Executor<Self::Context, S>,
            ) -> #crate_name::Value<S> {
                #crate_name::GraphQLType::resolve(&self.0, info, selection, executor)
            }
        }

        impl<S> #crate_name::ToInputValue<S> for #ident
        where
            S: #crate_name::ScalarValue,
            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
        {
            fn to_input_value(&self) -> #crate_name::InputValue<S> {
                #crate_name::ToInputValue::to_input_value(&self.0)
            }
        }

        impl<S> #crate_name::FromInputValue<S> for #ident
        where
            S: #crate_name::ScalarValue,
            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
        {
            fn from_input_value(v: &#crate_name::InputValue<S>) -> Option<#ident> {
                let inner: #inner_ty = #crate_name::FromInputValue::from_input_value(v)?;
                Some(#ident(inner))
            }
        }

        impl<S> #crate_name::ParseScalarValue<S> for #ident
        where
            S: #crate_name::ScalarValue,
            for<'__b> &'__b S: #crate_name::ScalarRefValue<'__b>,
        {
            fn from_str<'a>(
                value: #crate_name::parser::ScalarToken<'a>,
            ) -> #crate_name::ParseScalarResult<'a, S> {
                <#inner_ty as #crate_name::ParseScalarValue<S>>::from_str(value)
            }
        }
    )
}

fn impl_scalar_enum(ident: &syn::Ident, data: &syn::DataEnum, is_internal: bool) -> TokenStream {
    let froms = data
        .variants
        .iter()
        .map(|v| derive_from_variant(v, ident))
        .collect::<Result<Vec<_>, String>>()
        .unwrap_or_else(|s| panic!("{}", s));

    let serialize = derive_serialize(data.variants.iter(), ident, is_internal);

    let display = derive_display(data.variants.iter(), ident);

    quote! {
        #(#froms)*

        #serialize
        #display
    }
}

fn derive_display<'a, I>(variants: I, ident: &Ident) -> TokenStream
where
    I: Iterator<Item = &'a Variant>,
{
    let arms = variants.map(|v| {
        let variant = &v.ident;
        quote!(#ident::#variant(ref v) => write!(f, "{}", v),)
    });

    quote! {
        impl std::fmt::Display for #ident {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                match *self {
                    #(#arms)*
                }
            }
        }
    }
}

fn derive_serialize<'a, I>(variants: I, ident: &Ident, is_internal: bool) -> TokenStream
where
    I: Iterator<Item = &'a Variant>,
{
    let arms = variants.map(|v| {
        let variant = &v.ident;
        quote!(#ident::#variant(ref v) => v.serialize(serializer),)
    });

    let serde_path = if is_internal {
        quote!(crate::serde)
    } else {
        quote!(juniper::serde)
    };

    quote! {
        impl #serde_path::Serialize for #ident {
            fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
            where S: #serde_path::Serializer
            {
                match *self {
                    #(#arms)*
                }
            }
        }
    }
}

fn derive_from_variant(variant: &Variant, ident: &Ident) -> Result<TokenStream, String> {
    let ty = match variant.fields {
        Fields::Unnamed(ref u) if u.unnamed.len() == 1 => &u.unnamed.first().unwrap().ty,

        _ => {
            return Err(String::from(
                "Only enums with exactly one unnamed field per variant are supported",
            ));
        }
    };

    let variant = &variant.ident;

    Ok(quote! {
        impl std::convert::From<#ty> for #ident {
            fn from(t: #ty) -> Self {
                #ident::#variant(t)
            }
        }

        impl<'a> std::convert::From<&'a #ident> for std::option::Option<&'a #ty> {
            fn from(t: &'a #ident) -> Self {
                match *t {
                    #ident::#variant(ref t) => std::option::Option::Some(t),
                    _ => std::option::Option::None
                }
            }
        }

        impl std::convert::From<#ident> for std::option::Option<#ty> {
            fn from(t: #ident) -> Self {
                match t {
                    #ident::#variant(t) => std::option::Option::Some(t),
                    _ => std::option::Option::None
                }
            }
        }
    })
}
