Documentation
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;

fn access_struct_inner(
    data: &syn::DataStruct,
) -> Result<(proc_macro2::TokenStream, &syn::Type, bool), proc_macro2::TokenStream> {
    if data.fields.len() != 1 {
        return Err(quote! {"Struct must have a single field to derive Deref"});
    }
    let field = data.fields.iter().next().unwrap();
    let inner_name = &field
        .ident
        .as_ref()
        .map(|n| quote! { #n })
        .unwrap_or(quote! { 0 });

    // Identical to the above but with support for unit struct
    // Can you even Deref as () ???
    // let inner_access = match data.fields {
    //     syn::Fields::Named(field) => {let},
    //     syn::Fields::Unnamed(field) => quote! { #field.0 },
    //     syn::Fields::Unit => return Err(quote! {"Struct unit field not supported yet"}),
    // };

    let (ty, add_ref) = match &field.ty {
        syn::Type::Reference(ty) => (ty.elem.as_ref(), false),
        _ => (&field.ty, true),
    };

    Ok((
        quote! {
            self.#inner_name
        },
        ty,
        add_ref,
    ))
}

fn access_enum_inner(
    data: &syn::DataEnum,
) -> Result<(proc_macro2::TokenStream, &syn::Type, bool), proc_macro2::TokenStream> {
    if data.variants.len() != 1 {
        return Err(quote! {"Enum must have a single variant to derive Deref"});
    }
    let variant = data.variants.iter().next().unwrap();
    let v_ident = &variant.ident;

    if variant.fields.len() != 1 {
        return Err(quote! {"Enum variant must have a single field to derive Deref"});
    }

    let field = variant.fields.iter().next().unwrap();
    let _inner_name = &field.ident;

    let (ty, add_ref) = match &field.ty {
        syn::Type::Reference(ty) => (ty.elem.as_ref(), false),
        _ => (&field.ty, false),
    };

    Ok((
        quote! {
            match self {
                Self::#v_ident(k) => k
            }
        },
        ty,
        add_ref,
    ))
}

fn access_inner(
    data: &syn::Data,
) -> Result<(proc_macro2::TokenStream, &syn::Type, bool), proc_macro2::TokenStream> {
    match data {
        syn::Data::Struct(data) => access_struct_inner(data),
        syn::Data::Enum(data) => access_enum_inner(data),
        _ => Err(quote! {"Only structs and enums can derive Deref"}),
    }
}

#[proc_macro_derive(Deref)]
pub fn deref(item: TokenStream) -> TokenStream {
    // Parse the macro input
    let input = syn::parse_macro_input!(item as syn::DeriveInput);

    // Access the inner type
    let (access_inner, ty, add_ref) = match access_inner(&input.data) {
        Ok(inner) => inner,
        Err(err) => return quote! { compile_error!(#err) }.into(),
    };

    let name = &input.ident;
    let gens = &input.generics;

    let (target_ref, inner_ref) = if add_ref {
        (quote! {}, quote! { & })
    } else {
        (quote! {}, quote! {})
    };

    let derived = quote! {
        impl #gens std::ops::Deref for #name #gens {
            type Target = #target_ref #ty;
            fn deref(&self) -> &Self::Target {
                #inner_ref #access_inner
            }
        }
    };
    derived.into()
}

#[proc_macro_derive(DerefMut)]
pub fn deref_mut(item: TokenStream) -> TokenStream {
    // Parse the macro input
    let input = syn::parse_macro_input!(item as syn::DeriveInput);

    // Access the inner type
    let (access_inner, _, add_ref) = match access_inner(&input.data) {
        Ok(inner) => inner,
        Err(err) => return quote! { compile_error!(#err) }.into(),
    };

    let inner_ref = if add_ref {
        quote! { &mut }
    } else {
        quote! {}
    };

    let name = &input.ident;
    let gens = &input.generics;

    let derived = quote! {
        impl #gens std::ops::DerefMut for #name #gens {
            fn deref_mut(&mut self) -> &mut Self::Target {
                #inner_ref #access_inner
            }
        }
    };
    derived.into()
}