use std::time::Duration;
use badterm_core::screen::{Cell, Color, Screen};
use super::*;
pub const TIME: Duration = Duration::from_secs(2);
pub const COLS: usize = 80;
pub const ROWS: usize = 25;
const PW: usize = COLS;
const PH: usize = ROWS * 2;
const LETTERS: [(char, [&str; 12]); 7] = [
(
'B',
[
"#######..",
"########.",
"###...###",
"###...###",
"###..###.",
"#######..",
"########.",
"###...###",
"###...###",
"###...###",
"########.",
"#######..",
],
),
(
'A',
[
"..#####..",
".#######.",
"###...###",
"###...###",
"###...###",
"#########",
"#########",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
],
),
(
'D',
[
"#######..",
"########.",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"########.",
"#######..",
],
),
(
'T',
[
"#########",
"#########",
"...###...",
"...###...",
"...###...",
"...###...",
"...###...",
"...###...",
"...###...",
"...###...",
"...###...",
"...###...",
],
),
(
'E',
[
"#########",
"#########",
"###......",
"###......",
"###......",
"#######..",
"#######..",
"###......",
"###......",
"###......",
"#########",
"#########",
],
),
(
'R',
[
"#######..",
"########.",
"###...###",
"###...###",
"###...###",
"########.",
"#######..",
"###.###..",
"###..###.",
"###...###",
"###...###",
"###...###",
],
),
(
'M',
[
"##.....##",
"###...###",
"####.####",
"###.#.###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
"###...###",
],
),
];
const RAMP: [u8; 12] = [15, 11, 11, 11, 11, 11, 3, 3, 3, 1, 1, 1];
const LOGO_Y: usize = 10;
const SHADOW: u8 = 8;
struct Canvas([[u8; PW]; PH]);
impl Canvas {
fn set(&mut self, x: usize, y: usize, c: u8) {
if x < PW && y < PH {
self.0[y][x] = c;
}
}
fn draw(&self, s: &mut Screen) {
for y in 0..ROWS {
for x in 0..COLS {
let (top, bottom) = (self.0[y * 2][x], self.0[y * 2 + 1][x]);
let cell = match (top, bottom) {
(0, 0) => Cell::new(b' ', Color::Pc(7), Color::Pc(0)),
(t, b) if t == b => Cell::new(0xDB, Color::Pc(t), Color::Pc(0)),
(0, b) => Cell::new(0xDC, Color::Pc(b), Color::Pc(0)),
(t, b) => Cell::new(0xDF, Color::Pc(t), Color::Pc(b)),
};
if let Some(c) = s.cell_mut(x, y) {
*c = cell;
}
}
}
}
}
fn logo() -> Vec<(usize, usize)> {
let mut lit = Vec::new();
for (i, ch) in "BADTERM".chars().enumerate() {
let rows = LETTERS.iter().find(|(c, _)| *c == ch).map(|(_, r)| r).expect("a letter of the name");
for (y, row) in rows.iter().enumerate() {
for (x, b) in row.bytes().enumerate() {
if b == b'#' {
lit.push((i * 10 + x, y));
}
}
}
}
lit
}
fn rule(s: &mut Screen, y: usize) {
for x in 0..COLS {
let d = ((x as f64 + 0.5) - COLS as f64 / 2.0).abs() / (COLS as f64 / 2.0);
let (ch, fg) = match d {
d if d < 0.18 => (0xC4, 11),
d if d < 0.42 => (0xC4, 3),
d if d < 0.66 => (0xC4, 1),
d if d < 0.80 => (0xC4, 8),
d if d < 0.88 => (0xF9, 8),
d if d < 0.95 && x % 2 == 0 => (0xFA, 8),
_ => continue,
};
s.draw_text(x, y, &[ch], Color::Pc(fg), Color::Pc(0));
}
}
fn centered(s: &mut Screen, y: usize, parts: &[(&str, u8)]) {
let len: usize = parts.iter().map(|(t, _)| t.chars().count()).sum();
let mut x = (COLS - len) / 2;
for (text, fg) in parts {
label(s, x, y, text, Color::Pc(*fg), Color::Pc(0));
x += text.chars().count();
}
}
pub fn draw(s: &mut Screen) {
s.fill_rect(0, 0, COLS, ROWS, Cell::new(b' ', Color::Pc(7), Color::Pc(0)));
let mut c = Canvas([[0; PW]; PH]);
let lit = logo();
let width = lit.iter().map(|&(x, _)| x + 1).max().unwrap_or(0);
let x0 = (PW - width) / 2;
for &(x, y) in &lit {
c.set(x0 + x + 1, LOGO_Y + y + 1, SHADOW);
}
for &(x, y) in &lit {
c.set(x0 + x, LOGO_Y + y, RAMP[y]);
}
c.draw(s);
let (top, bottom) = (LOGO_Y / 2 - 2, (LOGO_Y + 13) / 2 + 2);
rule(s, top);
rule(s, bottom);
let version = format!("v{}", env!("CARGO_PKG_VERSION"));
centered(s, bottom + 3, &[("t h e b a d a s s t e r m i n a l", 7)]);
centered(s, bottom + 6, &[(version.as_str(), 15), (" \u{00B7} ", 8), ("badterm.com", 11)]);
let copyright = [("Copyright (C) 2026 Bad Cluster", 7), (" \u{00B7} ", 8), ("free software, GNU GPL v3+", 7)];
centered(s, bottom + 8, ©right);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fits() {
let mut s = Screen::new(COLS, ROWS, 0);
draw(&mut s);
let rows: Vec<String> = (0..ROWS).map(|y| Screen::row_text(s.row(y))).collect();
assert!(rows.iter().any(|r| r.contains(env!("CARGO_PKG_VERSION"))), "{rows:#?}");
let logo_row = &rows[LOGO_Y / 2 + 1];
assert!(logo_row.contains('\u{2588}') && !logo_row.starts_with('\u{2588}'), "{rows:#?}");
for (ch, rows) in LETTERS {
assert!(rows.iter().all(|r| r.len() == 9), "{ch}");
}
}
}