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
use palette::{
    encoding::{Linear, Srgb},
    FromColor, Hsl,
};
use serde::Serialize;

use crate::{
    component::ComponentName,
    global::{InputMessageData, Message},
    models::Color,
};

fn not_positive(x: &i64) -> bool {
    *x <= 0
}

fn color_to_hsl(color: Color) -> Hsl<Linear<Srgb>> {
    let (r, g, b) = color.into_components();
    palette::Hsl::from_color(palette::LinSrgb::new(
        r as f32 / 255.0,
        g as f32 / 255.0,
        b as f32 / 255.0,
    ))
}

#[derive(Debug, Serialize)]
pub struct PriorityInfo {
    pub priority: i32,
    #[serde(skip_serializing_if = "not_positive")]
    pub duration_ms: i64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub owner: Option<String>,
    pub component_id: ComponentName,
    pub origin: String,
    pub active: bool,
    pub visible: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value: Option<LedColor>,
}

impl PriorityInfo {
    pub fn new(
        msg: &crate::global::InputMessage,
        origin: String,
        expires: Option<std::time::Instant>,
        visible: bool,
    ) -> Self {
        let duration_ms = expires
            .and_then(|when| {
                let now = std::time::Instant::now();

                if when > now {
                    chrono::Duration::from_std(when - now).ok()
                } else {
                    Some(chrono::Duration::zero())
                }
            })
            .map(|d| d.num_milliseconds())
            .unwrap_or(-1);
        let active = duration_ms >= -1;

        match msg.data() {
            InputMessageData::SolidColor {
                priority, color, ..
            } => Self {
                priority: *priority,
                duration_ms,
                owner: None,
                component_id: msg.component(),
                origin,
                active,
                visible,
                value: Some(color.into()),
            },
            InputMessageData::Image { priority, .. }
            | InputMessageData::LedColors { priority, .. }
            | InputMessageData::Effect { priority, .. } => Self {
                priority: *priority,
                duration_ms,
                owner: None,
                component_id: msg.component(),
                origin,
                active,
                visible,
                value: None,
            },
            InputMessageData::Clear { .. } | InputMessageData::ClearAll { .. } => {
                panic!("cannot create PriorityInfo for InputMessage")
            }
        }
    }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct LedColor {
    pub rgb: [u8; 3],
    pub hsl: (u16, f32, f32),
}

impl From<&Color> for LedColor {
    fn from(c: &Color) -> Self {
        let hsl = color_to_hsl(*c);

        Self {
            rgb: [c.red, c.green, c.blue],
            hsl: (
                (hsl.hue.into_positive_degrees() * 100.) as u16,
                hsl.saturation,
                hsl.lightness,
            ),
        }
    }
}

pub fn i32_to_duration(d: Option<i32>) -> Option<chrono::Duration> {
    if let Some(d) = d {
        if d <= 0 {
            None
        } else {
            Some(chrono::Duration::milliseconds(d as _))
        }
    } else {
        None
    }
}