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
use std::{fmt::Display, str::FromStr};

use crate::ParseError;

#[derive(Debug)]
pub struct Event {
    pub kind: EventKind,
    pub params: Vec<String>,
    pub text: Option<String>,
}

#[derive(Debug)]
pub enum EventKind {
    /// Generic event.
    Message,

    /// Bookmarks are highlighted in the time line and in the event log. They are easy to spot and
    /// handy to highlight parts of the flight, like a bombing run, or when the trainee was in her
    /// final approach for landing.
    Bookmark,

    /// Debug events are highlighted and easy to spot in the timeline and event log. Because they
    /// must be used for development purposes, they are displayed only when launching Tacview with
    /// the command line argument /Debug:on
    Debug,

    /// This event is useful to specify when an aircraft (or any object) is cleanly removed from the
    /// battlefield (not destroyed). This prevents Tacview from generating a Destroyed event by
    /// error.
    LeftArea,

    /// When an object has been officially destroyed.
    Destroyed,

    /// Because Tacview may not always properly auto-detect take-off events, it can be useful to
    /// manually inject this event in the flight recording.
    TakenOff,

    /// Because Tacview may not always properly auto-detect landing events, it can be useful to
    /// manually inject this event in the flight recording.
    Landed,

    /// Mainly used for real-life training debriefing to specify when a weapon (typically a
    /// missile) reaches or misses its target. Tacview will report in the shot log as well as in the
    /// 3D view the result of the shot. Most parameters are optional. SourceId designates the object
    /// which has fired the weapon, while TargetId designates the target. Even if the displayed
    /// result may be in nautical miles, bullseye coordinates must be specified in meters. The
    /// target must be explicitly (manually) destroyed or disabled using the appropriate properties
    /// independently from this event.
    Timeout,

    /// Unknown event. This only exists for forward compatibility and using it is not recommended
    /// as the event you are using could be move to the known event in a future release.
    Unknown(String),
}

impl FromStr for Event {
    type Err = ParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut parts = s.split('|');
        let kind = parts.next().ok_or(ParseError::InvalidEvent)?;
        let kind = match kind {
            "Message" => EventKind::Message,
            "Bookmark" => EventKind::Bookmark,
            "Debug" => EventKind::Debug,
            "LeftArea" => EventKind::LeftArea,
            "Destroyed" => EventKind::Destroyed,
            "TakenOff" => EventKind::TakenOff,
            "Landed" => EventKind::Landed,
            "Timeout" => EventKind::Timeout,
            name => EventKind::Unknown(name.to_string()),
        };

        let mut params = parts.map(String::from).collect::<Vec<_>>();
        let text = if params.is_empty() {
            None
        } else {
            Some(params.remove(params.len() - 1)).filter(|s| !s.is_empty())
        };

        Ok(Event { kind, params, text })
    }
}

impl Display for Event {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "0,Event={}", self.kind.as_str())?;
        for param in &self.params {
            write!(f, "|{}", param)?;
        }
        write!(f, "|{}", self.text.as_deref().unwrap_or_default())?;
        Ok(())
    }
}

impl EventKind {
    fn as_str(&self) -> &str {
        use EventKind::*;
        match self {
            Message => "Message",
            Bookmark => "Bookmark",
            Debug => "Debug",
            LeftArea => "LeftArea",
            Destroyed => "Destroyed",
            TakenOff => "TakenOff",
            Landed => "Landed",
            Timeout => "Timeout",
            Unknown(name) => name,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_empty_event_text() {
        assert_eq!(
            Event {
                kind: EventKind::Landed,
                params: vec!["1".to_string(), "2".to_string()],
                text: None,
            }
            .to_string(),
            "0,Event=Landed|1|2|"
        )
    }
}