#![allow(missing_docs)]

use abi_serde::{wire, Deserialize, Serialize};

use crate::error::Error;

pub trait OptionValue<'de> {
    type Value: Deserialize<'de> + Serialize;
    type Variant: Copy + Into<u32> + TryFrom<u32, Error = Error>;
    const VARIANT: Self::Variant;

    fn store(value: &Self::Value, buffer: &mut [u8]) -> Result<usize, Error> {
        Ok(wire::to_slice(value, buffer)?)
    }

    fn load(buffer: &'de [u8]) -> Result<Self::Value, Error> {
        Ok(wire::from_slice(buffer)?)
    }
}

pub trait RequestValue<'de> {
    type Request: Deserialize<'de> + Serialize;
    type Response: Deserialize<'de> + Serialize;
    type Variant: Copy + Into<u32> + TryFrom<u32, Error = Error>;
    const VARIANT: Self::Variant;

    fn store_request(value: &Self::Request, buffer: &mut [u8]) -> Result<usize, Error> {
        Ok(wire::to_slice(value, buffer)?)
    }
    fn store_response(value: &Self::Response, buffer: &mut [u8]) -> Result<usize, Error> {
        Ok(wire::to_slice(value, buffer)?)
    }
    fn load_request(buffer: &'de [u8]) -> Result<Self::Request, Error> {
        Ok(wire::from_slice(buffer)?)
    }
    fn load_response(buffer: &'de [u8]) -> Result<Self::Response, Error> {
        Ok(wire::from_slice(buffer)?)
    }
}

pub trait OptionSizeHint {
    const SIZE_HINT: usize;
}

#[macro_export]
macro_rules! option_group {
    (
        $(#[$enum_meta:meta])*
        $vis:vis enum $variant_ty:ident<$lifetime:lifetime> {
            $(
                $(#[$variant_meta:meta])*
                $discriminant:literal: $variant:ident $(# $size_hint:literal)? ($value:ty)
            ),* $(,)?
        }
    ) => {
        $crate::primitive_enum! {
            $(#[$enum_meta])*
            $vis enum $variant_ty: u32 {
                $(
                    $(#[$variant_meta])*
                    $variant = $discriminant
                ),*
            }
        }

        $(
            $(#[$variant_meta])*
            $vis struct $variant;

            impl<$lifetime> $crate::option::OptionValue<$lifetime> for $variant {
                type Variant = $variant_ty;
                type Value = $value;
                const VARIANT: Self::Variant = $variant_ty::$variant;
            }

            $(
                impl $crate::option::OptionSizeHint for $variant {
                    const SIZE_HINT: usize = $size_hint;
                }
            )?
        )*
    };
}

#[macro_export]
macro_rules! request_group {
    (
        $(#[$enum_meta:meta])*
        $vis:vis enum $variant_ty:ident<$lifetime:lifetime> {
            $(
                $(#[$variant_meta:meta])*
                $discriminant:literal: $variant:ident $(# $size_hint:literal)? ($request:ty, $response:ty)
            ),* $(,)?
        }
    ) => {
        $crate::primitive_enum! {
            $(#[$enum_meta])*
            $vis enum $variant_ty: u32 {
                $(
                    $(#[$variant_meta])*
                    $variant = $discriminant
                ),*
            }
        }

        $(
            $(#[$variant_meta])*
            $vis struct $variant;

            impl<$lifetime> $crate::option::RequestValue<$lifetime> for $variant {
                type Variant = $variant_ty;
                type Request = $request;
                type Response = $response;
                const VARIANT: Self::Variant = $variant_ty::$variant;
            }

            $(
                impl $crate::option::OptionSizeHint for $variant {
                    const SIZE_HINT: usize = $size_hint;
                }
            )?
        )*
    };
}
