//! Derive macro for automatically implementing `jaded::FromJava` for Rust types.
//!
//! This is provided as a `FromJava` macro.
//!
//! Most implementations of `FromJava` will be very similar and automatically deriving them makes
//! much more sense than writing almost identical impl blocks for every type.
//!
//! For a 'simple' bean style Java class, eg
//!
//! ```java
//! public class Person implements Serializable {
//!     private String firstName;
//!     private String familyName;
//!     private int age;
//! }
//! ```
//! The rust equivalent can be automatically derived
//! ```
//! # use ::jaded_derive::FromJava;
//! #[derive(FromJava)]
//! struct Person {
//!     firstName: String,
//!     familyName: String,
//!     age: i32,
//! }
//! ```
//!
//! To allow both Java and rust to keep to their respective naming conventions,
//! a 'rename' feature is available that allows camelCase java fields to be
//! read into snake_case rust fields
//!
//! ```
//! # use ::jaded_derive::FromJava;
//! #[derive(FromJava)]
//! #[jaded(rename)]
//! struct Person {
//!     first_name: String,
//!     family_name: String,
//!     age: i32,
//! }
//! ```
//!
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro_error::{diagnostic, proc_macro_error, Diagnostic, Level, ResultExt};
use syn::{parse2, DeriveInput};

mod attributes;
mod internal;
mod model;

use model::Reader;
type Result<T> = std::result::Result<T, Diagnostic>;

/// # Derive FromJava for custom Rust types
///
/// By default each field is read from a field of the same name in the serialized
/// Java object. In this simple form, all fields must also implement `FromJava`
/// (similar to `derive(Debug)` requiring fields to implement `Debug`).
///
/// To modify the standard behaviour a `jaded` attribute is available
/// ## On structs
/// ### `#[jaded(rename)]`
/// Convert all fields to camelCase before looking them up in the Java object.
/// This allows a field `first_name` to be read from a Java field `firstName`.
///
/// NB. This requires the `renaming` feature as it requires additional
/// dependencies.
/// ### `#[jaded(class = "com.example.Demo")]`
/// By default, Jaded will try to read from the given fields and use them if
/// they're present without first checking the class of the object. This means
/// a class `Foo` with a single `String name` field will be treated exactly
/// the same as a class `Bar` that also has a single `String name` field.
/// This attribute allows a specific class to be given which will be checked
/// at conversion time.
///
/// ## On Fields
/// ### `#[jaded(field = "fieldName")]`
/// Customise the name of the Java field being read.
/// By default each rust field is read from a Java field of the same name. While
/// this is usually fine, it can be useful in some situations to supply a more
/// appropriate name. This allows code to better match domain language or to
/// avoid keyword clashes (eg type is a valid Java field name but can't be used
/// in Rust).
/// This could also be used to rename fields to match naming conventions if
/// the struct level `rename` attribute is not used.
///
/// ### `#[jaded(extract(method_name))]`
/// Serializable classes in Java can customise the way the builtin Serializer
/// writes their state to the output stream. As there is no standard for how this
/// may look, the custom data is written to an annotations field in the
/// intermediate representation and the field name is not available.
/// This data can be read using an interface similar to Java's `ObjectInputStream`
/// but the specifics must be user implemented and can't be derived.
/// The `extract` attribute allows a user method to be used to read a field's
/// value from the custom data stream.
///
/// The value given to the `extract` attribute must be a valid path to a function
/// with the signature:
/// `(&mut AnnotationIter) -> ConversionResult<T> where T: Into<F>`
/// and `F` is the type of the rust field being read.
///
/// By default, the `AnnotationIter` instance passed to the method is the stream
/// written by the concrete class being read. In rare situations, a parent class
/// may also write their own custom data and these can be read by specifying
/// which class in the hierarchy to use where `0` (the default) is the first class
/// in the hierarchy (top down) to include annotations, and 1 is the first subclass
/// of it to include annotations etc.
/// This is passed after the path to the method:
/// `#[jaded(extract(method_name, 1))]`.
///
/// NB. Fields that read from the annotation stream are all passed the same
///     instance of the `AnnotationIter`. This means that later fields do
///     not have to 'fast-forward' through the previous field's data but does
///     mean that the order fields are declared in is important.
/// NB. Fields read via a custom extract method do not have to implement `FromJava`.
#[proc_macro_derive(FromJava, attributes(jaded))]
#[proc_macro_error]
pub fn from_java(input: TokenStream) -> TokenStream {
    check_features();

    let input = parse(input.into()).unwrap_or_abort();
    let model = Reader::read_model(input).unwrap_or_abort();
    model.to_tokens().into()
}

/// Previous versions had a renaming feature to enable bulk camelcass conversion.
/// This is now provided by default so warn if users are still using it.
fn check_features() {
    #[cfg(feature = "renaming")]
    ::proc_macro_error::emit_warning!(
        proc_macro2::Span::call_site(),
        "Renaming feature has been deprecated. Functionality is available with no features enabled"
    );
}

/// Convert the original token stream into a DeriveInput instance.
fn parse(input: TokenStream2) -> Result<DeriveInput> {
    parse2::<DeriveInput>(input).map_err(|e| {
        diagnostic!(
            e.span(),
            Level::Error,
            "Only structs or enums can have FromJava derived",
        )
    })
}
