use proc_macro2::TokenStream;
use quote::quote;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;


pub enum Scalar {
    Enum(Enum),
}

pub struct Enum {
    pub ident: syn::Ident,
    pub variants: Vec<Variant>,
}

pub struct Variant {
    pub ident: syn::Ident,
    pub name: String,
}

impl Enum {
    fn new(ident: syn::Ident, _attrs: Vec<syn::Attribute>,
           src_variants: impl Iterator<Item=syn::Variant>)
        -> syn::Result<Self>
    {
        let mut variants = Vec::new();
        for var in src_variants {
            match var.fields {
                syn::Fields::Unit => {
                    let name = heck::ToKebabCase
                        ::to_kebab_case(&var.ident.unraw().to_string()[..]);
                    variants.push(Variant {
                        ident: var.ident,
                        name,
                    });
                }
                _ => {
                    return Err(syn::Error::new(var.span(),
                        "only unit variants are allowed for DecodeScalar"));
                }
            }
        }
        Ok(Enum {
            ident,
            variants,
        })
    }
}


impl Parse for Scalar {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut attrs = input.call(syn::Attribute::parse_outer)?;
        let ahead = input.fork();
        let _vis: syn::Visibility = ahead.parse()?;

        let lookahead = ahead.lookahead1();
        if lookahead.peek(syn::Token![enum]) {
            let item: syn::ItemEnum = input.parse()?;
            attrs.extend(item.attrs);
            Enum::new(item.ident, attrs,
                      item.variants.into_iter())
                .map(Scalar::Enum)
        } else {
            Err(lookahead.error())
        }
    }
}

pub fn emit_scalar(s: &Scalar) -> syn::Result<TokenStream> {
    match s {
        Scalar::Enum(e) => {
            emit_enum(e)
        }
    }
}


pub fn emit_enum(e: &Enum) -> syn::Result<TokenStream> {
    let e_name = &e.ident;
    let value_err = if e.variants.len() <= 3 {
        format!("expected one of {}",
                e.variants.iter()
                .map(|v| format!("`{}`", v.name.escape_default()))
                .collect::<Vec<_>>()
                .join(", "))
    } else {
        format!("expected `{}`, `{}`, or one of {} others",
                e.variants[0].name.escape_default(),
                e.variants[1].name.escape_default(),
                e.variants.len() - 2)
    };
    let match_branches = e.variants.iter()
        .map(|var| {
            let name = &var.name;
            let ident = &var.ident;
            quote!(#name => Ok(#e_name::#ident))
        });
    Ok(quote! {
        impl<S: ::knuffel::traits::ErrorSpan> ::knuffel::DecodeScalar<S>
                for #e_name {
            fn raw_decode(val: &::knuffel::span::Spanned<
                          ::knuffel::ast::Literal, S>,
                          ctx: &mut ::knuffel::decode::Context<S>)
                -> Result<#e_name, ::knuffel::errors::DecodeError<S>>
            {
                match &**val {
                    ::knuffel::ast::Literal::String(ref s) => {
                        match &s[..] {
                            #(#match_branches,)*
                            _ => {
                                Err(::knuffel::errors::DecodeError::conversion(
                                        val, #value_err))
                            }
                        }
                    }
                    _ => {
                        Err(::knuffel::errors::DecodeError::scalar_kind(
                            ::knuffel::decode::Kind::String,
                            &val,
                        ))
                    }
                }
            }
            fn type_check(type_name: &Option<::knuffel::span::Spanned<
                          ::knuffel::ast::TypeName, S>>,
                          ctx: &mut ::knuffel::decode::Context<S>)
            {
                if let Some(typ) = type_name {
                    ctx.emit_error(::knuffel::errors::DecodeError::TypeName {
                        span: typ.span().clone(),
                        found: Some((**typ).clone()),
                        expected: ::knuffel::errors::ExpectedType::no_type(),
                        rust_type: stringify!(#e_name),
                    });
                }
            }
        }
    })
}
