use arcis_interpreter::{
    arcis_type_macro,
    assert_current_module_macro,
    encrypted_mod_macro,
    error_macro,
    library_macro,
    module_macro,
};
use proc_macro::TokenStream;

/// Tells our arcis compiler to create encrypted instructions that mimic the rust module you wrote.
/// Use `#[instruction]` to mark the functions that will be the entry points.
#[proc_macro_attribute]
pub fn encrypted(_attr: TokenStream, item: TokenStream) -> TokenStream {
    module_macro(item.into()).into()
}

/// Validates an arcis library, trying to find errors.
/// Allows to build a library that can be used in `#[encrypted]` instructions.
///
/// Will transform
/// ```
/// mod arcis_library {
///     /* content */
/// }
/// ```
/// into
/// ```
/// /* content */
/// ```
/// if it does not detect any errors.
#[proc_macro_attribute]
pub fn encrypted_library(_attr: TokenStream, item: TokenStream) -> TokenStream {
    library_macro(item.into()).into()
}

/// Derives `ArcisType`, a trait that helps to use the type in automated tests.
#[doc(hidden)]
#[proc_macro_derive(ArcisType)]
pub fn derive_arcis_type(input: TokenStream) -> TokenStream {
    arcis_type_macro(input.into()).into()
}

/// An `#[instruction]`. Should be inside an `#[encrypted]` module.
#[proc_macro_attribute]
pub fn instruction(_attr: TokenStream, item: TokenStream) -> TokenStream {
    error_macro(
        item.into(),
        "`#[instruction]` can only be used inside `#[encrypted]`.",
    )
    .into()
}
#[doc(hidden)]
#[proc_macro_attribute]
pub fn arcis_circuit(_attr: TokenStream, item: TokenStream) -> TokenStream {
    error_macro(
        item.into(),
        "`#[arcis_circuit = \"...\"]` can only be used inside `#[encrypted]`.",
    )
    .into()
}

/// Use another file as a module in an `#[encrypted]` module.
///
/// Use `encrypted_mod!("path/to/my_module_name.rs")` or
/// `encrypted_mod!("path/to/my_module.rs", my_module_name)`
/// to use another file as a module. The path is relative to the current file.
///
/// The other file needs to look like this:
///
/// path/to/my_module.rs:
/// ```
/// use arcis::*;
///
/// #[encrypted_library]
/// mod arcis_library {
///     // Put your content here.
///
///     // Accessible through `my_module_name::MY_CONST`.
///     pub const MY_CONST: u16 = 67;
/// }
/// ```
/// And then it can be used it like this:
///
/// lib.rs:
/// ```
/// use arcis::*;
///
/// #[encrypted]
/// mod circuits {
///     use arcis::*;
///
///     // Allowing to use the contents of my_module.rs as `my_module_name::...`.
///     encrypted_mod!("path/to/my_module.rs", my_module_name);
///
///     pub struct InputValues {
///         v1: u8,
///         v2: u8,
///     }
///
///     #[instruction]
///     pub fn add_together(input_ctxt: Enc<Shared, InputValues>) -> Enc<Shared, u16> {
///         let input = input_ctxt.to_arcis();
///         let mut sum = input.v1 as u16 + input.v2 as u16;
///
///         // Using the constant declared in the other file.
///         sum += my_module_name::MY_CONST;
///
///         input_ctxt.owner.from_arcis(sum)
///     }
/// }
/// ```
#[proc_macro]
pub fn encrypted_mod(input: TokenStream) -> TokenStream {
    encrypted_mod_macro(input.into()).into()
}

/// Allows to use `crate::` in an #[encrypted] module.
/// Use it like this: `assert_current_module!(crate::path::to::encrypted::module);`
///
/// Example lib.rs:
/// ```
/// use arcis::*;
///
/// #[encrypted]
/// mod circuits {
///     use arcis::*;
///
///     assert_current_module!(crate::circuits);
///     // now I can use crate::circuits
///
///     const MY_CONST: usize = 0;
///
///     #[instruction]
///     fn return_my_const() -> usize {
///         crate::circuits::MY_CONST
///     }
/// }
/// ```
#[proc_macro]
pub fn assert_current_module(input: TokenStream) -> TokenStream {
    assert_current_module_macro(input.into()).into()
}
