fser 0.1.1

A dumbed-down TUI EPUB reader.
use std::io::Result;

use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind};
use ratatui::{
    DefaultTerminal, Frame,
    layout::{Constraint, Layout},
    style::Stylize,
    widgets::Paragraph,
};
use rbook::Epub;

use crate::reader::render_reader;

pub struct Fser {
    pub ebook: Epub,
    pub scroll: u16,
    pub current_content: Option<String>,
    pub cached_lines: Option<Vec<String>>,
    pub cached_width: u16,
    ebook_title: String,
    page_index: usize,
    state: FserState,
}

#[derive(PartialEq, Eq)]
enum FserState {
    Reading,
    Quit,
}

impl Fser {
    pub fn new(ebook: Epub) -> Self {
        let ebook_title = if let Some(title) = ebook.metadata().title() {
            title.value().to_string()
        } else {
            "Title-less book".to_string()
        };
        Self {
            ebook,
            scroll: 0,
            current_content: None,
            cached_lines: None,
            cached_width: 0,
            ebook_title,
            page_index: 0,
            state: FserState::Reading,
        }
    }

    pub fn spine_len(&self) -> usize {
        self.ebook.spine().len()
    }

    pub fn load_chapter(&mut self) -> Option<String> {
        let mut reader = self.ebook.reader();
        let content = reader.read(self.page_index).ok()?;
        Some(content.content().to_owned())
    }

    fn next_chapter(&mut self) {
        if self.page_index + 1 < self.spine_len() {
            self.page_index += 1;
            self.scroll = 0;
            self.current_content = self.load_chapter();
            self.cached_lines = None;
        }
    }

    fn prev_chapter(&mut self) {
        self.page_index = self.page_index.saturating_sub(1);
        self.scroll = 0;
        self.current_content = self.load_chapter();
        self.cached_lines = None;
    }

    pub fn run(&mut self, term: &mut DefaultTerminal) -> Result<()> {
        while self.state != FserState::Quit {
            term.draw(|frame| self.render(frame))?;
            match event::read()? {
                Event::Key(key) if key.kind == KeyEventKind::Press => {
                    self.handle_keys(&key);
                }
                Event::Resize(_, _) => {}
                _ => {}
            }
        }
        Ok(())
    }

    pub fn render(&mut self, frame: &mut Frame) {
        let area = frame.area();
        if area.width < 40 || area.height < 20 {
            // vv terminal too small text
            let ttst = Paragraph::new(format!(
                "Terminal too small\nRequired 40x20\nCurrent {}x{}",
                area.width, area.height
            ))
            .red()
            .centered();
            frame.render_widget(ttst, area);
        } else {
            let main_layout = Layout::vertical([
                Constraint::Length(1),
                Constraint::Fill(1),
                Constraint::Length(1),
            ])
            .split(area);
            let header = Layout::horizontal([Constraint::Length(15), Constraint::Fill(1)])
                .split(main_layout[0]);
            let name_and_ver = Paragraph::new("fser v0.1.0").bold();
            let ebook_title = Paragraph::new(self.ebook_title.clone()).right_aligned();
            let position_track =
                Paragraph::new(format!("{}/{}", self.page_index + 1, self.spine_len())).centered();
            frame.render_widget(name_and_ver, header[0]);
            frame.render_widget(ebook_title, header[1]);
            frame.render_widget(position_track, main_layout[2]);
            match self.state {
                FserState::Reading => render_reader(self, frame, main_layout[1]),
                _ => {}
            }
        }
    }

    pub fn handle_keys(&mut self, key: &KeyEvent) {
        match key.code {
            KeyCode::Char('q') => self.state = FserState::Quit,
            _ => match self.state {
                FserState::Reading => match key.code {
                    KeyCode::Char('n') => self.next_chapter(),
                    KeyCode::Char('p') => self.prev_chapter(),
                    KeyCode::Char('j') | KeyCode::Down => self.scroll += 1,
                    KeyCode::Char('d') => self.scroll += 5,
                    KeyCode::Char('k') | KeyCode::Up => self.scroll = self.scroll.saturating_sub(1),
                    KeyCode::Char('u') => self.scroll = self.scroll.saturating_sub(5),
                    _ => {}
                },
                _ => todo!(),
            },
        };
    }
}