Documentation
use super::{Text, Widget};
use crate::style::BoxStyle;
use crate::{MessageQueue, impl_style};
use agape_core::GlobalId;
use agape_layout::{BlockLayout, Layout};
use agape_renderer::Renderer;
use agape_renderer::rect::Rect;

type Callback = Box<dyn FnMut(&mut MessageQueue)>;

// TODO: add prefix and suffix icon
/// A widget that reacts to click and hover gestures.
///
/// # Example
/// ```
/// use agape::widgets::Button;
///
/// Button::text("Sign up")
///     .on_hover(|_| println!("About to sign up!"))
///     .on_click(|_| println!("Signed up!!!!"));
/// ```
pub struct Button<W> {
    id: GlobalId,
    child: W,
    style: BoxStyle,
    padding: u32,
    hover_callback: Option<Callback>,
    click_callback: Option<Callback>,
}

impl Button<Text> {
    pub fn text(text: &str) -> Self {
        Self::new(Text::new(text))
    }
}

impl<W> Button<W> {
    pub fn new(child: W) -> Self {
        Self {
            id: GlobalId::new(),
            style: BoxStyle::new(),
            child,
            padding: 0,
            hover_callback: None,
            click_callback: None,
        }
    }

    pub fn padding(mut self, padding: u32) -> Self {
        self.padding = padding;
        self
    }

    pub fn on_hover(mut self, f: impl FnMut(&mut MessageQueue) + Send + Sync + 'static) -> Self {
        self.hover_callback = Some(Box::new(f));
        self
    }

    pub fn on_click(mut self, f: impl FnMut(&mut MessageQueue) + Send + Sync + 'static) -> Self {
        self.click_callback = Some(Box::new(f));
        self
    }

    impl_style!();
}

impl<W: Widget> Widget for Button<W> {
    fn id(&self) -> GlobalId {
        self.id
    }

    fn traverse(&mut self, f: &mut dyn FnMut(&mut dyn Widget)) {
        f(&mut self.child);
        self.child.traverse(f);
    }

    fn children(&self) -> Vec<&dyn Widget> {
        vec![&self.child]
    }

    fn click(&mut self, messages: &mut MessageQueue) {
        if let Some(f) = &mut self.click_callback {
            f(messages);
        }
    }

    fn hover(&mut self, messages: &mut MessageQueue) {
        if let Some(f) = &mut self.hover_callback {
            f(messages);
        }
    }

    fn layout(&self, renderer: &mut Renderer) -> Box<dyn Layout> {
        let child = self.child.layout(renderer);
        let mut layout = BlockLayout::new(child);
        layout.id = self.id;
        layout.padding = self.padding;
        layout.intrinsic_size = self.style.intrinsic_size;
        Box::new(layout)
    }

    fn render(&self, renderer: &mut Renderer, layout: &dyn Layout) {
        let layout = layout.get(self.id).unwrap();
        let size = layout.size();
        let position = layout.position();
        let mut rect = Rect::new()
            .size(size.width, size.height)
            .position(position.x, position.y)
            .corner_radius(self.style.corner_radius)
            .color(self.style.background_color.clone());

        rect.border = self.style.border.clone();
        renderer.draw_rect(rect);
        self.child.render(renderer, layout);
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::widgets::Rect;
    use agape_core::{Color, Size};
    use agape_layout::solve_layout;

    #[test]
    fn traverse() {
        let mut button = Button::new(Rect::default());
        let mut ids = vec![];
        button.traverse(&mut |w| {
            ids.push(w.id());
        });
        assert_eq!(ids.len(), 1);
    }

    #[test]
    fn render_child() {
        let button = Button::new(
            Rect::new()
                .fixed(100.0, 100.0)
                .background_color(Color::rgb(53, 102, 145)),
        )
        .fixed(100.0, 100.0);

        let mut renderer = Renderer::new();
        renderer.resize(100, 100);
        let mut layout = button.layout(&mut renderer);
        solve_layout(layout.as_mut(), Size::default());
        button.render(&mut renderer, layout.as_ref());

        for pixel in renderer.pixmap().pixels() {
            assert_eq!(pixel.red(), 53);
            assert_eq!(pixel.green(), 102);
            assert_eq!(pixel.blue(), 145);
        }
    }
}