//! Provides the `#[trace_logging_provider]` macro, which allows you to define a
//! [Trace Logging Provider](https://docs.microsoft.com/en-us/windows/win32/etw/about-event-tracing#providers)
//! for use with the [Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/etw/event-tracing-portal)
//! framework.
//!
//! This macro is intended for use only when targeting Windows. When targeting other platforms,
//! this macro will still work, but will generate code that does nothing.
//!
//! This framework allows applications to log schematized events, rather than textual strings.
//! ETW analysis tools can reliably identify fields within your events, and treat them as
//! strongly-typed data, rather than text strings.
//!
//! # How to create and use an event provider
//!
//! In ETW, an _event provider_ is a software object that generates events. _Event controllers_
//! set up event logging sessions, and _event consumers_ read and interpret event data. This crate
//! focuses on enabling applications to create _event providers_.
//!
//! ## Add crate dependencies
//! Add these dependencies to your `Cargo.toml` file:
//!
//! ```text
//! [dependencies]
//! win_etw_macros = "0.1.*"
//! win_etw_provider = "0.1.*"
//! ```
//!
//! `win_etw_macros` contains the procedural macro that generates eventing code.
//! `win_etw_provider` contains library code that is called by the code that is generated by
//! `win_etw_macros`.
//!
//! ## Create a new GUID for your event provider
//! You _must_ assign a new, unique GUID to each event provider. ETW uses this GUID to identify your
//! events that are generated by your provider. Windows contains many event providers, so it is
//! important to be able to select only the events generated by your application. This GUID is also
//! used internally by ETW to identify event metadata (field types), so it is important that your
//! GUID be unique. Otherwise, events from conflicting sources that use the same GUID may be
//! incorrectly interpreted.
//!
//! There are many tools which can create a GUID, such as:
//!
//! * In Visual Studio, in the Tools menu, select "Create GUID".
//! * From a Visual Studio command line, run `uuidgen.exe`.
//! * From an Ubuntu shell, run `uuidgen`.
//!
//! ## Define the event provider and its events
//! Add a trait definition to your source code and annotate it with the
//! `#[trace_logging_provider(guid = "...")]` macro, using the GUID that you just created. The trait
//! definition is only used as input to the procedural macro; the trait is not emitted into your
//! crate, and cannot be used as a normal trait.
//!
//! In the trait definition, add method signatures. Each method signature defines an _event type_.
//! The parameters of each method define the fields of the event type. Only a limited set of field
//! types are supported (enumerated below).
//!
//! The `#[trace_logging_provider]` macro consumes the trait definition and produces a `struct`
//! definition with the same name and the same method signatures. (The trait is _not_ available for
//! use as an ordinary trait.)
//!
//! ```ignore
//! # use win_etw_macros::trace_logging_provider;
//! #[trace_logging_provider(guid = "... your guid here ...")]
//! pub trait MyAppEvents {
//!     fn http_request(client_address: &SockAddr, is_https: bool, status_code: u32, status: &str);
//!     fn database_connection_created(connection_id: u64, server: &str);
//!     fn database_connection_closed(connection_id: u64);
//!     // ...
//! }
//! ```
//!
//! ## Create an instance of the event provider
//! At initialization time (in your `fn main()`, etc.), create an instance of the event provider:
//!
//! ```ignore
//! let my_app_events = MyAppEvents::new();
//! ```
//!
//! Your application should only create a single instance of each event provider, per process.
//! That is, you should create a single instance of your event provider and share it across your
//! process. Typically, an instance is stored in static variable, using a lazy / atomic assignment.
//! There are many crates and types which can support this usage pattern.
//!
//! ## Call event methods to report events
//! To report an event, call one of the methods defined on the event provider. The method will
//! call into ETW to report the event, but there is no guarantee that the event is stored or
//! forwarded; events can be dropped if event buffer resources are scarce.
//!
//! ```ignore
//! my_app_events.client_connected(&"192.168.0.42:6667".parse(), false, 100, "OK");
//! ```
//!
//! # Supported field types
//! Only a limited set of field types are supported.
//!
//! * Integer primitives up to 64 bits: `i8`, `i16`, `i32`, `i64`, `u8`, `u16`, `u32`, `u64`
//! * Floating point primitives: `f32`, `f64`
//! * Architecture-dependent sizes: `usize`, `isize`.
//! * Boolean: `bool`
//! * Slices of all of the supported primitives, except for bool: `&[u8]`, `&[u16]`, etc.
//!   `&[bool]` is not supported because `bool` does not have a guaranteed stable representation.
//! * Windows `[FILETIME](https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime)`.
//!   The type must be declared _exactly_ as `FILETIME`; type aliases or fully-qualified paths
//!   (such as `winapi::shared::minwindef::FILETIME`) _will not work_. The parameter type in the
//!   generated code will be `win_etw_provider::FILETIME`, which is a newtype over `u64`.
//! * `std::time::SystemTime` is supported, but it must be declared _exactly_ as `SystemTime`;
//!   type aliases or fully-qualified paths (such as `std::time::SystemTime`) _will not work_.
//! * `SockAddr`, `SockAddrV4`, and `SockAddrV6` are supported. They must be declared exactly as
//!   shown, not using fully-qualified names or type aliases.
//!
//! # How to capture and view events
//!
//! There are a variety of tools which can be used to capture and view ETW events.
//! The simplest tool is the `TraceView` tool from the Windows SDK. Typically it is installed
//! at this path: `C:\Program Files (x86)\Windows Kits\10\bin\10.0.<xxxxx>.0\x64\traceview.exe`,
//! where `<xxxxx>` is the release number of the Windows SDK.
//!
//! Run `TraceView`, then select "File", then "Create New Log Session". Select "Manually Entered
//! GUID or Hashed Name" and enter the GUID that you have assigned to your event provider. Click OK.
//! The next dialog will prompt you to choose a source of WPP format information; select Auto
//! and click OK.
//!
//! At this point, `TraceView` should be capturing events (for your assigned GUID) and displaying
//! them in real time, regardless of which process reported the events.
//!
//! These tools can also be used to capture ETW events:
//! * [Windows Performance Recorder](https://docs.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-recorder)
//!   This tool is intended for capturing system-wide event streams. It is not useful for capturing
//!   events for a specific event provider.
//! * [logman](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/logman)
//!   is a command-line tool for managing events.
//! * [Tracelog](https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/tracelog)
//!
//! There are other tools, such as the Windows Performance Recorder, which can capture ETW events.
//!
//! # References
//! * [Event Tracing for Windows (ETW) Simplified](https://support.microsoft.com/en-us/help/2593157/event-tracing-for-windows-etw-simplified)
//! * [TraceLogging for Event Tracing for Windows (ETW)](https://docs.microsoft.com/en-us/windows/win32/tracelogging/trace-logging-portal)
//! * [Record and View TraceLogging Events](https://docs.microsoft.com/en-us/windows/win32/tracelogging/tracelogging-record-and-display-tracelogging-events)

