//! # [Ratatui] Widgets implementation examples
//!
//! This example demonstrates various ways to implement widget traits in Ratatui on a type, a
//! reference, and a mutable reference. It also shows how to use the `WidgetRef` trait to render
//! boxed widgets.
//!
//! The latest version of this example is available in the [examples] folder in the repository.
//!
//! Please note that the examples are designed to be run against the `main` branch of the Github
//! repository. This means that you may not be able to compile with the latest release version on
//! crates.io, or the one that you have installed locally.
//!
//! See the [examples readme] for more information on finding examples that match the version of the
//! library you are using.
//!
//! [Ratatui]: https://github.com/ratatui-org/ratatui
//! [examples]: https://github.com/ratatui-org/ratatui/blob/main/examples
//! [examples readme]: https://github.com/ratatui-org/ratatui/blob/main/examples/README.md
use std::time::{Duration, Instant};

use color_eyre::Result;
use crossterm::event::{self, Event, KeyCode};
use ratatui::{
    buffer::Buffer,
    layout::{Constraint, Layout, Position, Rect, Size},
    style::{Color, Style},
    widgets::{Widget, WidgetRef},
    DefaultTerminal,
};

fn main() -> Result<()> {
    color_eyre::install()?;
    let terminal = ratatui::init();
    let result = App::default().run(terminal);
    ratatui::restore();
    result
}

#[derive(Default)]
struct App {
    should_quit: bool,
    timer: Timer,
    #[cfg(feature = "unstable-widget-ref")]
    boxed_squares: BoxedSquares,
    green_square: RightAlignedSquare,
}

impl App {
    fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
        while !self.should_quit {
            self.draw(&mut terminal)?;
            self.handle_events()?;
        }
        Ok(())
    }

    fn draw(&mut self, tui: &mut DefaultTerminal) -> Result<()> {
        tui.draw(|frame| frame.render_widget(self, frame.area()))?;
        Ok(())
    }

    fn handle_events(&mut self) -> Result<()> {
        // Handle events at least 50 frames per second (gifs are usually 50fps)
        let timeout = Duration::from_secs_f64(1.0 / 50.0);
        if !event::poll(timeout)? {
            return Ok(());
        }
        if let Event::Key(key) = event::read()? {
            match key.code {
                KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
                _ => {}
            }
        }
        Ok(())
    }
}

/// Implement the `Widget` trait on a mutable reference to the `App` type.
///
/// This allows the `App` type to be rendered as a widget. The `App` type owns several other widgets
/// that are rendered as part of the app. The `Widget` trait is implemented on a mutable reference
/// to the `App` type, which allows this to be rendered without consuming the `App` type, and allows
/// the sub-widgets to be mutable.
impl Widget for &mut App {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let constraints = Constraint::from_lengths([1, 1, 2, 1]);
        let [greeting, timer, squares, position] = Layout::vertical(constraints).areas(area);

        // render an ephemeral greeting widget
        Greeting::new("Ratatui!").render(greeting, buf);

        // render a reference to the timer widget
        self.timer.render(timer, buf);

        // render a boxed widget containing red and blue squares
        #[cfg(feature = "unstable-widget-ref")]
        self.boxed_squares.render(squares, buf);

        // render a mutable reference to the green square widget
        self.green_square.render(squares, buf);
        // Display the dynamically updated position of the green square
        let square_position = format!("Green square is at {}", self.green_square.last_position);
        square_position.render(position, buf);
    }
}

/// An ephemeral greeting widget.
///
/// This widget is implemented on the type itself, which means that it is consumed when it is
/// rendered. This is useful for widgets that are cheap to create, don't need to be reused, and
/// don't need to store any state between renders. This is the simplest way to implement a widget in
/// Ratatui, but in most cases, it is better to implement the `Widget` trait on a reference to the
/// type, as shown in the other examples below.
///
/// This was the way most widgets were implemented in Ratatui before `Widget` was implemented on
/// references in [PR #903] (merged in Ratatui 0.26.0).
///
/// [PR #903]: https://github.com/ratatui-org/ratatui/pull/903
struct Greeting {
    name: String,
}

impl Greeting {
    fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
        }
    }
}

impl Widget for Greeting {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let greeting = format!("Hello, {}!", self.name);
        greeting.render(area, buf);
    }
}

