speedtest-tui 0.1.1

A terminal-based network speed test tool with real-time gauges and graphs
use ratatui::style::Color;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum ThemeName {
    #[default]
    Dark,
    Light,
    Ocean,
    Neon,
}

impl ThemeName {
    #[allow(dead_code)]
    pub fn all() -> Vec<ThemeName> {
        vec![
            ThemeName::Dark,
            ThemeName::Light,
            ThemeName::Ocean,
            ThemeName::Neon,
        ]
    }

    #[allow(dead_code)]
    pub fn next(&self) -> ThemeName {
        match self {
            ThemeName::Dark => ThemeName::Light,
            ThemeName::Light => ThemeName::Ocean,
            ThemeName::Ocean => ThemeName::Neon,
            ThemeName::Neon => ThemeName::Dark,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            ThemeName::Dark => "dark",
            ThemeName::Light => "light",
            ThemeName::Ocean => "ocean",
            ThemeName::Neon => "neon",
        }
    }
}

impl std::str::FromStr for ThemeName {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "dark" => Ok(ThemeName::Dark),
            "light" => Ok(ThemeName::Light),
            "ocean" => Ok(ThemeName::Ocean),
            "neon" => Ok(ThemeName::Neon),
            _ => Err(format!("Unknown theme: {}", s)),
        }
    }
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct Theme {
    pub name: ThemeName,
    pub background: Color,
    pub foreground: Color,
    pub primary: Color,
    pub secondary: Color,
    pub accent: Color,
    pub success: Color,
    pub warning: Color,
    pub error: Color,
    pub muted: Color,
    pub border: Color,
    pub highlight: Color,
    pub gauge_gradient: Vec<Color>,
}

impl Theme {
    pub fn dark() -> Self {
        Self {
            name: ThemeName::Dark,
            background: Color::Rgb(17, 17, 27),
            foreground: Color::Rgb(205, 214, 244),
            primary: Color::Rgb(137, 180, 250),
            secondary: Color::Rgb(203, 166, 247),
            accent: Color::Rgb(249, 226, 175),
            success: Color::Rgb(166, 227, 161),
            warning: Color::Rgb(249, 226, 175),
            error: Color::Rgb(243, 139, 168),
            muted: Color::Rgb(108, 112, 134),
            border: Color::Rgb(69, 71, 90),
            highlight: Color::Rgb(49, 50, 68),
            gauge_gradient: vec![
                Color::Rgb(166, 227, 161), // Green
                Color::Rgb(249, 226, 175), // Yellow
                Color::Rgb(250, 179, 135), // Orange
                Color::Rgb(243, 139, 168), // Red
            ],
        }
    }

    pub fn light() -> Self {
        Self {
            name: ThemeName::Light,
            background: Color::Rgb(239, 241, 245),
            foreground: Color::Rgb(76, 79, 105),
            primary: Color::Rgb(30, 102, 245),
            secondary: Color::Rgb(136, 57, 239),
            accent: Color::Rgb(223, 142, 29),
            success: Color::Rgb(64, 160, 43),
            warning: Color::Rgb(223, 142, 29),
            error: Color::Rgb(210, 15, 57),
            muted: Color::Rgb(156, 160, 176),
            border: Color::Rgb(188, 192, 204),
            highlight: Color::Rgb(220, 224, 232),
            gauge_gradient: vec![
                Color::Rgb(64, 160, 43),  // Green
                Color::Rgb(223, 142, 29), // Yellow
                Color::Rgb(254, 100, 11), // Orange
                Color::Rgb(210, 15, 57),  // Red
            ],
        }
    }

    pub fn ocean() -> Self {
        Self {
            name: ThemeName::Ocean,
            background: Color::Rgb(13, 17, 23),
            foreground: Color::Rgb(201, 209, 217),
            primary: Color::Rgb(88, 166, 255),
            secondary: Color::Rgb(121, 192, 255),
            accent: Color::Rgb(255, 166, 87),
            success: Color::Rgb(63, 185, 80),
            warning: Color::Rgb(210, 153, 34),
            error: Color::Rgb(248, 81, 73),
            muted: Color::Rgb(110, 118, 129),
            border: Color::Rgb(48, 54, 61),
            highlight: Color::Rgb(33, 38, 45),
            gauge_gradient: vec![
                Color::Rgb(63, 185, 80),  // Green
                Color::Rgb(210, 153, 34), // Yellow
                Color::Rgb(255, 166, 87), // Orange
                Color::Rgb(248, 81, 73),  // Red
            ],
        }
    }

    pub fn neon() -> Self {
        Self {
            name: ThemeName::Neon,
            background: Color::Rgb(10, 10, 20),
            foreground: Color::Rgb(230, 230, 250),
            primary: Color::Rgb(0, 255, 255),
            secondary: Color::Rgb(255, 0, 255),
            accent: Color::Rgb(255, 255, 0),
            success: Color::Rgb(0, 255, 128),
            warning: Color::Rgb(255, 200, 0),
            error: Color::Rgb(255, 50, 100),
            muted: Color::Rgb(100, 100, 120),
            border: Color::Rgb(60, 60, 80),
            highlight: Color::Rgb(30, 30, 50),
            gauge_gradient: vec![
                Color::Rgb(0, 255, 128), // Neon green
                Color::Rgb(255, 255, 0), // Yellow
                Color::Rgb(255, 128, 0), // Orange
                Color::Rgb(255, 0, 128), // Pink
            ],
        }
    }

    pub fn from_name(name: ThemeName) -> Self {
        match name {
            ThemeName::Dark => Self::dark(),
            ThemeName::Light => Self::light(),
            ThemeName::Ocean => Self::ocean(),
            ThemeName::Neon => Self::neon(),
        }
    }

    pub fn speed_color(&self, normalized: f64) -> Color {
        let n = normalized.clamp(0.0, 1.0);
        let idx = (n * (self.gauge_gradient.len() - 1) as f64) as usize;
        let idx = idx.min(self.gauge_gradient.len() - 1);
        self.gauge_gradient[idx]
    }
}

impl Default for Theme {
    fn default() -> Self {
        Self::dark()
    }
}

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

    #[test]
    fn test_theme_names() {
        assert_eq!(ThemeName::Dark.as_str(), "dark");
        assert_eq!(ThemeName::Light.as_str(), "light");
        assert_eq!(ThemeName::Ocean.as_str(), "ocean");
        assert_eq!(ThemeName::Neon.as_str(), "neon");
    }

    #[test]
    fn test_theme_cycle() {
        let theme = ThemeName::Dark;
        assert_eq!(theme.next(), ThemeName::Light);
        assert_eq!(ThemeName::Neon.next(), ThemeName::Dark);
    }

    #[test]
    fn test_speed_color() {
        let theme = Theme::dark();
        let _ = theme.speed_color(0.0);
        let _ = theme.speed_color(0.5);
        let _ = theme.speed_color(1.0);
    }
}