nyaa 0.6.0

A tui tool for browsing and downloading torrents from nyaa.si
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
    layout::{Constraint, Rect},
    style::{Color, Stylize as _},
    text::{Line, Span, Text},
    widgets::{Row, Table},
    Frame,
};

use crate::app::{App, LoadType, Mode};

use super::{create_block, Widget};

pub struct CatEntry {
    name: &'static str,
    pub cfg: &'static str,
    pub id: usize,
    pub icon: CatIcon,
}

#[derive(Clone)]
pub struct CatIcon {
    pub label: &'static str,
    pub color: Color,
}

impl Default for CatIcon {
    fn default() -> Self {
        CatIcon {
            label: "???",
            color: Color::Gray,
        }
    }
}

impl CatEntry {
    const fn new(
        name: &'static str,
        cfg: &'static str,
        id: usize,
        label: &'static str,
        color: Color,
    ) -> Self {
        CatEntry {
            name,
            cfg,
            id,
            icon: CatIcon { label, color },
        }
    }
}

pub struct CatStruct {
    name: &'static str,
    pub entries: &'static [CatEntry],
}

impl CatStruct {
    pub fn find(&self, category: usize) -> Option<CatIcon> {
        self.entries
            .iter()
            .find(|e| e.id == category)
            .map(|e| e.icon.clone())
    }
}

pub static ALL: CatStruct = CatStruct {
    name: "All Categories",
    entries: &[CatEntry::new(
        "All Categories",
        "AllCategories",
        0,
        "---",
        Color::White,
    )],
};

pub static ANIME: CatStruct = CatStruct {
    name: "Anime",
    entries: &[
        CatEntry::new("All Anime", "AllAnime", 10, "Ani", Color::Gray),
        CatEntry::new(
            "English Translated",
            "AnimeEnglishTranslated",
            12,
            "Sub",
            Color::LightMagenta,
        ),
        CatEntry::new(
            "Non-English Translated",
            "AnimeNonEnglishTranslated",
            13,
            "Sub",
            Color::LightGreen,
        ),
        CatEntry::new("Raw", "AnimeRaw", 14, "Raw", Color::Gray),
        CatEntry::new(
            "Anime Music Video",
            "AnimeMusicVideo",
            11,
            "AMV",
            Color::Magenta,
        ),
    ],
};

pub static AUDIO: CatStruct = CatStruct {
    name: "Audio",
    entries: &[
        CatEntry::new("All Audio", "AllAudio", 20, "Aud", Color::Gray),
        CatEntry::new("Lossless", "AudioLossless", 21, "Aud", Color::Red),
        CatEntry::new("Lossy", "AudioLossy", 22, "Aud", Color::Yellow),
    ],
};

pub static LITERATURE: CatStruct = CatStruct {
    name: "Literature",
    entries: &[
        CatEntry::new("All Literature", "AllLiterature", 30, "Lit", Color::Gray),
        CatEntry::new(
            "English-Translated",
            "LitEnglishTranslated",
            31,
            "Lit",
            Color::LightGreen,
        ),
        CatEntry::new(
            "Non-English Translated",
            "LitEnglishTranslated",
            32,
            "Lit",
            Color::Yellow,
        ),
        CatEntry::new("Raw", "LitRaw", 33, "Lit", Color::Green),
    ],
};

pub static LIVE_ACTION: CatStruct = CatStruct {
    name: "Live Action",
    entries: &[
        CatEntry::new("All Live Action", "AllLiveAction", 40, "Liv", Color::Gray),
        CatEntry::new(
            "English-Translated",
            "LiveEnglishTranslated",
            41,
            "Liv",
            Color::Yellow,
        ),
        CatEntry::new(
            "Non-English Translated",
            "LiveNonEnglishTranslated",
            43,
            "Liv",
            Color::LightCyan,
        ),
        CatEntry::new(
            "Idol/Promo Video",
            "LiveIdolPromoVideo",
            42,
            "Liv",
            Color::LightYellow,
        ),
        CatEntry::new("Raw", "LiveRaw", 44, "Liv", Color::Gray),
    ],
};

pub static PICTURES: CatStruct = CatStruct {
    name: "Pictures",
    entries: &[
        CatEntry::new("All Pictures", "AllPictures", 50, "Pic", Color::Gray),
        CatEntry::new("Graphics", "PicGraphics", 51, "Pic", Color::LightMagenta),
        CatEntry::new("Photos", "PicPhotos", 52, "Pic", Color::Magenta),
    ],
};

