use proc_macro2 as m4;
use quote::quote;

use super::utilities::{entity_name, reader_name, usize_lit};
use crate::ast::{DefaultContent, HasName};

mod implementation;

pub(super) trait GenEntity {
    fn gen_entity(&self) -> m4::TokenStream;
}

impl<T> GenEntity for T
where
    T: HasName
        + DefaultContent
        + super::display::ImplDisplay
        + super::constants::DefConstants
        + super::properties::DefProperties
        + super::getters::ImplGetters
        + implementation::ImplEntity,
{
    fn gen_entity(&self) -> m4::TokenStream {
        let entity = entity_name(self.name());
        let reader = reader_name(self.name());
        let default_size = usize_lit(self.default_content().len());
        let default_content = self
            .default_content()
            .into_iter()
            .map(|b| usize_lit(b as usize));
        let display_stmts = self.impl_display();
        let constants = self.def_constants();
        let properties = self.def_properties();
        let getters = self.impl_getters_for_entity();
        let implementation = self.impl_entity();
        quote!(
            #[derive(Clone)]
            pub struct #entity(molecule::bytes::Bytes);

            impl ::core::fmt::LowerHex for #entity {
                fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                    use molecule::hex_string;
                    if f.alternate() {
                        write!(f, "0x")?;
                    }
                    write!(f, "{}", hex_string(self.as_slice()))
                }
            }

            impl ::core::fmt::Debug for #entity {
                fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                    write!(f, "{}({:#x})", Self::NAME, self)
                }
            }

            impl ::core::fmt::Display for #entity {
                fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                    #display_stmts
                }
            }

            impl ::core::default::Default for #entity {
                fn default() -> Self {
                    let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE);
                    #entity::new_unchecked(v)
                }
            }

            impl #entity {
                const DEFAULT_VALUE: [u8; #default_size] = [#( #default_content, )*];
                #constants
                #properties
                #getters
                pub fn as_reader<'r>(&'r self) -> #reader<'r> {
                    #reader::new_unchecked(self.as_slice())
                }
            }

            #implementation
        )
    }
}
