1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Implementation of general [`Report`] serialization.
//!
//! The value can be of any type, currently only printable attachments and context are supported, in
//! the near future any values will be supported through the use of hooks.
//!
//! The serialized [`Report`] is a list of all current sources with the following output:
//!
//! ```json
//! {
//!     "context": "context display output",
//!     "attachments": ["all", "attachments", "leading", "up", "to", "this", "context"],
//!     "sources": [] // recursive render using `frame.sources()`
//! }
//! ```

use alloc::{format, vec, vec::Vec};

use serde::{ser::SerializeMap, Serialize, Serializer};

use crate::{AttachmentKind, Context, Frame, FrameKind, Report};

struct SerializeAttachment<'a>(&'a Frame);

impl<'a> Serialize for SerializeAttachment<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let Self(frame) = self;

        #[allow(clippy::match_same_arms)]
        match frame.kind() {
            FrameKind::Context(_) => {
                // TODO: for now `Context` is unsupported, upcoming PR will fix via hooks
                // `SerializeAttachmentList` ensures that no context is ever serialized
                unimplemented!()
            }
            FrameKind::Attachment(AttachmentKind::Opaque(_)) => {
                // TODO: for now opaque attachments are unsupported, upcoming PR will fix that
                // `SerializeAttachmentList` ensures that no such attachment is added
                unimplemented!()
            }
            FrameKind::Attachment(AttachmentKind::Printable(attachment)) => {
                format!("{attachment}").serialize(serializer)
            }
        }
    }
}

struct SerializeAttachmentList<'a, 'b>(&'a [&'b Frame]);

impl<'a, 'b> Serialize for SerializeAttachmentList<'a, 'b> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_seq(
            self.0
                .iter()
                .copied()
                .filter(|attachment| {
                    // for now opaque attachments are ignored
                    !matches!(
                        attachment.kind(),
                        FrameKind::Attachment(AttachmentKind::Opaque(_))
                    )
                })
                .map(SerializeAttachment),
        )
    }
}

struct SerializeContext<'a> {
    attachments: Vec<&'a Frame>,
    context: &'a dyn Context,
    sources: &'a [Frame],
}

impl<'a> Serialize for SerializeContext<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let Self {
            context,
            attachments,
            sources,
        } = self;

        let mut map = serializer.serialize_map(Some(3))?;
        map.serialize_entry("context", &format!("{context}").as_str())?;
        map.serialize_entry("attachments", &SerializeAttachmentList(attachments))?;
        map.serialize_entry("sources", &SerializeSources(sources))?;

        map.end()
    }
}

struct SerializeSources<'a>(&'a [Frame]);

impl<'a> Serialize for SerializeSources<'a> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.collect_seq(self.0.iter().flat_map(|source| find_next(&[], source)))
    }
}

// find the next applicable context and return the serializer
fn find_next<'a>(head: &[&'a Frame], mut current: &'a Frame) -> Vec<SerializeContext<'a>> {
    let mut attachments = vec![];
    attachments.extend(head);

    loop {
        if let FrameKind::Context(context) = current.kind() {
            // found the context, return all attachments (reversed)
            attachments.reverse();

            return vec![SerializeContext {
                attachments,
                context,
                sources: current.sources(),
            }];
        } else if current.sources().len() > 1 {
            // current is an attachment, add to attachments and recursively probe
            attachments.push(current);

            return current
                .sources()
                .iter()
                .flat_map(|source| find_next(&attachments, source))
                .collect();
        } else if current.sources().len() == 1 {
            attachments.push(current);

            current = &current.sources()[0];
        } else {
            // there are no more frames, therefore we need to abandon
            // this is theoretically impossible (the bottom is always a context), but not enforced
            return vec![];
        }
    }
}

impl<C: Context> Serialize for Report<C> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        SerializeSources(self.current_frames()).serialize(serializer)
    }
}