/// A timer widget that displays the elapsed time since the timer was started.
#[derive(Debug)]
struct Timer {
    start: Instant,
}

impl Default for Timer {
    fn default() -> Self {
        Self {
            start: Instant::now(),
        }
    }
}

/// This implements `Widget` on a reference to the type, which means that it can be reused and
/// doesn't need to be consumed when it is rendered. This is useful for widgets that need to store
/// state and be updated over time.
///
/// This approach was probably always available in Ratatui, but it wasn't widely used until `Widget`
/// was implemented on references in [PR #903] (merged in Ratatui 0.26.0). This is because all the
/// built-in widgets previously would consume themselves when rendered.
impl Widget for &Timer {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let elapsed = self.start.elapsed().as_secs_f32();
        let message = format!("Elapsed: {elapsed:.1?}s");
        message.render(area, buf);
    }
}

/// A widget that contains a list of several different widgets.
struct BoxedSquares {
    squares: Vec<Box<dyn WidgetRef>>,
}

impl Default for BoxedSquares {
    fn default() -> Self {
        let red_square: Box<dyn WidgetRef> = Box::new(RedSquare);
        let blue_square: Box<dyn WidgetRef> = Box::new(BlueSquare);
        Self {
            squares: vec![red_square, blue_square],
        }
    }
}

/// A widget that renders a red square.
struct RedSquare;

/// A widget that renders a blue square.
struct BlueSquare;

/// This implements the `Widget` trait on a reference to the type. It contains a list of boxed
/// widgets that implement the `WidgetRef` trait. This is useful for widgets that contain a list of
/// other widgets that can be different types.
impl Widget for &BoxedSquares {
    fn render(self, area: Rect, buf: &mut Buffer) {
        let constraints = vec![Constraint::Length(4); self.squares.len()];
        let areas = Layout::horizontal(constraints).split(area);
        for (widget, area) in self.squares.iter().zip(areas.iter()) {
            widget.render_ref(*area, buf);
        }
    }
}

/// `RedSquare` and `BlueSquare` are widgets that render a red and blue square, respectively. They
/// implement the `WidgetRef` trait instead of the `Widget` trait, which which allows them to be
/// rendered as boxed widgets. It's not possible to use Widget for this as a dynamic reference to a
/// widget cannot generally be moved out of the box.
impl WidgetRef for RedSquare {
    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
        fill(area, buf, "█", Color::Red);
    }
}

impl WidgetRef for BlueSquare {
    fn render_ref(&self, area: Rect, buf: &mut Buffer) {
        fill(area, buf, "█", Color::Blue);
    }
}

/// A widget that renders a green square aligned to the right of the area.
#[derive(Default)]
struct RightAlignedSquare {
    last_position: Position,
}

/// This widget is implemented on a mutable reference to the type, which means that it can store
/// state and update it when it is rendered. This is useful for widgets that need to store the
/// result of some calculation that can only be done when the widget is rendered.
///
/// The x and y coordinates of the square are stored in the widget and updated when the widget is
/// rendered. This allows the square to be aligned to the right of the area. These coordinates could
/// be used to perform hit testing (e.g. checking if a mouse click is inside the square). This app
/// just displays the coordinates as a string.
///
/// This approach was probably always available in Ratatui, but it wasn't widely used either. This
/// is an alternative to implementing the `StatefulWidget` trait, for situations where you want to
/// store the state in the widget itself instead of a separate struct.
impl Widget for &mut RightAlignedSquare {
    /// Render a green square aligned to the right of the area and store the position.
    fn render(self, area: Rect, buf: &mut Buffer) {
        const WIDTH: u16 = 4;
        let x = area.right() - WIDTH; // Align to the right
        self.last_position = Position { x, y: area.y };
        let size = Size::new(WIDTH, area.height);
        let area = Rect::from((self.last_position, size));
        fill(area, buf, "█", Color::Green);
    }
}

/// Fill the area with the specified symbol and style.
///
/// This probably should be a method on the `Buffer` type, but it is defined here for simplicity.
/// <https://github.com/ratatui-org/ratatui/issues/1146>
fn fill<S: Into<Style>>(area: Rect, buf: &mut Buffer, symbol: &str, style: S) {
    let style = style.into();
    for y in area.top()..area.bottom() {
        for x in area.left()..area.right() {
            buf[(x, y)].set_symbol(symbol).set_style(style);
        }
    }
}
