use proc_macro2 as m4;
use quote::quote;

use super::utilities::func_name;
use crate::ast;

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

impl ImplDisplay for ast::Option_ {
    fn impl_display(&self) -> m4::TokenStream {
        quote!(if let Some(v) = self.to_opt() {
            write!(f, "{}(Some({}))", Self::NAME, v)
        } else {
            write!(f, "{}(None)", Self::NAME)
        })
    }
}

impl ImplDisplay for ast::Union {
    fn impl_display(&self) -> m4::TokenStream {
        quote!(
            write!(f, "{}(", Self::NAME)?;
            self.to_enum().display_inner(f)?;
            write!(f, ")")
        )
    }
}

impl ImplDisplay for ast::Array {
    fn impl_display(&self) -> m4::TokenStream {
        if self.item().typ().is_byte() {
            quote!(
                use molecule::hex_string;
                let raw_data = hex_string(&self.raw_data());
                write!(f, "{}(0x{})", Self::NAME, raw_data)
            )
        } else {
            let display_items = (0..self.item_count()).map(|idx| {
                let func = func_name(&format!("nth{}", idx));
                if idx == 0 {
                    quote!(write!(f, "{}", self.#func())?;)
                } else {
                    quote!(write!(f, ", {}", self.#func())?;)
                }
            });
            quote!(
                write!(f, "{} [", Self::NAME)?;
                #( #display_items )*
                write!(f, "]")
            )
        }
    }
}

impl ImplDisplay for ast::Struct {
    fn impl_display(&self) -> m4::TokenStream {
        let display_fields = self.fields().iter().enumerate().map(|(i, f)| {
            let field = f.name().to_owned();
            let func = func_name(f.name());
            if i == 0 {
                quote!(write!(f, "{}: {}", #field, self.#func())?;)
            } else {
                quote!(write!(f, ", {}: {}", #field, self.#func())?;)
            }
        });
        quote!(
            write!(f, "{} {{ ", Self::NAME)?;
            #( #display_fields )*
            write!(f, " }}")
        )
    }
}

impl ImplDisplay for ast::FixVec {
    fn impl_display(&self) -> m4::TokenStream {
        if self.item().typ().is_byte() {
            quote!(
                use molecule::hex_string;
                let raw_data = hex_string(&self.raw_data());
                write!(f, "{}(0x{})", Self::NAME, raw_data)
            )
        } else {
            quote!(
                write!(f, "{} [", Self::NAME)?;
                for i in 0..self.len() {
                    if i == 0 {
                        write!(f, "{}", self.get_unchecked(i))?;
                    } else {
                        write!(f, ", {}", self.get_unchecked(i))?;
                    }
                }
                write!(f, "]")
            )
        }
    }
}

impl ImplDisplay for ast::DynVec {
    fn impl_display(&self) -> m4::TokenStream {
        quote!(
            write!(f, "{} [", Self::NAME)?;
            for i in 0..self.len() {
                if i == 0 {
                    write!(f, "{}", self.get_unchecked(i))?;
                } else {
                    write!(f, ", {}", self.get_unchecked(i))?;
                }
            }
            write!(f, "]")
        )
    }
}

impl ImplDisplay for ast::Table {
    fn impl_display(&self) -> m4::TokenStream {
        let display_fields = self.fields().iter().enumerate().map(|(i, f)| {
            let field = f.name().to_owned();
            let func = func_name(f.name());
            if i == 0 {
                quote!(write!(f, "{}: {}", #field, self.#func())?;)
            } else {
                quote!(write!(f, ", {}: {}", #field, self.#func())?;)
            }
        });
        let display_unresolved = if self.fields().is_empty() {
            quote!(write!(f, ".. ({} fields)", extra_count)?;)
        } else {
            quote!(write!(f, ", .. ({} fields)", extra_count)?;)
        };
        quote!(
            write!(f, "{} {{ ", Self::NAME)?;
            #( #display_fields )*
            let extra_count = self.count_extra_fields();
            if extra_count != 0 {
                #display_unresolved
            }
            write!(f, " }}")
        )
    }
}
