//! Framework for implementing FSCC Info Classes

use binrw::{
    meta::{ReadEndian, WriteEndian},
    prelude::*,
};

/// Trait for file information types.
/// This trait contains all types of all file info types and classes, specified in MS-FSCC.
///
/// It's role is to allow converting an instance of a file information type to a class,
/// and to provide the class type from the file information type.
pub trait FileInfoType:
    Sized
    + for<'a> BinRead<Args<'static> = (Self::Class,)>
    + ReadEndian
    + for<'a> BinWrite<Args<'static> = ()>
    + WriteEndian
    + std::fmt::Debug
{
    /// The class of the file information.
    type Class;

    /// Get the class of the file information.
    fn class(&self) -> Self::Class;
}

/// An internal macro for generating a file class enums,
/// for both the file information class, and information value.
/// including a trait for the value types.
macro_rules! file_info_classes {
    (
        $(#[doc = $docstring:literal])*
        $svis:vis $name:ident {
            $($vis:vis $field_name:ident = $cid:literal,)+
        }
    ) => {
        #[allow(unused_imports)]
        use binrw::prelude::*;
        pastey::paste! {
            /// Trait for inner values in
            #[doc = concat!("[`", stringify!($name), "`][crate::", stringify!($name), "].")]
            ///
            /// This trait provides the `CLASS_ID` constant for each value type,
            /// linking values to their information class IDs.
            ///
            /// _Auto-generated by the `file_info_classes!` macro_
            pub trait [<$name Value>] :
                TryFrom<$name, Error = $crate::SmbFsccError>
                + Send + 'static
                + Into<$name>
                + for <'a> [<BinRead>]<Args<'a> = ()>
                + for <'b> [<BinWrite>]<Args<'b> = ()>
            {
                const CLASS_ID: [<$name Class>];
            }

            /// Information class IDs for
            #[doc = concat!("[`", stringify!($name), "`][crate::", stringify!($name), "]")]
            ///
            /// _Auto-generated by the `file_info_classes!` macro_
            #[binrw::binrw]
            #[derive(Debug, PartialEq, Eq, Clone, Copy)]
            #[brw(repr(u8))]
            $svis enum [<$name Class>] {
                $(
                    $vis [<$field_name Information>] = $cid,
                )*
            }

            $(#[doc = $docstring])*
            ///
            /// See:
            #[doc = concat!("- [`", stringify!([<$name Class>]), "`][crate::", stringify!([<$name Class>]), "] - class IDs")]
            #[doc = concat!("- [`", stringify!([<$name Value>]), "`][crate::", stringify!([<$name Value>]), "] - value trait")]
            ///
            /// _Auto-generated by the `file_info_classes!` macro_
            #[binrw::binrw]
            #[derive(Debug, PartialEq, Eq)]
            #[brw(little)]
            #[br(import(c: [<$name Class>]))]
            $svis enum $name {
                $(
                    #[br(pre_assert(matches!(c, [<$name Class>]::[<$field_name Information>])))]
                    [<$field_name Information>]([<File $field_name Information>]),
                )*
            }

            impl std::fmt::Display for [<$name Class>] {
                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                    match self {
                        $(
                            [<$name Class>]::[<$field_name Information>] => write!(f, stringify!([<$field_name Information>])),
                        )*
                    }
                }
            }

            impl $crate::FileInfoType for $name {
                type Class = [<$name Class>];
                fn class(&self) -> Self::Class {
                    match self {
                        $(
                            $name::[<$field_name Information>](_) => [<$name Class>]::[<$field_name Information>],
                        )*
                    }
                }
            }

            $(
                impl From<[<File $field_name Information>]> for $name {
                    fn from(value: [<File $field_name Information>]) -> $name {
                        $name::[<$field_name Information>](value)
                    }
                }

                impl TryFrom<$name> for [<File $field_name Information>] {
                    type Error = $crate::SmbFsccError;

                    fn try_from(value: $name) -> Result<Self, Self::Error> {
                        pub use $crate::FileInfoType;
                        match value {
                            $name::[<$field_name Information>](v) => Ok(v),
                            _ => Err($crate::SmbFsccError::UnexpectedInformationType(stringify!([<$field_name Information>]), <Self as [<$name Value>]>::CLASS_ID as u8, value.class() as u8)),
                        }
                    }
                }

                impl [<$name Value>] for [<File $field_name Information>] {
                    const CLASS_ID: [<$name Class>] = [<$name Class>]::[<$field_name Information>];
                }
            )*
        }
    }
}

pub(crate) use file_info_classes;
