rusty-ping 0.1.0

A pretty ping/ICMP utility
use crate::state::State;
use crate::stats::Stats;
use std::net::IpAddr;

struct SymbolTableElement {
    fg: &'static str,
    bg: &'static str,
    character: &'static str,
}

const GREEN_FG: &str = "\x1b[32m";
const ORANGE_FG: &str = "\x1b[38;5;208m";
const RED_FG: &str = "\x1b[31m";
const BLACK_BG: &str = "\x1b[40m";
const GREEN_BG: &str = "\x1b[42m";
const ORANGE_BG: &str = "\x1b[48;5;208m";
const ERASE_LINE_END: &str = "\x1b[0K";
const RESET: &str = "\x1b[0m";

const CHARS_TABLE: [SymbolTableElement; 25] = [
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: GREEN_FG,
        bg: BLACK_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: ORANGE_FG,
        bg: GREEN_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: ORANGE_BG,
        character: "",
    },
    SymbolTableElement {
        fg: RED_FG,
        bg: BLACK_BG,
        character: "?",
    },
];

#[allow(clippy::unusual_byte_groupings)]
const TO_HIDE_LEGEND_ELEMENTS: [u32; 26] = [
    0b000_000_000_000_0_000_000_000_000,
    0b000_000_000_000_1_000_000_000_000,
    0b000_000_001_000_0_000_100_000_000,
    0b000_000_100_000_1_000_001_000_000,
    0b000_010_000_100_0_001_000_010_000,
    0b000_100_010_000_1_000_010_001_000, // 5
    0b001_000_100_010_0_010_001_000_100,
    0b010_001_000_100_1_001_000_100_010,
    0b010_010_001_001_0_100_100_010_010,
    0b010_010_010_010_1_010_010_010_010,
    0b001_010_100_101_0_101_001_010_100, // 10
    0b010_101_010_100_1_001_010_101_010,
    0b010_101_010_101_0_101_010_101_010,
    0b011_010_101_010_1_010_101_010_110,
    0b011_010_101_011_0_110_101_010_110,
    0b011_101_011_010_1_010_110_101_110, // 15
    0b011_101_101_101_0_101_101_101_110,
    0b011_101_110_110_1_011_011_101_110,
    0b011_110_111_011_0_110_111_011_110,
    0b011_101_111_011_1_110_111_101_110,
    0b011_111_011_111_0_111_110_111_110, // 20
    0b011_111_110_111_1_111_011_111_110,
    0b011_111_111_111_0_111_111_111_110,
    0b011_111_111_111_1_111_111_111_110,
    0b111_111_111_111_1_111_111_111_110,
    0b111_111_111_111_1_111_111_111_111, // 25
];

const LEN_BY_NB_ELEMENT: [u32; 26] = [
    139, 133, 128, 122, 117, 111, 105, 100, 94, 88, 83, 78, 72, 66, 60, 55, 50, 44, 38, 32, 27, 21,
    16, 10, 4, 0,
];


pub fn print_status_line(target_str: &String, ip: &IpAddr) {
    println!("PING {} ({})", target_str, ip);
}

pub fn print_legend_line() {
    let nb_cols: u32 = if let Some((w, _)) = term_size::dimensions() {
        w as u32
    } else {
        0
    };

    let mut index = 0;
    while LEN_BY_NB_ELEMENT[index] > nb_cols {
        index += 1
    }

    let nb_elements_to_skip = index;

    for (index, element) in CHARS_TABLE.iter().enumerate() {
        if (TO_HIDE_LEGEND_ELEMENTS[nb_elements_to_skip]) & (1 << index) != 0 {
            continue;
        }
        print!(
            "{} {}{}{}\x1b[0m ",
            10 * index,
            element.fg,
            element.bg,
            element.character
        );
    }
    println!("{}", ERASE_LINE_END);
}

// TODO: Simplify this function
pub fn print_ping_line(states: &Vec<State>) {
    for state in states {
        match state {
            State::Received { rtt, .. } => {
                let (fg, bg, symbol) = get_symbol_and_colors_for_rrt(*rtt);
                print!("{}{}{}{}", fg, bg, symbol, RESET);
            }
            State::Lost => {
                let (fg, bg, symbol) = get_symbol_and_colors_for_rrt(250);
                print!("{}{}{}{}", fg, bg, symbol, RESET);
            }
            State::NotYetReceived { .. } => {
                // fg grey, bg black
                print!("\x1b[35;40m?\x1b[0m");
            }
            State::SendError => {
                print!("!");
            }
        }
    }

    println!("{}", ERASE_LINE_END);
}

fn get_symbol_and_colors_for_rrt(rrt: u64) -> (&'static str, &'static str, &'static str) {
    let index: usize = if rrt >= 240 { 24 } else { rrt / 10 } as usize;
    let SymbolTableElement { fg, bg, character } = CHARS_TABLE[index];

    (fg, bg, character)
}

pub fn print_stats(stats: &Stats) {
    let total = stats.lost + stats.received;
    let percent_lost = if total > 0 {
        (stats.lost as f64 / total as f64) * 100.0
    } else {
        0.0
    };

    let mad_rtt_rounded = stats.mad_rtt.round() as u64;
    let mean_rtt_round = stats.avg_rtt.round() as u32;

    println!(
        "{}/{} ({:.1}%) | ⌊{}⌋ ⌈{}⌉ [{}] [Δ{}]ms{}",
        stats.lost,
        total,
        percent_lost,
        stats.min_rtt,
        stats.max_rtt,
        mean_rtt_round,
        mad_rtt_rounded,
        ERASE_LINE_END
    );
}