// https://doc.rust-lang.org/reference/procedural-macros.html

#![deny(missing_docs)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::cognitive_complexity)]
#![allow(clippy::single_match)]

extern crate proc_macro;

mod errors;
mod well_known_types;

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use std::iter::Extend;
use syn::spanned::Spanned;
use syn::{parse_quote, Error, Expr, ExprLit, FnArg, Ident, Lit, Token};
use uuid::Uuid;
use well_known_types::{WellKnownType, WellKnownTypes};

#[cfg(test)]
mod tests;

/// Allows you to create ETW Trace Logging Providers. See the module docs for more detailed
/// instructions for this macro.
#[proc_macro_attribute]
pub fn trace_logging_provider(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    // let logging_trait = parse_macro_input!(input as syn::ItemTrait);
    let output = trace_logging_events_core(attr.into(), input.into());
    output.into()
}

fn trace_logging_events_core(attr: TokenStream, item_tokens: TokenStream) -> TokenStream {
    let mut errors: Vec<Error> = Vec::new();

    let logging_trait: syn::ItemTrait = match syn::parse2(item_tokens) {
        Err(e) => {
            return e.to_compile_error();
        }
        Ok(syn::Item::Trait(t)) => t,
        Ok(syn::Item::Mod(m)) => {
            return Error::new_spanned(&m, "Modules are not yet supported, but thanks for asking.")
                .to_compile_error();
        }
        Ok(unrecognized) => {
            return Error::new_spanned(
                &unrecognized,
                "The #[trace_logging_provider] attribute cannot be used with this kind of item.",
            )
            .to_compile_error();
        }
    };

    let provider_attrs: ProviderAttributes = match syn::parse2(attr) {
        Ok(p) => p,
        Err(e) => {
            errors.push(e);
            ProviderAttributes::default()
        }
    };

    // provider_ident is the identifier used in Rust source code for the generated provider type.
    let provider_ident = &logging_trait.ident;
    let provider_ident_string = provider_ident.to_string();

    let wk = WellKnownTypes::new();

    let mut output = TokenStream::new();

    // provider_mod_ident is a module that we generate that contains implementation details.
    // It is not intended to be used directly by applications.
    let provider_mod_ident: Ident = ident_suffix(provider_ident, "implementation");

    let mut event_descriptors: Vec<syn::Item> = Vec::new();

    let provider_metadata_ident = Ident::new(
        &format!("{}_PROVIDER_METADATA", provider_ident_string),
        provider_ident.span(),
    );

    // Create the provider metadata.
    // provider_ident is the identifier used in Rust source code for the generated code.
    // When writing the provider metadata, we allow the user to specify a different name to ETW.
    {
        let provider_name = provider_attrs
            .provider_name
            .as_ref()
            .unwrap_or(&provider_ident_string);
        output.extend(create_provider_metadata(
            &provider_name,
            &provider_metadata_ident,
        ));
    }

    // Definitions that go inside the "impl MyProvider { ... }" block.
    let mut provider_impl_items = TokenStream::new();

    for (method_index, method) in logging_trait
        .items
        .iter()
        .filter_map(|item| {
            if let syn::TraitItem::Method(m) = item {
                Some(m)
            } else {
                None
            }
        })
        .enumerate()
    {
        let event_id = method_index as u16;

        // Check requirements for the method signature. If the requirements are not met, we
        // emit an error but keep going. This allows us to report as many errors as possible in
        // each build, rather than having errors "unlocked" one by one.
        if method.sig.asyncness.is_some() {
            errors.push(Error::new_spanned(
                method,
                "Async event methods are not supported.",
            ));
        }
        if method.sig.unsafety.is_some() {
            errors.push(Error::new_spanned(
                method,
                "Event methods should not be marked unsafe.",
            ));
        }
        if !method.sig.generics.params.is_empty() {
            errors.push(Error::new_spanned(
                method,
                "Generic event methods are not supported.",
            ));
        }
        match &method.sig.output {
            syn::ReturnType::Default => {}
            _ => {
                errors.push(Error::new_spanned(
                    method,
                    "Event methods must not return data.",
                ));
            }
        }
        if let Some(block) = method.default.as_ref() {
            errors.push(Error::new_spanned(
                block,
                "Event methods must not contain an implementation.",
            ));
        }

        let event_ident = &method.sig.ident;
        let event_name: String = method.sig.ident.to_string();

        // Here we build the data descriptor array. The data descriptor array is constructed on
        // the stack, and has a statically-known size. It contains pointers to data fields. The
        // event metadata describes the order and type of the data pointed-to by the data
        // descriptors.
        //
        // For self-describing events (TraceLogging), the first two items in the data descriptor
        // array identify the provider metadata and the event metadata.
        let mut data_descriptor_array = TokenStream::new();
        data_descriptor_array.extend(quote! {});

        // See comments in traceloggingprovider.h, around line 2300, which describe the
        // encoding of the event mdata.
        let mut event_metadata: Vec<Expr> = Vec::new();
        event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 0)
        event_metadata.push(parse_quote! { 0 }); // reserve space for the size (byte 1)
        event_metadata.push(parse_quote! { 0 }); // no extensions
        append_utf8_str_chars(&mut event_metadata, &event_name);

        // Some fields require running some code before building the data descriptors, so we
        // collect statements here.
        let mut statements = TokenStream::new();

        // Each parameter (except for &self) becomes an event field.
        let mut found_receiver = false;

        // sig is the function signature for the function that we will generate for this event.
        let mut sig = method.sig.clone();

        for param in sig.inputs.iter_mut() {
            let param_span = param.span();
            match param {
                FnArg::Receiver(_) => {
                    errors.push(Error::new_spanned(param, "Event methods should not provide any receiver arguments (&self, &mut self, etc.)."));
                    found_receiver = true;
                }

                FnArg::Typed(param_typed) => {
                    let mut event_attr: Option<syn::Attribute> = None;
                    param_typed.attrs.retain(|a| {
                        if a.path == parse_quote!(event) {
                            event_attr = Some(a.clone());
                            false
                        } else if a.path == parse_quote!(doc) {
                            true
                        } else {
                            errors.push(Error::new_spanned(
                                a,
                                "This attribute is not permitted on event fields.",
                            ));
                            true
                        }
                    });
                    let param_name: &Ident = match &*param_typed.pat {
                        syn::Pat::Ident(ref name) => &name.ident,
                        _ => {
                            errors.push(Error::new(
                                param.span(),
                                "Only ordinary parameter patterns are supported on event methods.",
                            ));
                            continue;
                        }
                    };

                    if parse_event_field(
                        &mut errors,
                        &wk,
                        event_attr.as_ref(),
                        param_span,
                        &param_name,
                        &mut *param_typed.ty,
                        &mut data_descriptor_array,
                        &mut event_metadata,
                        &mut statements,
                    )
                    .is_err()
                    {
                        errors.push(Error::new_spanned(
                            &param,
                            "This type is not supported for event parameters.",
                        ));
                    }
                }
            }
        }

        // We require that every function declare a '&self' receiver parameter.
        if !found_receiver {
            sig.inputs.insert(0, parse_quote!(&self));
        }

        // Insert the "options" argument.
        sig.inputs.insert(
            1,
            parse_quote!(options: core::option::Option<&::win_etw_provider::EventOptions>),
        );

        // Now that we have processed all parameters ("fields"), we can finish constructing
        // the per-event metadata.
        let event_metadata_len = event_metadata.len();
        if event_metadata_len > 0xffff {
            errors.push(Error::new(
                method.span(),
                "Event metadata is too large to encode; reduce the complexity of this event.",
            ));
            continue;
        }
        let event_metadata_len_b0 = (event_metadata_len & 0xff) as u8;
        let event_metadata_len_b1 = (event_metadata_len >> 8) as u8;
        event_metadata[0] = parse_quote! { #event_metadata_len_b0 };
        event_metadata[1] = parse_quote! { #event_metadata_len_b1 };

        let event_attrs = parse_event_attributes(&mut errors, &method.sig.ident, &method.attrs);

        // Generate the event descriptor for this event.
        // This is a static variable. The name is exactly the name of the event.
        let event_level = event_attrs.level;
        let event_opcode = event_attrs.opcode;
        let event_task = event_attrs.task;
        event_descriptors.push(parse_quote!{
            pub(crate) static #event_ident: ::win_etw_provider::EventDescriptor = ::win_etw_provider::EventDescriptor {
                id: #event_id,
                version: 0,
                channel: 11,
                level: #event_level,
                opcode: #event_opcode,
                task: #event_task,
                keyword: 0,
            };
        });

        let event_attrs_method_attrs = &event_attrs.method_attrs;

        // Generate the `${name}_is_enabled` function for this event.
        // We do not use ident_suffix() because this is not a private identifier.
        let event_is_enabled_name = Ident::new(
            &format!("{}_is_enabled", method.sig.ident.to_string()),
            method.sig.ident.span(),
        );

        // Build the method that implements this event.
        provider_impl_items.extend(quote!{
            #( #event_attrs_method_attrs )*
            #[cfg_attr(not(target_os = "windows"), allow(unused_variables))]
            pub #sig
            {
                #[cfg(target_os = "windows")]
                {
                    use ::win_etw_provider::EventDataDescriptor;

                    // This places the EVENT_METADATA into a read-only linker section, properly
                    // ordered with respect to TRACE_LOGGING_METADATA and other related sections.
                    #[link_section = ".rdata$etw1"]
                    #[used]
                    static EVENT_METADATA: [u8; #event_metadata_len] = [ #( #event_metadata, )* ];

                    let mut event_descriptor: ::win_etw_provider::EventDescriptor = ::win_etw_provider::EventDescriptor {
                        id: #event_id,
                        version: 0,
                        channel: 11,
                        level: #event_level,
                        opcode: #event_opcode,
                        task: #event_task,
                        keyword: 0,
                    };

                    if let Some(opts) = options {
                        if let Some(level) = opts.level {
                            event_descriptor.level = level;
                        }
                    }

                    #statements

                    let data_descriptors = [
                        EventDataDescriptor::for_provider_metadata(&#provider_metadata_ident[..]),
                        EventDataDescriptor::for_event_metadata(&EVENT_METADATA[..]),
                        #data_descriptor_array
                    ];
                    ::win_etw_provider::Provider::write(&self.provider,
                        options,
                        &event_descriptor,
                        &data_descriptors,
                    );
                }
            }

            pub fn #event_is_enabled_name(&self, level: ::core::option::Option<::win_etw_provider::Level>) -> bool {
                #[cfg(target_os = "windows")]
                {
                    let mut event_descriptor: ::win_etw_provider::EventDescriptor = ::win_etw_provider::EventDescriptor {
                        id: #event_id,
                        version: 0,
                        channel: 11,
                        level: level.unwrap_or(#event_level),
                        opcode: #event_opcode,
                        task: #event_task,
                        keyword: 0,
                    };

                    ::win_etw_provider::Provider::is_event_enabled(
                        &self.provider,
                        &event_descriptor)
                }
                #[cfg(not(target_os = "windows"))]
                {
                    false
                }
            }
        });
    }

    // We propagate the visibility of the trait definition to the structure definition.
    let vis = logging_trait.vis.clone();
    let provider_guid_const = uuid_to_expr(&provider_attrs.uuid);

    // If the input item has doc attributes, then carry them over to the output type.
    let doc_path: syn::Path = parse_quote!(doc);
    let provider_attrs = logging_trait
        .attrs
        .iter()
        .filter(|a| a.path == doc_path)
        .collect::<Vec<_>>();

    output.extend(quote! {
        #( #provider_attrs )*
        #vis struct #provider_ident {
            provider: ::core::option::Option<::win_etw_provider::EtwProvider>,
        }

        impl #provider_ident {
            /// Creates (registers) a new instance of this provider. If registration fails,
            /// returns a "null" provider. This prevents problems with event logging from
            /// disrupting the normal operation of applications.
            ///
            /// On non-Windows platforms, this function always returns a null provider.
            ///
            /// Creating an event source is a costly operation, because it requires contacting the
            /// event manager, allocating event buffers, potentially receiving callbacks from
            /// event consumers, etc. Applications should only create event sources during process
            /// initialization, and should always reuse them, never re-creating them.
            pub fn new() -> Self {
                let provider = match ::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID) {
                    Ok(mut provider) => {
                        #[cfg(target_os = "windows")]
                        {
                            let _ = provider.register_provider_metadata(&#provider_metadata_ident);
                        }
                        Some(provider)
                    }
                    Err(_) => None,
                };
                Self { provider }
            }

            /// Creates (registers) a new instance of this provider. If registration fails, then
            /// this method returns a "null" provider.
            ///
            /// On non-Windows platforms, this function always returns `Ok`, containing a null
            /// provider.
            ///
            /// Creating an event source is a costly operation, because it requires contacting the
            /// event manager, allocating event buffers, potentially receiving callbacks from
            /// event consumers, etc. Applications should only create event sources during process
            /// initialization, and should always reuse them, never re-creating them.
            pub fn new_err() -> ::core::result::Result<Self, ::win_etw_provider::Error> {
                Ok(Self {
                    provider: Some(::win_etw_provider::EtwProvider::new(&Self::PROVIDER_GUID)?),
                })
            }

            /// Creates a new "null" instance of the provider. All events written to this provider
            /// are discarded.
            pub fn null() -> Self {
                Self { provider: None }
            }

            #[allow(unused_variable)]
            pub const PROVIDER_GUID: ::win_etw_provider::GUID = #provider_guid_const;
        }

        // We intentionally generate identifiers that are not snake-case.
        #[allow(non_snake_case)]
        impl #provider_ident {
            #provider_impl_items
        }

        #[doc(hidden)]
        #[allow(non_snake_case)]
        mod #provider_mod_ident {
            /*
            pub(crate) mod event_descriptors {
                #![allow(non_upper_case_globals)]
                #( #event_descriptors )*
            }
            */
        }
    });

    output.extend(errors.into_iter().map(|e| e.to_compile_error()));
    output
}

