Documentation
use super::MessageChain;
use crate::message::at::At;
use crate::message::meta::{Anonymous, MessageMetadata, Reply};
use crate::message::MessageElement;
use atri_ffi::ffi::ForFFI;
use atri_ffi::message::meta::{
    FFIAnonymous, FFIMessageMetadata, FFIReply, ANONYMOUS_FLAG, NONE_META, REPLY_FLAG,
};
use atri_ffi::message::{
    FFIAt, FFIMessageChain, FFIMessageValue, MessageValueFlag, MessageValueUnion,
};
use atri_ffi::{Managed, RustString, RustVec};
use std::mem::{ManuallyDrop, MaybeUninit};

impl ForFFI for MessageChain {
    type FFIValue = FFIMessageChain;

    fn into_ffi(self) -> Self::FFIValue {
        let meta = self.meta.into_ffi();
        let ffi: Vec<FFIMessageValue> = self
            .value
            .into_iter()
            .map(MessageElement::into_ffi)
            .collect();

        let raw = RustVec::from(ffi);
        FFIMessageChain { meta, inner: raw }
    }

    fn from_ffi(ffi: Self::FFIValue) -> Self {
        let meta = MessageMetadata::from_ffi(ffi.meta);

        let v = ffi.inner.into_vec();
        let values: Vec<MessageElement> = v.into_iter().map(MessageElement::from_ffi).collect();

        Self {
            meta,
            value: values,
        }
    }
}

impl ForFFI for MessageElement {
    type FFIValue = FFIMessageValue;

    fn into_ffi(self) -> Self::FFIValue {
        match self {
            MessageElement::Text(s) => FFIMessageValue {
                t: MessageValueFlag::Text.value(),
                union: MessageValueUnion {
                    text: ManuallyDrop::new(RustString::from(s)),
                },
            },
            MessageElement::Image(img) => FFIMessageValue {
                t: MessageValueFlag::Image.value(),
                union: MessageValueUnion {
                    image: ManuallyDrop::new(Managed::from_value(img)),
                },
            },
            MessageElement::At(At { target, display }) => FFIMessageValue {
                t: MessageValueFlag::At.value(),
                union: MessageValueUnion {
                    at: ManuallyDrop::new({
                        FFIAt {
                            target,
                            display: RustString::from(display),
                        }
                    }),
                },
            },
            MessageElement::AtAll => FFIMessageValue {
                t: MessageValueFlag::AtAll.value(),
                union: MessageValueUnion { at_all: () },
            },
            or => FFIMessageValue {
                t: MessageValueFlag::Unknown.value(),
                union: MessageValueUnion {
                    unknown: ManuallyDrop::new(Managed::from_value(or)),
                },
            },
        }
    }

    fn from_ffi(value: Self::FFIValue) -> Self {
        unsafe {
            match MessageValueFlag::try_from(value.t)
                .unwrap_or_else(|e| panic!("Unknown message value flag: {}", e))
            {
                MessageValueFlag::Text => {
                    MessageElement::Text(ManuallyDrop::into_inner(value.union.text).into())
                }
                MessageValueFlag::Image => {
                    MessageElement::Image(ManuallyDrop::into_inner(value.union.image).into_value())
                }
                MessageValueFlag::At => {
                    let inner = ManuallyDrop::into_inner(value.union.at);
                    MessageElement::At(At {
                        target: inner.target,
                        display: String::from(inner.display),
                    })
                }
                MessageValueFlag::AtAll => MessageElement::AtAll,
                MessageValueFlag::Unknown => {
                    ManuallyDrop::into_inner(value.union.unknown).into_value()
                }
            }
        }
    }
}

impl ForFFI for Reply {
    type FFIValue = FFIReply;

    fn into_ffi(self) -> Self::FFIValue {
        let ffi: Vec<FFIMessageValue> = self
            .elements
            .into_iter()
            .map(MessageElement::into_ffi)
            .collect();
        let ffi_chain = RustVec::from(ffi);

        FFIReply {
            reply_seq: self.reply_seq,
            sender: self.sender,
            time: self.time,
            elements: ffi_chain,
        }
    }

    fn from_ffi(value: Self::FFIValue) -> Self {
        let FFIReply {
            reply_seq,
            sender,
            time,
            elements,
        } = value;

        let elems = elements.into_vec();
        let values: Vec<MessageElement> = elems.into_iter().map(MessageElement::from_ffi).collect();

        Self {
            reply_seq,
            sender,
            time,
            elements: values,
        }
    }
}

impl ForFFI for Anonymous {
    type FFIValue = FFIAnonymous;

    fn into_ffi(self) -> Self::FFIValue {
        let Self {
            anon_id,
            nick,
            portrait_index,
            bubble_index,
            expire_time,
            color,
        } = self;

        let anon_id = RustVec::from(anon_id);
        let nick = RustString::from(nick);
        let color = RustString::from(color);

        FFIAnonymous {
            anon_id,
            nick,
            portrait_index,
            bubble_index,
            expire_time,
            color,
        }
    }

    fn from_ffi(value: Self::FFIValue) -> Self {
        let FFIAnonymous {
            anon_id,
            nick,
            portrait_index,
            bubble_index,
            expire_time,
            color,
        } = value;

        let anon_id = anon_id.into_vec();
        let nick = String::from(nick);
        let color = String::from(color);

        Self {
            anon_id,
            nick,
            portrait_index,
            bubble_index,
            expire_time,
            color,
        }
    }
}

impl ForFFI for At {
    type FFIValue = FFIAt;

    fn into_ffi(self) -> Self::FFIValue {
        let At { target, display } = self;

        FFIAt {
            target,
            display: RustString::from(display),
        }
    }

    fn from_ffi(value: Self::FFIValue) -> Self {
        let FFIAt { target, display } = value;

        Self {
            target,
            display: String::from(display),
        }
    }
}

impl ForFFI for MessageMetadata {
    type FFIValue = FFIMessageMetadata;

    fn into_ffi(self) -> Self::FFIValue {
        let Self {
            seqs,
            rands,
            time,
            sender,
            anonymous,
            reply,
        } = self;
        let mut flags = NONE_META;

        let mut ffi_anonymous = MaybeUninit::uninit();
        if let Some(anonymous) = anonymous {
            flags |= ANONYMOUS_FLAG;
            ffi_anonymous.write(anonymous.into_ffi());
        }

        let mut ffi_reply = MaybeUninit::uninit();
        if let Some(reply) = reply {
            flags |= REPLY_FLAG;
            ffi_reply.write(reply.into_ffi());
        }

        FFIMessageMetadata {
            seqs: seqs.into(),
            rands: rands.into(),
            time,
            sender,
            flags,
            anonymous: ffi_anonymous,
            reply: ffi_reply,
        }
    }

    fn from_ffi(value: Self::FFIValue) -> Self {
        let FFIMessageMetadata {
            seqs,
            rands,
            time,
            sender,
            flags,
            anonymous,
            reply,
        } = value;

        unsafe {
            Self {
                seqs: seqs.into_vec(),
                rands: rands.into_vec(),
                time,
                sender,
                anonymous: if flags & ANONYMOUS_FLAG != 0 {
                    Some(Anonymous::from_ffi(anonymous.assume_init()))
                } else {
                    None
                },
                reply: if flags & REPLY_FLAG != 0 {
                    Some(Reply::from_ffi(reply.assume_init()))
                } else {
                    None
                },
            }
        }
    }
}