//! 構造体の実装

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

pub fn struct_name(n: u32, is_signed: bool) -> Ident {
    let prefix = if is_signed { "I" } else { "U" };
    Ident::new(&format!("{}{}", prefix, n), Span::call_site())
}

pub fn feature_name(n: u32, is_signed: bool) -> String {
    let prefix = if is_signed { "i" } else { "u" };
    format!("{}{}", prefix, n)
}

pub fn inner_type(n: u32, is_signed: bool) -> Type {
    if is_signed {
        inner_int_type(n)
    } else {
        inner_uint_type(n)
    }
}

pub fn size(n: u32) -> u32 {
    if n <= 8 {
        8
    } else if n <= 16 {
        16
    } else if n <= 32 {
        32
    } else if n <= 64 {
        64
    } else if n <= 128 {
        128
    } else {
        unimplemented!()
    }
}

pub fn inner_uint_type(n: u32) -> Type {
    if n <= 8 {
        parse_quote! { u8 }
    } else if n <= 16 {
        parse_quote! { u16 }
    } else if n <= 32 {
        parse_quote! { u32 }
    } else if n <= 64 {
        parse_quote! { u64 }
    } else if n <= 128 {
        parse_quote! { u128 }
    } else {
        unimplemented!()
    }
}

pub fn inner_int_type(n: u32) -> Type {
    if n <= 8 {
        parse_quote! { i8 }
    } else if n <= 16 {
        parse_quote! { i16 }
    } else if n <= 32 {
        parse_quote! { i32 }
    } else if n <= 64 {
        parse_quote! { i64 }
    } else if n <= 128 {
        parse_quote! { i128 }
    } else {
        unimplemented!()
    }
}

pub fn shift(n: u32) -> Option<LitInt> {
    let s = size(n) - n;
    if s == 0 { None } else { Some(parse_quote!(#s)) }
}

pub fn masking(shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            >> #shift << #shift
        }
    } else {
        quote! {}
    }
}

pub fn impl_new_func(ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            #[doc = " 整数値から生成"]
            #[doc = ""]
            #[doc = " # Panics"]
            #[doc = ""]
            #[doc = " デバッグ時に範囲外を超えるとパニックする。"]
            #[doc = " デバッグ時でなければ、上位桁が無視される。"]
            pub fn new(value: #ty) -> Self {
                debug_assert!(
                    (<Self as num_traits::Bounded>::min_value().0 >> #shift) <= value,
                    "underflow value: {}",
                    value
            );
                debug_assert!(
                    value <= (<Self as num_traits::Bounded>::max_value().0 >> #shift),
                    "overflow value: {}",
                    value
                );
                Self::from_bits(value << #shift)
            }
        }
    } else {
        quote! {
            #[doc = " 整数値から生成"]
            pub fn new(value: #ty) -> Self {
                Self::from_bits(value)
            }
        }
    }
}

fn impl_from_bits(ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    if let Some(shift) = shift {
        quote! {
            #[doc = " ビットから直接生成"]
            #[doc = ""]
            #[doc = " 直接の利用を非推奨。"]
            #[doc = ""]
            #[doc = " # Panics"]
            #[doc = ""]
            #[doc = " デバッグ時に下位桁が 0 でなければパニックする。"]
            #[doc = " デバッグ時でなければ、下位桁はそのまま入る。"]
            pub fn from_bits(value: #ty) -> Self {
                debug_assert_eq!(value % (1 << #shift), 0);
                Self(value)
            }
        }
    } else {
        quote! {
            #[doc = " ビットから直接生成"]
            #[doc = ""]
            #[doc = " 直接の利用を非推奨。"]
            pub fn from_bits(value: #ty) -> Self {
                Self(value)
            }
        }
    }
}

pub fn impl_struct(struct_name: &Ident, ty: &Type, shift: &Option<LitInt>) -> TokenStream {
    let new_func_impl = impl_new_func(ty, shift);
    let from_bits_impl = impl_from_bits(ty, shift);
    let doc = {
        let sn = format!("{}", struct_name);
        if sn.starts_with("U") {
            format!(" 符号なし {} ビット整数", sn.strip_prefix("U").unwrap())
        } else if sn.starts_with("I") {
            format!(" 符号付き {} ビット整数", sn.strip_prefix("I").unwrap())
        } else {
            unreachable!()
        }
    };
    quote! {
        #[doc = #doc]
        #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
        pub struct #struct_name(#ty);
        impl #struct_name {
            #new_func_impl
            #from_bits_impl
        }
    }
}

pub fn impl_debug(struct_name: &Ident, shift: &Option<LitInt>) -> TokenStream {
    let fs = format!("{}({{}})", struct_name);
    if let Some(shift) = shift {
        quote! {
            impl std::fmt::Debug for #struct_name {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    write!(f, #fs, self.0 >> #shift)
                }
            }
        }
    } else {
        quote! {
            impl std::fmt::Debug for #struct_name {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    write!(f, #fs, self.0)
                }
            }
        }
    }
}
