use crate::tags::{self, KeyTag, KeyTagImpl, PrimaryKeyTag};
use crate::{DeserializeError, DeserializeKey, SerializeError, SerializeKey};
use std::borrow::Cow;
use uuid::Uuid;

macro_rules! impl_primitive {
    { $ty:ty $( :primary $primary:ty )? } => {
        impl_primitive! {
            $ty $( :primary $primary )?
            :tag tags::U8, tags::I8, tags::U16, tags::I16,
                 tags::U32, tags::I32, tags::U64, tags::I64
        }
    };

    { $ty:ty $( :primary $primary:ty )? :tag $( $tag:ty ),+ } => {
        $(
            impl PrimaryKeyTag for $ty {
                type KeyTag = $primary;
            }
        )?

        $(
            impl SerializeKey<$tag> for $ty {
                fn try_as_key(&self) -> Result<<$tag as KeyTagImpl>::Key<'_>, SerializeError> {
                    (*self).try_into().map_err(|_| SerializeError::UnexpectedValue)
                }
            }

            impl DeserializeKey<$tag> for $ty {
                fn try_from_key(key: <$tag as KeyTagImpl>::Key<'_>,) -> Result<Self, DeserializeError> {
                    key.try_into().map_err(|_| DeserializeError::UnexpectedValue)
                }
            }
        )+
    };
}

impl_primitive!(u8 :primary tags::U8);
impl_primitive!(i8 :primary tags::I8);
impl_primitive!(u16 :primary tags::U16);
impl_primitive!(i16 :primary tags::I16);
impl_primitive!(u32 :primary tags::U32);
impl_primitive!(i32 :primary tags::I32);
impl_primitive!(u64 :primary tags::U64);
impl_primitive!(i64 :primary tags::I64);
impl_primitive!(usize :primary tags::U64);
impl_primitive!(isize :primary tags::I64);
impl_primitive!(u128);
impl_primitive!(i128);

impl PrimaryKeyTag for String {
    type KeyTag = tags::String;
}

impl SerializeKey<tags::String> for String {
    fn try_as_key(&self) -> Result<Cow<'_, str>, SerializeError> {
        Ok(Cow::Borrowed(self))
    }
}

impl DeserializeKey<tags::String> for String {
    fn try_from_key(key: Cow<str>) -> Result<Self, DeserializeError> {
        Ok(key.into_owned())
    }
}

impl PrimaryKeyTag for str {
    type KeyTag = tags::String;
}

impl SerializeKey<tags::String> for str {
    fn try_as_key(&self) -> Result<Cow<'_, Self>, SerializeError> {
        Ok(Cow::Borrowed(self))
    }
}

impl PrimaryKeyTag for Uuid {
    type KeyTag = tags::Uuid;
}

impl SerializeKey<tags::Uuid> for Uuid {
    fn try_as_key(&self) -> Result<Self, SerializeError> {
        Ok(*self)
    }
}

impl DeserializeKey<tags::Uuid> for Uuid {
    fn try_from_key(key: Self) -> Result<Self, DeserializeError> {
        Ok(key)
    }
}

impl<T: PrimaryKeyTag + ?Sized> PrimaryKeyTag for &T {
    type KeyTag = T::KeyTag;
}

impl<T: KeyTag, U: SerializeKey<T> + ?Sized> SerializeKey<T> for &U {
    fn try_as_key(&self) -> Result<<T::Impl as KeyTagImpl>::Key<'_>, SerializeError> {
        (**self).try_as_key()
    }
}

impl<T: PrimaryKeyTag + ?Sized> PrimaryKeyTag for &mut T {
    type KeyTag = T::KeyTag;
}

impl<T: KeyTag, U: SerializeKey<T> + ?Sized> SerializeKey<T> for &mut U {
    fn try_as_key(&self) -> Result<<T::Impl as KeyTagImpl>::Key<'_>, SerializeError> {
        (**self).try_as_key()
    }
}

impl<T: PrimaryKeyTag + ?Sized> PrimaryKeyTag for Box<T> {
    type KeyTag = T::KeyTag;
}

impl<T: KeyTag, U: SerializeKey<T> + ?Sized> SerializeKey<T> for Box<U> {
    fn try_as_key(&self) -> Result<<T::Impl as KeyTagImpl>::Key<'_>, SerializeError> {
        (**self).try_as_key()
    }
}

impl<T: KeyTag, U: DeserializeKey<T>> DeserializeKey<T> for Box<U> {
    fn try_from_key(key: <T::Impl as KeyTagImpl>::Key<'_>) -> Result<Self, DeserializeError> {
        U::try_from_key(key).map(Self::new)
    }
}
