use proc_macro2 as m4;
use quote::quote;

use super::super::utilities::{builder_name, entity_name, field_name, func_name, reader_name};
use crate::ast::{self as ast, HasName};

pub(in super::super) trait ImplEntity: HasName {
    fn impl_entity_internal(&self) -> m4::TokenStream;

    fn impl_entity(&self) -> m4::TokenStream {
        let entity = entity_name(self.name());
        let entity_string = entity.to_string();
        let reader = reader_name(self.name());
        let builder = builder_name(self.name());
        let internal = self.impl_entity_internal();
        quote!(
            impl molecule::prelude::Entity for #entity {
                type Builder = #builder;
                const NAME: &'static str = #entity_string;
                fn new_unchecked(data: molecule::bytes::Bytes) -> Self {
                    #entity(data)
                }
                fn as_bytes(&self) -> molecule::bytes::Bytes {
                    self.0.clone()
                }
                fn as_slice(&self) -> &[u8] {
                    &self.0[..]
                }
                fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult<Self> {
                    #reader::from_slice(slice).map(|reader| reader.to_entity())
                }
                fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult<Self> {
                    #reader::from_compatible_slice(slice).map(|reader| reader.to_entity())
                }
                fn new_builder() -> Self::Builder {
                    ::core::default::Default::default()
                }
                #internal
            }
        )
    }
}

impl ImplEntity for ast::Option_ {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder().set(self.to_opt())
            }
        )
    }
}

impl ImplEntity for ast::Union {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder().set(self.to_enum())
            }
        )
    }
}

impl ImplEntity for ast::Array {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        let items = (0..self.item_count())
            .map(|idx| func_name(&format!("nth{}", idx)))
            .map(|func| quote!(self.#func()));
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder().set([ #( #items, )* ])
            }
        )
    }
}

impl ImplEntity for ast::Struct {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        let fields = self.fields().iter().map(|f| field_name(f.name()));
        let fields_func = fields.clone();
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder()
                    #( .#fields(self.#fields_func()) )*
            }
        )
    }
}

impl ImplEntity for ast::FixVec {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder().extend(self.into_iter())
            }
        )
    }
}

impl ImplEntity for ast::DynVec {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder().extend(self.into_iter())
            }
        )
    }
}

impl ImplEntity for ast::Table {
    fn impl_entity_internal(&self) -> m4::TokenStream {
        let fields = self.fields().iter().map(|f| field_name(f.name()));
        let fields_func = fields.clone();
        quote!(
            fn as_builder(self) -> Self::Builder {
                Self::new_builder()
                    #( .#fields(self.#fields_func()) )*
            }
        )
    }
}
