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

pub fn impl_prim_int(
    struct_name: &Ident,
    ty: &Type,
    shift: &Option<LitInt>,
    masking: &TokenStream,
) -> TokenStream {
    let shl_shift;
    let shr_shift;
    let sub_shift;
    let one_mask;
    let rotate_left;
    let rotate_right;
    if let Some(shift) = shift {
        shl_shift = quote! { << #shift };
        shr_shift = quote! { >> #shift };
        sub_shift = quote! { - #shift as u32 };
        one_mask = quote! { | (1 << #shift) - 1 };
        rotate_left = quote! {
            {
                let up = self.0.unsigned_shl(n);
                let down = self.0.rotate_left(n) & ((1 << #shift) - 1);
                let down = down.unsigned_shl(#shift);
                up | down
            }
        };
        rotate_right = quote! {
            {
                let up = self.0.unsigned_shr(n) & !((1 << #shift) - 1);
                let down = self.0.unsigned_shr(#shift).rotate_right(n);
                let down = down #masking;
                up | down
            }
        };
    } else {
        shl_shift = quote! {};
        shr_shift = quote! {};
        sub_shift = quote! {};
        one_mask = quote! {};
        rotate_left = quote! {
            self.0.rotate_left(n)
        };
        rotate_right = quote! {
            self.0.rotate_right(n)
        };
    }
    quote! {
        impl num_traits::int::PrimInt for #struct_name {
            fn count_ones(self) -> u32 {
                <#ty as num_traits::int::PrimInt>::count_ones(self.0)
            }

            fn count_zeros(self) -> u32 {
                <#ty as num_traits::int::PrimInt>::count_zeros(self.0) #sub_shift
            }

            fn leading_zeros(self) -> u32 {
                <#ty as num_traits::int::PrimInt>::leading_zeros(self.0 #one_mask)
            }

            fn trailing_zeros(self) -> u32 {
                <#ty as num_traits::int::PrimInt>::trailing_zeros(self.0 #shr_shift)
            }

            fn rotate_left(self, n: u32) -> Self {
                let result = #rotate_left;
                Self::from_bits(result as #ty)
            }

            fn rotate_right(self, n: u32) -> Self {
                let result = #rotate_right;
                Self::from_bits(result as #ty)
            }

            fn signed_shl(self, n: u32) -> Self {
                Self::from_bits(self.0.signed_shl(n) #masking)
            }

            fn signed_shr(self, n: u32) -> Self {
                Self::from_bits(self.0.signed_shr(n) #masking)
            }

            fn unsigned_shl(self, n: u32) -> Self {
                Self::from_bits(self.0.unsigned_shl(n) #masking)
            }

            fn unsigned_shr(self, n: u32) -> Self {
                Self::from_bits(self.0.unsigned_shr(n) #masking)
            }

            fn swap_bytes(self) -> Self {
                unimplemented!()
            }

            fn from_be(x: Self) -> Self {
                unimplemented!()
            }

            fn from_le(x: Self) -> Self {
                unimplemented!()
            }

            fn to_be(self) -> Self {
                unimplemented!()
            }

            fn to_le(self) -> Self {
                unimplemented!()
            }

            fn pow(self, exp: u32) -> Self {
                Self::from_bits(<#ty as num_traits::int::PrimInt>::pow(self.0 #shr_shift, exp) #shl_shift)
            }

            fn leading_ones(self) -> u32 {
                <#ty as num_traits::int::PrimInt>::leading_ones(self.0)
            }

            fn trailing_ones(self) -> u32 {
                <#ty as num_traits::int::PrimInt>::trailing_ones(self.0 #shr_shift)
            }

            fn reverse_bits(self) -> Self {
                Self::from_bits(<#ty as num_traits::int::PrimInt>::reverse_bits(self.0) #shl_shift)
            }
        }
    }
}
