// Copyright (c) 2020 Huawei Technologies Co.,Ltd. All rights reserved.
//
// StratoVirt is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan
// PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//         http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

//! # migration_derive
//!
//! Exports two derives for migration flow:
//! The `Desc` derive pro macro to generate the DeviceStateDesc structure for
//! DeviceState struct.It also offers two attributes: one to describe version
//! and compat version for structure, the other to give struct field an `alias`
//! name.
//!
//! ```no_run
//! #[macro_use]
//! extern crate migration_derive;
//! extern crate migration;
//! extern crate util;
//!
//! use migration::{DeviceStateDesc, FieldDesc, MigrationManager};
//!
//! #[derive(Desc)]
//! #[desc_version(compat_version = "0.1.0")]
//! struct DeviceState {
//!     #[alias(activated)]
//!     device_activated: bool,
//!     #[alias(select)]
//!     features_select: u32,
//!     #[alias(acked_select)]
//!     acked_features_select: u32,
//!     #[alias(status)]
//!     device_status: u32,
//! }
//!
//! fn main() {
//!     println!("Description of DeviceState is {:?}", DeviceState::descriptor());
//! }
//!
//! ```
//!
//! 2. The `ByteCode` derive to auto add `ByteCode` trait and its relying trait for
//! struct, such as `Default`, `Sync`, `Send`.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

mod attr_parser;
mod field_parser;
mod struct_parser;

/// Define a macro derive `Desc`.
#[proc_macro_derive(Desc, attributes(desc_version, alias))]
pub fn derive_desc(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let ident = input.ident.clone();

    // Get attr info
    let (mut current_version, mut compat_version) =
        attr_parser::parse_struct_attributes(&input.attrs);
    attr_parser::validate_version(&mut current_version, &mut compat_version);

    let desc = match &input.data {
        syn::Data::Struct(data_struct) => {
            struct_parser::parse_struct(data_struct, &ident, current_version, compat_version)
        }
        _ => panic!("Only support struct."),
    };

    (quote! {
        impl #ident {
            pub fn descriptor() -> DeviceStateDesc {
                #desc
            }
        }
    })
    .into()
}

#[proc_macro_derive(ByteCode)]
pub fn derive_bytecode(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let ident = input.ident.clone();

    let struct_default = match &input.data {
        syn::Data::Struct(data_struct) => struct_parser::parse_struct_default(data_struct, &ident),
        _ => panic!("Only support struct."),
    };

    (quote! {
        impl Default for #ident {
            fn default() -> #ident {
                #struct_default
            }
        }
        unsafe impl Send for #ident {}
        unsafe impl Sync for #ident {}
        impl util::byte_code::ByteCode for #ident {}
    })
    .into()
}