pub static SOFTWARE: CatStruct = CatStruct {
    name: "Software",
    entries: &[
        CatEntry::new("All Software", "AllSoftware", 60, "Sof", Color::Gray),
        CatEntry::new("Applications", "SoftApplications", 61, "Sof", Color::Blue),
        CatEntry::new("Games", "SoftGames", 62, "Sof", Color::LightBlue),
    ],
};

pub static ALL_CATEGORIES: &[&CatStruct] = &[
    &ALL,
    &ANIME,
    &AUDIO,
    &LITERATURE,
    &LIVE_ACTION,
    &PICTURES,
    &SOFTWARE,
];

#[derive(Default)]
pub struct CategoryPopup {
    pub category: usize,
    pub major: usize,
    pub minor: usize,
}

impl Widget for CategoryPopup {
    fn draw(&self, f: &mut Frame, app: &App, area: Rect) {
        if let Some(cat) = ALL_CATEGORIES.get(self.major) {
            let mut tbl: Vec<Row> = ALL_CATEGORIES
                .iter()
                .enumerate()
                .map(|(i, e)| match i == self.major {
                    false => Row::new(Text::raw(format!("{}", e.name))),
                    true => Row::new(Text::raw(format!("{}", e.name)))
                        .bg(app.theme.solid_bg)
                        .fg(app.theme.solid_fg),
                })
                .collect();

            let cat_rows = cat.entries.iter().enumerate().map(|(i, e)| {
                let row = Row::new(vec![Line::from(vec![
                    Span::raw(match e.id == self.category {
                        true => "",
                        false => "   ",
                    }),
                    e.icon.label.fg(e.icon.color),
                    Span::raw(" "),
                    Span::raw(e.name),
                ])]);
                match i == self.minor {
                    true => row.bg(app.theme.hl_bg),
                    false => row,
                }
            });

            tbl.splice(self.major + 1..self.major + 1, cat_rows);

            let center = super::centered_rect(33, 14, area);
            let clear = super::centered_rect(center.width + 2, center.height, area);
            super::clear(f, clear, app.theme.bg);
            f.render_widget(
                Table::new(tbl, [Constraint::Percentage(100)])
                    .block(create_block(app.theme, true).title("Category")),
                center,
            );
        }
    }

    fn handle_event(&mut self, app: &mut App, e: &Event) {
        if let Event::Key(KeyEvent {
            code,
            kind: KeyEventKind::Press,
            ..
        }) = e
        {
            match code {
                KeyCode::Enter => {
                    if let Some(cat) = ALL_CATEGORIES.get(self.major) {
                        if let Some(item) = cat.entries.get(self.minor) {
                            self.category = item.id;
                        }
                    }
                    app.mode = Mode::Loading(LoadType::Categorizing);
                }
                KeyCode::Esc | KeyCode::Char('c') | KeyCode::Char('q') => {
                    app.mode = Mode::Normal;
                }
                KeyCode::Char('j') | KeyCode::Down => {
                    if let Some(cat) = ALL_CATEGORIES.get(self.major) {
                        self.minor = match self.minor + 1 >= cat.entries.len() {
                            true => 0,
                            false => self.minor + 1,
                        };
                    }
                }
                KeyCode::Char('k') | KeyCode::Up => {
                    if let Some(cat) = ALL_CATEGORIES.get(self.major) {
                        self.minor = match self.minor < 1 {
                            true => cat.entries.len() - 1,
                            false => self.minor - 1,
                        };
                    }
                }
                KeyCode::Char('G') => {
                    if let Some(cat) = ALL_CATEGORIES.get(self.major) {
                        self.minor = cat.entries.len() - 1;
                    }
                }
                KeyCode::Char('g') => {
                    self.minor = 0;
                }
                KeyCode::Tab | KeyCode::Char('J') => {
                    self.major = match self.major + 1 >= ALL_CATEGORIES.len() {
                        true => 0,
                        false => self.major + 1,
                    };
                    self.minor = 0;
                }
                KeyCode::BackTab | KeyCode::Char('K') => {
                    self.major = match self.major == 0 {
                        true => ALL_CATEGORIES.len() - 1,
                        false => self.major - 1,
                    };
                    self.minor = 0;
                }
                _ => {}
            }
        }
    }

    fn get_help() -> Option<Vec<(&'static str, &'static str)>> {
        Some(vec![
            ("Enter", "Confirm"),
            ("Esc, c, q", "Close"),
            ("j, ↓", "Down"),
            ("k, ↑", "Up"),
            ("g", "Top"),
            ("G", "Bottom"),
            ("Tab, J", "Next Tab"),
            ("S-Tab, K", "Prev Tab"),
        ])
    }
}