use std::{
    io::{self, stdout, Stdout},
    rc::Rc,
    time::Duration,
};

use anyhow::Result;
use crossterm::{
    event::{self, Event, KeyCode, KeyEventKind},
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
    ExecutableCommand,
};
use ratatui::{
    prelude::*,
    widgets::{block::Title, *},
};

fn main() -> Result<()> {
    App::run()
}

struct App {
    term: Term,
    should_quit: bool,
    state: AppState,
}

#[derive(Debug, Default, Clone, Copy)]
struct AppState {
    progress1: u16,
    progress2: u16,
    progress3: f64,
    progress4: u16,
}

impl App {
    fn run() -> Result<()> {
        // run at ~10 fps minus the time it takes to draw
        let timeout = Duration::from_secs_f32(1.0 / 10.0);
        let mut app = Self::start()?;
        while !app.should_quit {
            app.update();
            app.draw()?;
            app.handle_events(timeout)?;
        }
        app.stop()?;
        Ok(())
    }

    fn start() -> Result<Self> {
        Ok(App {
            term: Term::start()?,
            should_quit: false,
            state: AppState {
                progress1: 0,
                progress2: 0,
                progress3: 0.0,
                progress4: 0,
            },
        })
    }

    fn stop(&mut self) -> Result<()> {
        Term::stop()?;
        Ok(())
    }

    fn update(&mut self) {
        self.state.progress1 = (self.state.progress1 + 4).min(100);
        self.state.progress2 = (self.state.progress2 + 3).min(100);
        self.state.progress3 = (self.state.progress3 + 0.02).min(1.0);
        self.state.progress4 = (self.state.progress4 + 1).min(100);
    }

    fn draw(&mut self) -> Result<()> {
        self.term.draw(|frame| {
            let state = self.state;
            let layout = Self::equal_layout(frame);
            Self::render_gauge1(state.progress1, frame, layout[0]);
            Self::render_gauge2(state.progress2, frame, layout[1]);
            Self::render_gauge3(state.progress3, frame, layout[2]);
            Self::render_gauge4(state.progress4, frame, layout[3]);
        })?;
        Ok(())
    }

    fn handle_events(&mut self, timeout: Duration) -> io::Result<()> {
        if event::poll(timeout)? {
            if let Event::Key(key) = event::read()? {
                if key.kind == KeyEventKind::Press {
                    if let KeyCode::Char('q') = key.code {
                        self.should_quit = true;
                    }
                }
            }
        }
        Ok(())
    }

    fn equal_layout(frame: &Frame) -> Rc<[Rect]> {
        Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Percentage(25),
                Constraint::Percentage(25),
                Constraint::Percentage(25),
                Constraint::Percentage(25),
            ])
            .split(frame.size())
    }

    fn render_gauge1(progress: u16, frame: &mut Frame, area: Rect) {
        let title = Self::title_block("Gauge with percentage progress");
        let gauge = Gauge::default()
            .block(title)
            .gauge_style(Style::new().light_red())
            .percent(progress);
        frame.render_widget(gauge, area);
    }

    fn render_gauge2(progress: u16, frame: &mut Frame, area: Rect) {
        let title = Self::title_block("Gauge with percentage progress and custom label");
        let label = format!("{}/100", progress);
        let gauge = Gauge::default()
            .block(title)
            .gauge_style(Style::new().blue().on_light_blue())
            .percent(progress)
            .label(label);
        frame.render_widget(gauge, area);
    }

    fn render_gauge3(progress: f64, frame: &mut Frame, area: Rect) {
        let title =
            Self::title_block("Gauge with ratio progress, custom label with style, and unicode");
        let label = Span::styled(
            format!("{:.2}%", progress * 100.0),
            Style::new().red().italic().bold(),
        );
        let gauge = Gauge::default()
            .block(title)
            .gauge_style(Style::default().fg(Color::Yellow))
            .ratio(progress)
            .label(label)
            .use_unicode(true);
        frame.render_widget(gauge, area);
    }

    fn render_gauge4(progress: u16, frame: &mut Frame, area: Rect) {
        let title = Self::title_block("Gauge with percentage progress and label");
        let label = format!("{}/100", progress);
        let gauge = Gauge::default()
            .block(title)
            .gauge_style(Style::new().green().italic())
            .percent(progress)
            .label(label);
        frame.render_widget(gauge, area);
    }

    fn title_block(title: &str) -> Block {
        let title = Title::from(title).alignment(Alignment::Center);
        Block::default().title(title).borders(Borders::TOP)
    }
}

struct Term {
    terminal: Terminal<CrosstermBackend<Stdout>>,
}

impl Term {
    pub fn start() -> io::Result<Term> {
        stdout().execute(EnterAlternateScreen)?;
        enable_raw_mode()?;
        let terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
        Ok(Self { terminal })
    }

    pub fn stop() -> io::Result<()> {
        disable_raw_mode()?;
        stdout().execute(LeaveAlternateScreen)?;
        Ok(())
    }

    fn draw(&mut self, frame: impl FnOnce(&mut Frame)) -> Result<()> {
        self.terminal.draw(frame)?;
        Ok(())
    }
}
