use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::HashSet;
use syn::{Data, DataStruct, DeriveInput, Fields, Result};

use crate::attributes::{ClassData, FieldData};

pub(crate) fn expand_from_value(input: DeriveInput) -> syn::Result<TokenStream> {
    let class_data = ClassData::from_input(&input)?;
    let fields = match input.data {
        Data::Struct(DataStruct {
            fields: Fields::Named(fields),
            ..
        }) => fields.named,
        _ => panic!("Only named field structs for now"),
    };

    let mut need_anno = HashSet::new();
    let java_fields = fields
        .into_iter()
        .map(|f| {
            let name = f.ident.clone().expect("Named field");
            let data = FieldData::from_input(&f)?;
            Ok(match data.extract_method {
                Some(a) => {
                    let anno = data.annotation_number.unwrap_or(0);
                    need_anno.insert(anno);
                    let anno_name = format_ident!("anno_{}", anno);
                    quote!(#name: #a(&mut #anno_name)?)
                }
                None => {
                    let field_name = match data.field {
                        Some(other_name) => other_name,
                        None => class_data.field_name_for(&name),
                    };
                    quote!(#name: data.get_field_as(#field_name)?)
                }
            })
        })
        .collect::<Result<Vec<TokenStream>>>()?;

    let anno = need_anno.iter().map(|a| {
        let name = format_ident!("anno_{}", a);
        quote!(let mut #name = data.get_annotation(#a).ok_or(jaded::ConversionError::MissingAnnotations)?;)
    }).collect::<TokenStream>();

    // If given, check the class of the java object is as expected
    let class_check = class_data.class_check();

    let st_name = input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    Ok(quote! {
        #[automatically_derived]
        impl #impl_generics jaded::FromJava for #st_name #ty_generics #where_clause {
            fn from_value(value: &jaded::Value) -> jaded::ConversionResult<Self> {
                match value {
                    jaded::Value::Object(data) => {
                        #class_check
                        #anno
                        Ok(#st_name { #(#java_fields),* })
                    },
                    jaded::Value::Null => Err(jaded::ConversionError::NullPointerException),
                    _ => Err(jaded::ConversionError::InvalidType("Object")),
                }
            }
        }
    })
}
