use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{Ident, LitInt, Type};

pub fn impl_to_primitive(struct_name: &Ident, ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    let mut impls = Vec::new();
    for prim in [
        "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize",
    ]
    .iter()
    {
        let prim_ty: Type = syn::parse_str(prim).unwrap();
        let fn_name = Ident::new(&format!("to_{}", prim), struct_name.span());
        impls.push(if prim_ty == *ty {
            if let Some(shift) = shift {
                quote! {
                    fn #fn_name(&self) -> Option<#prim_ty> {
                        Some(self.0 >> #shift)
                    }
                }
            } else {
                quote! {
                    fn #fn_name(&self) -> Option<#prim_ty> {
                        Some(self.0)
                    }
                }
            }
        } else {
            quote! {
                fn #fn_name(&self) -> Option<#prim_ty> {
                    self.0.#fn_name()
                }
            }
        })
    }
    quote! {
        impl num_traits::ToPrimitive for #struct_name {
            #(#impls)*
        }
    }
}

pub fn impl_num_cast(struct_name: &Ident, ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    let fn_name = Ident::new(&format!("to_{}", ty.to_token_stream()), struct_name.span());
    let check = if let Some(shift) = shift {
        quote! {
                if !((<Self as num_traits::Bounded>::min_value().0 >> #shift)..=(<Self as num_traits::Bounded>::max_value().0 >> #shift)).contains(&(n)) {
                    return None;
                }
        }
    } else {
        quote! {}
    };
    quote! {
        impl num_traits::NumCast for #struct_name {
            fn from<T: num_traits::ToPrimitive>(n: T) -> Option<Self> {
                let n = n.#fn_name()?;
                #check
                Some(Self::new(n))
            }
        }
    }
}
