use crate::ui::{
    component::{Component, Event, EventCtx, Never},
    display::toif::Icon,
    geometry::{LinearPlacement, Offset, Rect, CENTER},
};

use super::theme;

pub struct ScrollBar {
    area: Rect,
    layout: LinearPlacement,
    pub page_count: usize,
    pub active_page: usize,
}

impl ScrollBar {
    pub const DOT_SIZE: i16 = 8;
    /// If there's more pages than this value then smaller dots are used at the
    /// beginning/end of the scrollbar to denote the fact.
    const MAX_DOTS: usize = 7;
    /// Center to center.
    const DOT_INTERVAL: i16 = 18;

    fn new(layout: LinearPlacement) -> Self {
        Self {
            area: Rect::zero(),
            layout: layout.align_at_center().with_spacing(Self::DOT_INTERVAL),
            page_count: 0,
            active_page: 0,
        }
    }

    pub fn vertical() -> Self {
        Self::new(LinearPlacement::vertical())
    }

    pub fn horizontal() -> Self {
        Self::new(LinearPlacement::horizontal())
    }

    pub fn set_count_and_active_page(&mut self, page_count: usize, active_page: usize) {
        self.page_count = page_count;
        self.active_page = active_page;
    }

    pub fn has_pages(&self) -> bool {
        self.page_count > 1
    }

    pub fn has_next_page(&self) -> bool {
        self.active_page < self.page_count - 1
    }

    pub fn has_previous_page(&self) -> bool {
        self.active_page > 0
    }

    pub fn go_to_next_page(&mut self) {
        self.go_to(self.active_page.saturating_add(1).min(self.page_count - 1));
    }

    pub fn go_to_previous_page(&mut self) {
        self.go_to(self.active_page.saturating_sub(1));
    }

    pub fn go_to(&mut self, active_page: usize) {
        self.active_page = active_page;
    }
}

impl Component for ScrollBar {
    type Msg = Never;

    fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
        None
    }

    fn paint(&mut self) {
        fn dotsize(distance: usize, nhidden: usize) -> Icon {
            match (nhidden.saturating_sub(distance)).min(2 - distance) {
                0 => Icon::new(theme::DOT_INACTIVE),
                1 => Icon::new(theme::DOT_INACTIVE_HALF),
                _ => Icon::new(theme::DOT_INACTIVE_QUARTER),
            }
        }

        // Number of visible dots.
        let num_shown = self.page_count.min(Self::MAX_DOTS);
        // Page indices corresponding to the first (and last) dot.
        let first_shown = self
            .active_page
            .saturating_sub(Self::MAX_DOTS / 2)
            .min(self.page_count.saturating_sub(Self::MAX_DOTS));
        let last_shown = first_shown + num_shown - 1;

        let mut cursor = self.area.center()
            - Offset::on_axis(
                self.layout.axis,
                Self::DOT_INTERVAL * (num_shown.saturating_sub(1) as i16) / 2,
            );
        for i in first_shown..(last_shown + 1) {
            let icon = if i == self.active_page {
                Icon::new(theme::DOT_ACTIVE)
            } else if i <= first_shown + 1 {
                let before_first_shown = first_shown;
                dotsize(i - first_shown, before_first_shown)
            } else if i >= last_shown - 1 {
                let after_last_shown = self.page_count - 1 - last_shown;
                dotsize(last_shown - i, after_last_shown)
            } else {
                Icon::new(theme::DOT_INACTIVE)
            };
            icon.draw(cursor, CENTER, theme::FG, theme::BG);
            cursor = cursor + Offset::on_axis(self.layout.axis, Self::DOT_INTERVAL);
        }
    }

    fn place(&mut self, bounds: Rect) -> Rect {
        self.area = bounds;
        bounds
    }

    fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
        sink(self.area);
    }
}
