//! Definition of macros for defining a device, its commands and attributes.

use crate::data::{DataKind, FromData, IntoData};
use crate::error::Result;

use super::DevCtx;


#[macro_export]
macro_rules! dev_def {
    () => {
        
    };
}


/// This macro can be used to easily construct a [`CommandDef`]. This macro accepts the
/// same fields as the structure for construction. 
/// 
/// The exception is that it's easier to construct each field, the `name`, 
/// `in_description`, `out_description` can accept either string literals, or expressions
/// yielding `&'static str`, or [`String`]. The `exec` field is special as it can accept 
/// many signatures to be more developer-friendly. The following declarations can be used 
/// depending on the expected signature:
/// 
/// - `exec(ref): async fn(&Model, data: I) -> Result<O>`: The default expected signature,
/// it gets a shared access to the model, which allows async-parallel execution of such 
/// functions.
/// 
/// - `exec(mut): async fn(&mut Model, data: I) -> Result<O>`: This signature requires
/// an exclusive access to the model.
/// 
/// - `exec(ctx): async fn(&DevCtx<Model>, data: I) -> Result<O>`: This signature provides
/// an access only to the device context, which can be used to access the data
/// 
/// - `exec(ref, ctx): async fn(&Model, &DevCtx<Model>, data: I) -> Result<O>`: 
/// Combine shared access to model and access to the context.
/// 
/// - `exec(mut, ctx): async fn(&mut Model, &DevCtx<Model>, data: I) -> Result<O>`: 
/// Combine exclusive access to model and access to the context.
/// 
/// Note that the `in_kind` and `out_kind` are inferred from the given async function's 
/// signature.
#[macro_export]
macro_rules! command_def {
    ( @build: () -> { $($output:tt)* } ) => {
        CommandDef {
            $($output)*
        }
    };
    ( @build: (name: $value:literal $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* name: ::std::borrow::Cow::Borrowed($value), })
    };
    ( @build: (name: $value:expr $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* name: $value.into(), })
    };
    ( @build: (in_description: $value:literal $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* in_description: ::std::borrow::Cow::Borrowed($value), })
    };
    ( @build: (in_description: $value:expr $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* in_description: $value.into(), })
    };
    ( @build: (out_description: $value:literal $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* out_description: ::std::borrow::Cow::Borrowed($value), })
    };
    ( @build: (out_description: $value:expr $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* out_description: $value.into(), })
    };
    ( @build: (exec: $value:expr $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {
        $crate::command_def!(@build: ($($($input)*)?) -> { $($output)* exec: $value, })
    };
    ( @build: (exec ($($attr:ident),*): $value:expr $(,$($input:tt)*)?) -> { $($output:tt)* } ) => {{
        $crate::command_def!(@build: ($($($input)*)?) -> { 
            $($output)* 
            in_kind: $crate::command_def!(@exec_check: ($($attr),*), $value, false),
            out_kind: $crate::command_def!(@exec_check: ($($attr),*), $value, true),
            exec: |ctx, name, input, output| {
                Box::pin(async move {

                    let input = $crate::data::FromData::from_data(input)  // type: I
                        .map_err(|e| $crate::error::Error::new($crate::error::ErrorKind::InvalidData)
                            .with_append_description("Failed to parse input data of command")
                            .with_append_description(format!("{e}")))?;
                    
                    let data = $crate::command_def!(@exec: ($($attr),*), $value, ctx, name, input)?;
                    *output = data;
                    Ok(())

                })
            },
        })
    }};
    ( @exec_check: (ref), $func:expr, $out:literal ) => {
        $crate::server::macros::__command_def_check_ref($func, $out)
    };
    ( @exec_check: (mut), $func:expr, $out:literal ) => {
        $crate::server::macros::__command_def_check_mut($func, $out)
    };
    ( @exec_check: (ref, ctx), $func:expr, $out:literal ) => {
        $crate::server::macros::__command_def_check_ref_ctx($func, $out)
    };
    ( @exec_check: (mut, ctx), $func:expr, $out:literal ) => {
        $crate::server::macros::__command_def_check_mut_ctx($func, $out)
    };
    ( @exec: (ref), $func:expr, $ctx:expr, $name:expr, $data:expr ) => {{
        let model = $ctx.read_model().await;
        let res = ($func)(&*model, $name, $data).await;
        res.map(|res| $crate::data::IntoData::into_data(res).into_owned())
    }};
    ( @exec: (mut), $func:expr, $ctx:expr, $name:expr, $data:expr ) => {{
        let mut model = $ctx.write_model().await;
        let res = ($func)(&mut *model, $name, $data).await;
        res.map(|res| $crate::data::IntoData::into_data(res).into_owned())
    }};
    ( @exec: (ref, ctx), $func:expr, $ctx:expr, $name:expr, $data:expr ) => {{
        let model = $ctx.read_model().await;
        let res = ($func)(&*model, $ctx, $name, $data).await;
        res.map(|res| $crate::data::IntoData::into_data(res).into_owned())
    }};
    ( @exec: (mut, ctx), $func:expr, $ctx:expr, $name:expr, $data:expr ) => {{
        let mut model = $ctx.write_model().await;
        let res = ($func)(&mut *model, $ctx, $name, $data).await;
        res.map(|res| $crate::data::IntoData::into_data(res).into_owned())
    }};
    ( $($input:tt)* ) => { $crate::command_def!(@build: ($($input)*) -> { }) };
}

#[doc(hidden)]
pub const fn __command_def_check_ref<'m, 'i, Model, I, O, F>(_func: fn(&'m Model, &'i str, I) -> F, out: bool) -> DataKind
where 
    I: FromData<'i>,
    O: IntoData<'m>,
    F: Future<Output = Result<O>> + 'm + 'i,
{
    if out { O::DATA_KIND } else { I::DATA_KIND }
}

#[doc(hidden)]
pub const fn __command_def_check_mut<'m, 'i, Model, I, O, F>(_func: fn(&'m mut Model, &'i str, I) -> F, out: bool) -> DataKind
where 
    I: FromData<'i>,
    O: IntoData<'m>,
    F: Future<Output = Result<O>> + 'm + 'i,
{
    if out { O::DATA_KIND } else { I::DATA_KIND }
}

#[doc(hidden)]
pub const fn __command_def_check_ref_ctx<'m, 'c, 'i, Model, I, O, F>(_func: fn(&'m Model, &'c DevCtx<Model>, &'i str, I) -> F, out: bool) -> DataKind
where 
    I: FromData<'i>,
    O: IntoData<'m>,
    F: Future<Output = Result<O>> + 'm + 'i + 'c,
{
    if out { O::DATA_KIND } else { I::DATA_KIND }
}

#[doc(hidden)]
pub const fn __command_def_check_mut_ctx<'m, 'c, 'i, Model, I, O, F>(_func: fn(&'m mut Model, &'c DevCtx<Model>, &'i str, I) -> F, out: bool) -> DataKind
where 
    I: FromData<'i>,
    O: IntoData<'m>,
    F: Future<Output = Result<O>> + 'm + 'i + 'c,
{
    if out { O::DATA_KIND } else { I::DATA_KIND }
}