fn err_spanned<T: quote::ToTokens>(item: &T, msg: &str) -> TokenStream {
    Error::new_spanned(item, msg).to_compile_error()
}

// `provider_name` is not necessarily the same as the identifier used in source code.
// It can be overridden using `#[trace_logging_provider(name = "some_name_here")]`.
fn create_provider_metadata(provider_name: &str, provider_metadata_ident: &Ident) -> TokenStream {
    let mut provider_metadata: Vec<u8> = Vec::new();

    let provider_metadata_len = 2 + provider_name.len() + 1;
    if provider_metadata_len > 0xffff {
        return err_spanned(&provider_name, "The provider name is too long.");
    }
    provider_metadata.push((provider_metadata_len & 0xff) as u8);
    provider_metadata.push((provider_metadata_len >> 8) as u8);
    provider_metadata.extend_from_slice(provider_name.as_bytes());
    provider_metadata.push(0);

    quote! {
        #[link_section = ".rdata$etw2"]
        #[used]
        #[allow(non_upper_case_globals)]
        #[cfg(target_os = "windows")]
        static #provider_metadata_ident: [u8; #provider_metadata_len] = [
            #(
                #provider_metadata,
            )*
        ];
    }
}

fn append_utf8_str_chars(output: &mut Vec<Expr>, s: &str) {
    for &b in s.as_bytes().iter() {
        output.push(parse_quote! { #b });
    }
    // Add the NUL byte at the end.
    output.push(parse_quote! { 0 });
}

struct UnsupportedField;

/// Parses one event field. Event fields are declared as function parameters.
///
/// * `event_metadata`: This builds the static [u8; N] array that contains the metadata for this
///   event. It can contain literals, symbolic expressions, etc.
fn parse_event_field(
    errors: &mut Vec<Error>,
    well_known_types: &WellKnownTypes,
    event_attr: Option<&syn::Attribute>,
    field_span: proc_macro2::Span,
    field_name: &Ident,
    field_ty: &mut syn::Type,
    data_descriptor_array: &mut TokenStream,
    event_metadata: &mut Vec<syn::Expr>,
    statements: &mut TokenStream,
) -> Result<(), UnsupportedField> {
    // Write the field metadata.
    // // FieldMetadata:
    // struct FieldMetadata // Variable-length pseudo-structure, byte-aligned, tightly-packed.
    // {
    //     char Name[]; // UTF-8 nul-terminated field name
    //     UINT8 InType; // Values from the TlgIn enumeration.
    //     UINT8 OutType; // TlgOut enumeration. Only present if (InType & 128) == 128.
    //     UINT8 Extension[]; // Only present if OutType is present and (OutType & 128) == 128. Read until you hit a byte with high bit unset.
    //     UINT16 ValueCount;  // Only present if (InType & CountMask) == Ccount.
    //     UINT16 TypeInfoSize; // Only present if (InType & CountMask) == Custom.
    //     char TypeInfo[TypeInfoSize]; // Only present if (InType & CountMask) == Custom.
    // };

    let param_name_string = field_name.to_string();
    append_utf8_str_chars(event_metadata, &param_name_string);
    // We will append more data to event_metadata, below.

    // The user can annotate fields with #[event(...)] in order to specify output formats.
    let mut output_hex = false;
    if let Some(event_attr) = event_attr {
        match event_attr.parse_meta() {
            Ok(syn::Meta::List(list)) => {
                for item in list.nested.iter() {
                    match item {
                        syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
                            path,
                            lit,
                            ..
                        })) => {
                            if *path == parse_quote!(output) {
                                match &lit {
                                    Lit::Str(lit) => {
                                        let output_string = lit.value();
                                        match output_string.as_str() {
                                            "hex" => {
                                                output_hex = true;
                                            }
                                            _ => {
                                                errors.push(Error::new_spanned(
                                                    path,
                                                    "Output format is not recognized.",
                                                ));
                                            }
                                        }
                                    }
                                    _ => errors.push(Error::new_spanned(
                                        path,
                                        "This metadata is expected to be a string.",
                                    )),
                                }
                            } else {
                                errors.push(Error::new_spanned(
                                    path,
                                    "This metadata key is not recognized.",
                                ));
                            }
                        }
                        _ => errors.push(Error::new_spanned(
                            item,
                            "This metadata item is not recognized.",
                        )),
                    }
                }
            }
            Ok(_) => errors.push(Error::new_spanned(
                event_attr,
                "This metadata is not recognized.",
            )),
            Err(e) => {
                errors.push(e);
            }
        }
    }

    let mut field_metadata_intype: Expr;
    let mut field_metadata_out_type: Option<Expr> = None;

    if let Some(t) = well_known_types.find(&*field_ty) {
        field_metadata_intype = if let Some(in_type_expr) = t.opts.in_type_expr.as_ref() {
            in_type_expr.clone()
        } else {
            let in_type: u8 = t.in_type.bits();
            parse_quote!(#in_type)
        };

        if let Some(out_type) = t.opts.out_type {
            let out_type: u8 = out_type.bits();
            field_metadata_out_type = Some(parse_quote!(#out_type));
        } else {
            field_metadata_out_type = None;
        }

        if let Some(r) = t.opts.replacement_type.as_ref() {
            *field_ty = r.clone();
        }
        match t.code {
            WellKnownType::ref_str => {
                // We encode &str as COUNTEDANSISTRING (so that we do not need
                // a NUL-terminated string) and marking its output type as UTF-8.
                // This uses two EVENT_DATA_DESCRIPTOR slots.
                let field_len = ident_suffix(field_name, "len");
                statements.extend(quote_spanned! {
                    field_span =>
                    let #field_len: u16 = #field_name.len() as u16;
                });
                data_descriptor_array.extend(quote! {
                    EventDataDescriptor::from(&#field_len),
                    EventDataDescriptor::from(#field_name),
                });
            }
            WellKnownType::u16cstr => {
                let field_len = ident_suffix(field_name, "len");
                statements.extend(quote! {
                    let #field_len: usize = #field_name.len(); // length in code units
                    let #field_len: u16 = #field_len.min(0xffff) as u16;
                });
                data_descriptor_array.extend(quote! {
                    EventDataDescriptor::from(&#field_len),
                    EventDataDescriptor::from(#field_name),
                });
            }
            WellKnownType::osstr => {
                let field_len = ident_suffix(field_name, "len");
                let field_desc = ident_suffix(field_name, "desc");
                let field_u16cstring = ident_suffix(field_name, "u16cstring");
                let field_u16cstr = ident_suffix(field_name, "u16cstr");
                statements.extend(quote! {
                    let #field_desc: EventDataDescriptor;
                    let #field_len: u16;
                    let #field_u16cstring: ::win_etw_provider::types::U16CString;
                    let #field_u16cstr: &::win_etw_provider::types::U16CStr;
                    match ::win_etw_provider::types::U16CString::from_os_str(#field_name) {
                        Ok(s) => {
                            #field_u16cstring = s;
                            #field_u16cstr = #field_u16cstring.as_ref();
                            #field_desc = EventDataDescriptor::from(#field_u16cstr);
                            #field_len = #field_u16cstr.len() as u16; // length in UTF-16 code units, not bytes
                        }
                        Err(_) => {
                            #field_desc = EventDataDescriptor::empty();
                            #field_len = 0;
                        }
                    }
                });
                data_descriptor_array.extend(quote! {
                    EventDataDescriptor::from(&#field_len),
                    #field_desc,
                });
            }
            WellKnownType::SocketAddrV4 => {
                // We cannot simply pass a copy of std::net::SocketAddrV4 to ETW because it does
                // not have a guaranteed memory layout. So we convert it to
                // win_etw_provider::SocketAddrV4, which does.
                let field_len = ident_suffix(field_name, "len");
                statements.extend(quote_spanned! {
                    field_span =>
                    let #field_name = ::win_etw_provider::SocketAddrV4::from(#field_name);
                    let #field_len: u16 = (::core::mem::size_of::<::win_etw_provider::SocketAddrV4>()) as u16;
                });
                data_descriptor_array.extend(quote! {
                    EventDataDescriptor::from(&#field_len),
                    EventDataDescriptor::from(&#field_name),
                });
            }
            WellKnownType::SocketAddrV6 => {
                // We cannot simply pass a copy of std::net::SocketAddrV6 to ETW because it does
                // not have a guaranteed memory layout. So we convert it to
                // win_etw_provider::SocketAddrV6, which does.
                let field_len = ident_suffix(field_name, "len");
                statements.extend(quote_spanned! {
                    field_span =>
                    let #field_name = ::win_etw_provider::SocketAddrV6::from(#field_name);
                    let #field_len: u16 = (::core::mem::size_of::<::win_etw_provider::SocketAddrV6>()) as u16;
                });
                data_descriptor_array.extend(quote_spanned! {
                    field_span =>
                    EventDataDescriptor::from(&#field_len),
                    EventDataDescriptor::from(&#field_name),
                });
            }
            WellKnownType::SocketAddr => {
                let field_desc = ident_suffix(field_name, "desc");
                let field_v4 = ident_suffix(field_name, "v4");
                let field_v6 = ident_suffix(field_name, "v6");
                let field_len_ident = ident_suffix(field_name, "len");
                statements.extend(quote_spanned! {
                    field_span =>
                    let #field_v4;
                    let #field_v6;
                    let #field_len_ident;
                    let #field_desc;
                    match #field_name {
                        ::std::net::SocketAddr::V4(ref address_v4) => {
                            #field_v4 = ::win_etw_provider::SocketAddrV4::from(address_v4);
                            #field_len_ident = ::core::mem::size_of::<::win_etw_provider::SocketAddrV4>() as u16;
                            #field_desc = EventDataDescriptor::from(&#field_v4);
                        }
                        ::std::net::SocketAddr::V6(ref address_v6) => {
                            #field_v6 = ::win_etw_provider::SocketAddrV6::from(address_v6);
                            #field_len_ident = ::core::mem::size_of::<::win_etw_provider::SocketAddrV6>() as u16;
                            #field_desc = EventDataDescriptor::from(&#field_v6);
                        }
                    }
                });
                data_descriptor_array.extend(quote_spanned! {
                    field_span =>
                    EventDataDescriptor::from(&#field_len_ident),
                    #field_desc,
                });
            }
            WellKnownType::SystemTime => {
                // If the SystemTime value cannot be converted to a FILETIME, then the data
                // descriptor for this field will be empty, rather than pointing to an invalid
                // or incorrect time value.
                statements.extend(quote_spanned!{
                    field_span =>
                    let #field_name = <::win_etw_provider::FILETIME as ::core::convert::TryFrom<::std::time::SystemTime>>
                    ::try_from(#field_name);
                });
                data_descriptor_array.extend(quote_spanned! {
                    field_span =>
                    match &#field_name {
                        Ok(ref t) => EventDataDescriptor::from(&t.0),
                        Err(_) => EventDataDescriptor::empty(),
                    }
                });
            }
            WellKnownType::FILETIME => {
                data_descriptor_array.extend(quote_spanned! {
                    field_span =>
                    EventDataDescriptor::from(&#field_name.0),
                });
            }
            WellKnownType::bool => {
                statements.extend(quote_spanned! {
                    field_span =>
                    let #field_name: i8 = #field_name as i8;
                });
                data_descriptor_array.extend(quote! {
                    EventDataDescriptor::from(&#field_name),
                });
            }
            _ => {
                if t.is_ref {
                    data_descriptor_array.extend(quote_spanned! {
                        field_span =>
                        EventDataDescriptor::from(#field_name),
                    });
                } else {
                    data_descriptor_array.extend(quote_spanned! {
                        field_span =>
                        EventDataDescriptor::from(&#field_name),
                    });
                }
            }
        }
    } else {
        match &*field_ty {
            syn::Type::Reference(ref_ty) => {
                match &*ref_ty.elem {
                    syn::Type::Slice(slice_ty) => {
                        if let Some(t) = well_known_types.find(&slice_ty.elem) {
                            if !t.primitive {
                                return Err(UnsupportedField);
                            }
                            // Slices are encoded using two data descriptors.
                            // The first is for the length field, the second for the data.
                            let field_len_ident = ident_suffix(field_name, "len");
                            statements.extend(quote_spanned! {
                                field_span =>
                                let #field_name = &#field_name[..#field_name.len().min(0xffff)];
                                let #field_len_ident: u16 = #field_name.len() as u16;
                            });
                            data_descriptor_array.extend(quote! {
                                EventDataDescriptor::from(&#field_len_ident),
                                EventDataDescriptor::from(#field_name),
                            });
                            // 0x40 is VCOUNT flag
                            let in_type_u8 = t.in_type.bits();
                            field_metadata_intype = parse_quote!(#in_type_u8);
                            field_metadata_intype = parse_quote!(#field_metadata_intype | ::win_etw_provider::metadata::InFlag::VCOUNT_FLAG.bits());
                        } else {
                            return Err(UnsupportedField);
                        }
                    }
                    _ => {
                        return Err(UnsupportedField);
                    }
                }
            }
            _ => {
                return Err(UnsupportedField);
            }
        }
    }

    if output_hex {
        let hex: Expr = parse_quote!(::win_etw_provider::metadata::OutFlag::HEX.bits());
        field_metadata_out_type = Some(if let Some(out_type) = field_metadata_out_type {
            parse_quote!(#out_type | #hex)
        } else {
            hex
        });
    }

    if let Some(out_type) = field_metadata_out_type {
        field_metadata_intype = parse_quote!(#field_metadata_intype | ::win_etw_provider::metadata::InFlag::CHAIN_FLAG.bits());
        event_metadata.push(field_metadata_intype);
        event_metadata.push(out_type);
    } else {
        event_metadata.push(field_metadata_intype);
    }
    Ok(())
}

use errors::CombinedErrors;

/// Represents the "attribute" parameter of the `#[trace_logging_provider]` proc macro.
#[derive(Default, Debug)]
struct ProviderAttributes {
    uuid: Uuid,
    provider_name: Option<String>,
}

impl syn::parse::Parse for ProviderAttributes {
    fn parse(stream: syn::parse::ParseStream) -> syn::Result<Self> {
        let mut errors = CombinedErrors::default();

        let mut uuid_opt = None;
        let items: syn::punctuated::Punctuated<syn::Meta, Token![,]> =
            stream.parse_terminated(syn::Meta::parse)?;

        let mut provider_name = None;
        for item in items.iter() {
            errors.scope(item.span(), |scope| {
                match item {
                    syn::Meta::NameValue(syn::MetaNameValue { path, lit, .. }) => {
                        if *path == parse_quote!(guid) {
                            let s = if let syn::Lit::Str(s) = lit {
                                s
                            } else {
                                scope.msg(
                                    "The attribute value is required to be a GUID in string form.",
                                );
                                return Ok(());
                            };

                            let guid_str = s.value();
                            let uuid = if let Ok(value) = guid_str.parse::<Uuid>() {
                                if value == Uuid::nil() {
                                    scope.msg("The GUID cannot be the NIL (all-zeroes) GUID.");
                                }
                                value
                            } else {
                                scope.msg("The attribute value is required to be a valid GUID.");
                                Uuid::nil()
                            };
                            if uuid_opt.is_some() {
                                scope.msg(
                                    "The 'guid' attribute key cannot be specified more than once.",
                                );
                            } else {
                                uuid_opt = Some(uuid);
                            }
                        } else if *path == parse_quote!(name) {
                            if let syn::Lit::Str(s) = lit {
                                if provider_name.is_none() {
                                    provider_name = Some(s.value());
                                } else {
                                    scope.msg("The 'name' attribute can only be specified once.");
                                }
                            } else {
                                scope.msg("The 'name' attribute key requires a string value.");
                            }
                        } else {
                            scope.msg("Unrecognized attribute key.");
                        }
                    }
                    syn::Meta::Path(path) if *path == parse_quote!(static_mode) => {
                        // eprintln!("Found 'static'");
                    }
                    _ => {
                        // eprintln!("item: {:#?}", item);
                        scope.msg("Unrecognized attribute item.");
                    }
                }
                Ok(())
            });
        }

        // We could generate a deterministic GUID by hashing the event provider name or the
        // signatures of the event methods. Both of those approaches have problems, unfortunately.
        // It's best to require developers to specify a GUID.
        //
        // I considered generating a GUID and printing it in the error message, but I decided not
        // to do that because it introduces non-determinism.
        let uuid = if let Some(provider_uuid) = uuid_opt {
            provider_uuid
        } else {
            errors.push(Error::new_spanned(
                &items,
                "The 'guid' attribute is required.
Please generate a GUID that uniquely identfies this event provider.
Do not use the same GUID for different event providers.
Example: #[trace_logging_provider(guid = \"123e4567-e89b...\")]",
            ));
            Uuid::nil()
        };

        errors.into_result(ProviderAttributes {
            uuid,
            provider_name,
        })
    }
}

fn uuid_to_expr(uuid: &Uuid) -> syn::Expr {
    let bytes: &[u8; 16] = uuid.as_bytes();
    let data1: u32 = ((bytes[0] as u32) << 24)
        | ((bytes[1] as u32) << 16)
        | ((bytes[2] as u32) << 8)
        | (bytes[3] as u32);
    let data2: u16 = ((bytes[4] as u16) << 8) | (bytes[5] as u16);
    let data3: u16 = ((bytes[6] as u16) << 8) | (bytes[7] as u16);
    let data4_0 = bytes[8];
    let data4_1 = bytes[9];
    let data4_2 = bytes[10];
    let data4_3 = bytes[11];
    let data4_4 = bytes[12];
    let data4_5 = bytes[13];
    let data4_6 = bytes[14];
    let data4_7 = bytes[15];
    return parse_quote! {
        ::win_etw_provider::GUID {
            data1: #data1,
            data2: #data2,
            data3: #data3,
            data4: [
                #data4_0,
                #data4_1,
                #data4_2,
                #data4_3,
                #data4_4,
                #data4_5,
                #data4_6,
                #data4_7,
            ]
        }
    };
}

struct EventAttributes {
    level: syn::Expr,
    opcode: syn::Expr,
    task: syn::Expr,
    method_attrs: Vec<syn::Attribute>,
}

fn parse_event_attributes(
    errors: &mut Vec<Error>,
    method_ident: &Ident,
    input_method_attrs: &[syn::Attribute],
) -> EventAttributes {
    let mut level: Expr = parse_quote!(::win_etw_provider::Level::INFO);
    let mut opcode: Expr = parse_quote!(0);
    let mut task: Expr = parse_quote!(0);

    let mut method_attrs: Vec<syn::Attribute> = Vec::new();

    let mut event_already_has_doc = false;
    let enable_default_event_doc = false;

    for attr in input_method_attrs.iter() {
        if attr.path == parse_quote!(doc) {
            method_attrs.push(attr.clone());
            event_already_has_doc = true;
        } else if attr.path == parse_quote!(event) {
            // The #[event] attribute lets the application specify the level, opcode, task,
            // keyword, etc.
            match attr.parse_meta() {
                Ok(syn::Meta::List(list)) => {
                    for item in list.nested.iter() {
                        match item {
                            syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
                                path,
                                lit,
                                ..
                            })) => {
                                if *path == parse_quote!(level) {
                                    match lit {
                                        Lit::Str(lit_str) => {
                                            let level_ident = match lit_str.value().as_str() {
                                                "critical" => quote!(CRITICAL),
                                                "error" => quote!(ERROR),
                                                "warn" => quote!(WARN),
                                                "info" => quote!(INFO),
                                                "verbose" => quote!(VERBOSE),
                                                _ => {
                                                    errors.push(Error::new_spanned(item, "The value specified for 'level' is not a valid string."));
                                                    quote!(INFO)
                                                }
                                            };
                                            level = parse_quote!(::win_etw_provider::Level::#level_ident);
                                        }
                                        Lit::Int(_) => {
                                            level = parse_quote!(::win_etw_provider::Level(#lit));
                                        }
                                        _ => {
                                            errors.push(Error::new_spanned(item, "The value specified for 'level' is not recognized."));
                                        }
                                    }
                                } else if *path == parse_quote!(opcode) {
                                    opcode = Expr::Lit(ExprLit {
                                        lit: lit.clone(),
                                        attrs: Vec::new(),
                                    });
                                } else if *path == parse_quote!(task) {
                                    task = Expr::Lit(ExprLit {
                                        lit: lit.clone(),
                                        attrs: Vec::new(),
                                    });
                                } else {
                                    errors
                                        .push(Error::new_spanned(item, "Unrecognized attribute."));
                                }
                            }
                            _ => {
                                errors.push(Error::new_spanned(item, "Unrecognized attribute."));
                            }
                        }
                    }
                }
                Ok(_) => {
                    errors.push(Error::new_spanned(
                        attr,
                        "The form of the #[event] attribute is invalid. \
                        It should be: #[event(name = \"value\", name2 = \"value2\", ...)].",
                    ));
                }
                Err(e) => {
                    errors.push(e);
                }
            }
        } else {
            errors.push(Error::new_spanned(
                attr,
                "The only attributes allowed on event methods are #[doc] and #[event(...)] attributes.",
            ));
        }
    }

    if !event_already_has_doc && enable_default_event_doc {
        let method_doc = format!(
            "Writes the `{}` event to the ETW log stream.",
            method_ident.to_string()
        );
        method_attrs.push(parse_quote!( #![doc = #method_doc] ));
    }

    EventAttributes {
        method_attrs,
        level,
        opcode,
        task,
    }
}

/// The separator we use to build dynamic identifiers, based on existing identifiers.
/// Ideally, we would use a string that will not cause collisions with user-provided
/// identifiers. Rust supports non-ASCII identifiers, which would allow us to
/// use something like `U+0394 GREEK CAPITAL LETTER DELTA`, but this is an unstable
/// feature.
///
/// Instead, we use "__". Idiomatic identifiers should not contain "__" because this
/// does not meet Rust's definition of a snake-case name. So we add use this, and add
/// `#[allow(non_snake_case)]` to our generated code.
const IDENT_SEPARATOR: &str = "__";

/// Builds a new identifier, based on an existing identifier.
fn ident_suffix(ident: &Ident, suffix: &str) -> Ident {
    Ident::new(
        &format!("{}{}{}", ident.to_string(), IDENT_SEPARATOR, suffix),
        ident.span(),
    )
}
