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

pub fn impl_add(struct_name: &Ident) -> TokenStream {
    quote! {
        impl std::ops::Add for #struct_name {
            type Output = Self;

            fn add(self, rhs: Self) -> Self {
                Self::from_bits(self.0 + rhs.0)
            }
        }
    }
}

pub fn impl_checked_add(struct_name: &Ident) -> TokenStream {
    quote! {
        impl num_traits::ops::checked::CheckedAdd for #struct_name {
            fn checked_add(&self, rhs: &Self) -> Option<Self> {
                Some(Self::from_bits(self.0.checked_add(rhs.0)?))
            }
        }
    }
}

pub fn impl_sub(struct_name: &Ident) -> TokenStream {
    quote! {
        impl std::ops::Sub for #struct_name {
            type Output = Self;

            fn sub(self, rhs: Self) -> Self {
                Self::from_bits(self.0 - rhs.0)
            }
        }
    }
}

pub fn impl_checked_sub(struct_name: &Ident) -> TokenStream {
    quote! {
        impl num_traits::ops::checked::CheckedSub for #struct_name {
            fn checked_sub(&self, rhs: &Self) -> Option<Self> {
                Some(Self::from_bits(self.0.checked_sub(rhs.0)?))
            }
        }
    }
}

pub fn impl_mul(struct_name: &Ident, shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            impl std::ops::Mul for #struct_name {
                type Output = Self;

                fn mul(self, rhs: Self) -> Self {
                    Self::from_bits(self.0 * (rhs.0 >> #shift))
                }
            }
        }
    } else {
        quote! {
            impl std::ops::Mul for #struct_name {
                type Output = Self;

                fn mul(self, rhs: Self) -> Self {
                    Self::from_bits(self.0 * rhs.0)
                }
            }
        }
    }
}

pub fn impl_checked_mul(struct_name: &Ident, ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            impl num_traits::ops::checked::CheckedMul for #struct_name {
                fn checked_mul(&self, rhs: &Self) -> Option<Self> {
                    Some(Self::from_bits(
                            <#ty as num_traits::ops::checked::CheckedMul>::checked_mul(&self.0, &(rhs.0 >> #shift))?
                    ))
                }
            }
        }
    } else {
        quote! {
            impl num_traits::ops::checked::CheckedMul for #struct_name {
                fn checked_mul(&self, rhs: &Self) -> Option<Self> {
                    Some(Self::from_bits(
                        <#ty as num_traits::ops::checked::CheckedMul>::checked_mul(&self.0, &rhs.0)?
                    ))
                }
            }
        }
    }
}

pub fn impl_div(struct_name: &Ident, shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            impl std::ops::Div for #struct_name {
                type Output = Self;

                fn div(self, rhs: Self) -> Self {
                    Self::from_bits((self.0 / rhs.0) << #shift)
                }
            }
        }
    } else {
        quote! {
            impl std::ops::Div for #struct_name {
                type Output = Self;

                fn div(self, rhs: Self) -> Self {
                    Self::from_bits(self.0 / rhs.0)
                }
            }
        }
    }
}

pub fn impl_checked_div(struct_name: &Ident, ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            impl num_traits::ops::checked::CheckedDiv for #struct_name {
                fn checked_div(&self, rhs: &Self) -> Option<Self> {
                    let value = <#ty as num_traits::ops::checked::CheckedDiv>::checked_div(&self.0, &rhs.0)?;
                    if (<Self as num_traits::Bounded>::min_value().0 >> #shift) <= value && value <= (<Self as num_traits::Bounded>::max_value().0 >> #shift) {
                        Some(Self::new(value))
                    } else {
                        None
                    }
                }
            }
        }
    } else {
        quote! {
            impl num_traits::ops::checked::CheckedDiv for #struct_name {
                fn checked_div(&self, rhs: &Self) -> Option<Self> {
                    Some(Self::from_bits(
                        <#ty as num_traits::ops::checked::CheckedDiv>::checked_div(&self.0, &rhs.0)?
                    ))
                }
            }
        }
    }
}

pub fn impl_rem(struct_name: &Ident) -> TokenStream {
    quote! {
        impl std::ops::Rem for #struct_name {
            type Output = Self;

            fn rem(self, rhs: Self) -> Self {
                Self::from_bits(self.0 % rhs.0)
            }
        }
    }
}

pub fn impl_checked_rem(struct_name: &Ident, ty: &Type) -> TokenStream {
    quote! {
        impl num_traits::ops::checked::CheckedRem for #struct_name {
            fn checked_rem(&self, rhs: &Self) -> Option<Self> {
                Some(Self::from_bits(
                    <#ty as num_traits::ops::checked::CheckedRem>::checked_rem(&self.0, &rhs.0)?
                ))
            }
        }
    }
}

pub fn impl_saturating(struct_name: &Ident, ty: &Type, masking: &TokenStream) -> TokenStream {
    quote! {
        impl num_traits::ops::saturating::Saturating for #struct_name {
            fn saturating_add(self, rhs: Self) -> Self {
                Self::from_bits(
                    <#ty as num_traits::ops::saturating::Saturating>::saturating_add(self.0, rhs.0) #masking
                )
            }

            fn saturating_sub(self, rhs: Self) -> Self {
                Self::from_bits(
                    <#ty as num_traits::ops::saturating::Saturating>::saturating_sub(self.0, rhs.0) #masking
                )
            }
        }
    }
}
