use super::{
    theme, Button, ButtonDetails, ButtonLayout, ButtonPos, HoldToConfirm, HoldToConfirmMsg,
};
use crate::{
    time::{Duration, Instant},
    ui::{
        component::{base::Event, Component, EventCtx, Pad, Timer},
        event::{ButtonEvent, PhysicalButton},
        geometry::Rect,
        shape::Renderer,
    },
};

/// All possible states buttons (left and right) can be at.
#[derive(Copy, Clone, PartialEq, Eq)]
enum ButtonState {
    /// Both buttons are in untouched state.
    /// _ _
    /// NEXT: OneDown
    Nothing,
    /// One Button is down when previously nothing was.
    /// _ _  ... ↓ _ | _ ↓
    /// NEXT: Nothing, BothDown, HTCNeedsRelease
    OneDown(PhysicalButton),
    /// Both buttons are down ("middle-click").
    /// ↓ _ | _ ↓ ... ↓ ↓
    /// NEXT: OneReleased
    BothDown,
    /// One button is down when previously both were.
    /// Happens when "middle-click" is performed.
    /// ↓ ↓ ... ↓ _ | _ ↓
    /// NEXT: Nothing, BothDown
    OneReleased(PhysicalButton),
    /// One button is down after it triggered a HoldToConfirm event.
    /// Needed so that we can cleanly reset the state.
    /// ↓ _ | _ ↓ ... ↓ _ | _ ↓
    /// NEXT: Nothing
    HTCNeedsRelease(PhysicalButton),
}

pub enum ButtonControllerMsg {
    /// Button was pressed down.
    Pressed(ButtonPos),
    /// Which button was triggered, and whether it was pressed for a longer
    /// time before releasing.
    Triggered(ButtonPos, bool),
    /// Button was pressed and held for longer time (not released yet).
    LongPressed(ButtonPos),
    /// Hold-to-confirm button was released prematurely - without triggering
    /// LongPressed.
    ReleasedWithoutLongPress(ButtonPos),
}

/// Defines what kind of button should be currently used.
pub enum ButtonType {
    Button(Button),
    HoldToConfirm(HoldToConfirm),
    Nothing,
}

impl ButtonType {
    pub fn from_button_details(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
        if let Some(btn_details) = btn_details {
            if btn_details.duration.is_some() {
                Self::HoldToConfirm(HoldToConfirm::from_button_details(pos, btn_details))
            } else {
                Self::Button(Button::from_button_details(pos, btn_details))
            }
        } else {
            Self::Nothing
        }
    }

    pub fn place(&mut self, button_area: Rect) {
        match self {
            Self::Button(button) => {
                button.place(button_area);
            }
            Self::HoldToConfirm(htc) => {
                htc.place(button_area);
            }
            Self::Nothing => {}
        }
    }

    pub fn paint(&mut self) {
        match self {
            Self::Button(button) => {
                button.paint();
            }
            Self::HoldToConfirm(htc) => {
                htc.paint();
            }
            Self::Nothing => {}
        }
    }

    pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
        match self {
            Self::Button(button) => {
                button.render(target);
            }
            Self::HoldToConfirm(htc) => {
                htc.render(target);
            }
            Self::Nothing => {}
        }
    }
}

/// Wrapping a button and its state, so that it can be easily
/// controlled from outside.
///
/// Users have a choice of a normal button or Hold-to-confirm button.
/// `button_type` specified what from those two is used, if anything.
pub struct ButtonContainer {
    pos: ButtonPos,
    button_type: ButtonType,
    /// Holds the timestamp of when the button was pressed.
    pressed_since: Option<Instant>,
    /// How long the button should be pressed to send `long_press=true` in
    /// `ButtonControllerMsg::Triggered`
    long_press_ms: u32,
    /// Timer for sending `ButtonControllerMsg::LongPressed`
    long_pressed_timer: Timer,
    /// Whether it should even send `ButtonControllerMsg::LongPressed` events
    /// (optional)
    send_long_press: bool,
}

impl ButtonContainer {
    /// Supplying `None` as `btn_details`  marks the button inactive
    /// (it can be later activated in `set()`).
    pub fn new(pos: ButtonPos, btn_details: Option<ButtonDetails>) -> Self {
        const DEFAULT_LONG_PRESS_MS: u32 = 1000;
        let send_long_press = btn_details
            .as_ref()
            .map_or(false, |btn| btn.send_long_press);
        Self {
            pos,
            button_type: ButtonType::from_button_details(pos, btn_details),
            pressed_since: None,
            long_press_ms: DEFAULT_LONG_PRESS_MS,
            long_pressed_timer: Timer::new(),
            send_long_press,
        }
    }

    /// Changing the state of the button.
    ///
    /// Passing `None` as `btn_details` will mark the button as inactive.
    pub fn set(&mut self, btn_details: Option<ButtonDetails>, button_area: Rect) {
        self.send_long_press = btn_details
            .as_ref()
            .map_or(false, |btn| btn.send_long_press);
        self.button_type = ButtonType::from_button_details(self.pos, btn_details);
        self.button_type.place(button_area);
    }

    /// Placing the possible component.
    pub fn place(&mut self, bounds: Rect) {
        self.button_type.place(bounds);
    }

    /// Painting the component that should be currently visible, if any.
    pub fn paint(&mut self) {
        self.button_type.paint();
    }

    pub fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
        self.button_type.render(target);
    }

    /// Setting the visual state of the button - released/pressed.
    pub fn set_pressed(&mut self, ctx: &mut EventCtx, is_pressed: bool) {
        if let ButtonType::Button(btn) = &mut self.button_type {
            btn.set_pressed(ctx, is_pressed);
        }
    }

    /// Trigger an action or end hold.
    /// Called when the button is released. If it is a simple button, it returns
    /// a Triggered message. If it is a hold-to-confirm button, it ends the
    /// hold.
    pub fn maybe_trigger(&mut self, ctx: &mut EventCtx) -> Option<ButtonControllerMsg> {
        match self.button_type {
            ButtonType::Button(_) => {
                // Finding out whether the button was long-pressed
                let long_press = self.pressed_since.map_or(false, |since| {
                    Instant::now().saturating_duration_since(since).to_millis() > self.long_press_ms
                });
                self.pressed_since = None;
                self.long_pressed_timer.stop();
                Some(ButtonControllerMsg::Triggered(self.pos, long_press))
            }
            ButtonType::HoldToConfirm(_) => {
                self.hold_ended(ctx);
                Some(ButtonControllerMsg::ReleasedWithoutLongPress(self.pos))
            }
            _ => None,
        }
    }

    /// Find out whether hold-to-confirm was triggered.
    pub fn htc_got_triggered(&mut self, ctx: &mut EventCtx, event: Event) -> bool {
        if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
            if matches!(htc.event(ctx, event), Some(HoldToConfirmMsg::Confirmed)) {
                return true;
            }
        }
        false
    }

    /// Saving the timestamp of when the button was pressed.
    /// Also requesting a timer for long-press if wanted.
    pub fn got_pressed(&mut self, ctx: &mut EventCtx) {
        self.pressed_since = Some(Instant::now());
        if self.send_long_press {
            self.long_pressed_timer
                .start(ctx, Duration::from_millis(self.long_press_ms));
        }
    }

    /// Reset the pressed information.
    pub fn reset(&mut self) {
        self.pressed_since = None;
        self.long_pressed_timer.stop();
    }

    /// Whether token matches what we have
    pub fn timer_event(&mut self, event: Event) -> bool {
        self.long_pressed_timer.expire(event)
    }

    /// Registering hold event.
    pub fn hold_started(&mut self, ctx: &mut EventCtx) {
        if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
            htc.event(ctx, Event::Button(ButtonEvent::HoldStarted));
        }
    }

    /// Cancelling hold event.
    pub fn hold_ended(&mut self, ctx: &mut EventCtx) {
        if let ButtonType::HoldToConfirm(htc) = &mut self.button_type {
            htc.event(ctx, Event::Button(ButtonEvent::HoldEnded));
        }
    }
}

/// Component responsible for handling buttons.
///
/// Acts as a state-machine of `ButtonState`.
///
/// Storing all three possible buttons - left, middle and right -
/// and handling their placement, painting and returning
/// appropriate events when they are triggered.
///
/// Buttons can be interactively changed by clients by `set()`.
///
/// Only "final" button events are returned in `ButtonControllerMsg::Triggered`,
/// based upon the buttons being long-press or not.
///
/// There is optional complexity with IgnoreButtonDelay, which gets executed
/// only in cases where clients request it.
pub struct ButtonController {
    pad: Pad,
    left_btn: ButtonContainer,
    middle_btn: ButtonContainer,
    right_btn: ButtonContainer,
    state: ButtonState,
    /// Button area is needed so the buttons
    /// can be "re-placed" after their text is changed
    /// Will be set in `place`
    button_area: Rect,
    /// Handling optional ignoring of buttons after pressing the other button.
    ignore_btn_delay: Option<IgnoreButtonDelay>,
    /// Whether to count with middle button
    handle_middle_button: bool,
}

impl ButtonController {
    pub fn new(btn_layout: ButtonLayout) -> Self {
        let handle_middle_button = btn_layout.btn_middle.is_some();
        Self {
            pad: Pad::with_background(theme::BG).with_clear(),
            left_btn: ButtonContainer::new(ButtonPos::Left, btn_layout.btn_left),
            middle_btn: ButtonContainer::new(ButtonPos::Middle, btn_layout.btn_middle),
            right_btn: ButtonContainer::new(ButtonPos::Right, btn_layout.btn_right),
            state: ButtonState::Nothing,
            button_area: Rect::zero(),
            ignore_btn_delay: None,
            handle_middle_button,
        }
    }

    /// Set up the logic to ignore a button after some time from pressing
    /// the other button.
    pub fn with_ignore_btn_delay(mut self, delay_ms: u32) -> Self {
        self.ignore_btn_delay = Some(IgnoreButtonDelay::new(delay_ms));
        self
    }

    /// Updating all the three buttons to the wanted states.
    pub fn set(&mut self, btn_layout: ButtonLayout) {
        self.handle_middle_button = btn_layout.btn_middle.is_some();
        self.pad.clear();
        self.left_btn.set(btn_layout.btn_left, self.button_area);
        self.middle_btn.set(btn_layout.btn_middle, self.button_area);
        self.right_btn.set(btn_layout.btn_right, self.button_area);
    }

    /// Setting the pressed state for all three buttons by boolean flags.
    fn set_pressed(&mut self, ctx: &mut EventCtx, left: bool, mid: bool, right: bool) {
        self.left_btn.set_pressed(ctx, left);
        self.middle_btn.set_pressed(ctx, mid);
        self.right_btn.set_pressed(ctx, right);
    }

    pub fn highlight_button(&mut self, ctx: &mut EventCtx, pos: ButtonPos) {
        match pos {
            ButtonPos::Left => self.left_btn.set_pressed(ctx, true),
            ButtonPos::Middle => self.middle_btn.set_pressed(ctx, true),
            ButtonPos::Right => self.right_btn.set_pressed(ctx, true),
        }
    }

    /// Handle middle button hold-to-confirm start.
    /// We need to cancel possible holds in both other buttons.
    fn middle_hold_started(&mut self, ctx: &mut EventCtx) {
        self.left_btn.hold_ended(ctx);
        self.middle_btn.hold_started(ctx);
        self.right_btn.hold_ended(ctx);
    }

    /// Handling the expiration of HTC elements.
    /// Finding out if they have been triggered and sending event
    /// for the appropriate button.
    /// Setting the state to wait for the appropriate release event
    /// from the pressed button. Also resetting visible state.
    fn handle_htc_expiration(
        &mut self,
        ctx: &mut EventCtx,
        event: Event,
    ) -> Option<ButtonControllerMsg> {
        if self.left_btn.htc_got_triggered(ctx, event) {
            self.state = ButtonState::HTCNeedsRelease(PhysicalButton::Left);
            self.set_pressed(ctx, false, false, false);
            return Some(ButtonControllerMsg::Triggered(ButtonPos::Left, true));
        } else if self.middle_btn.htc_got_triggered(ctx, event) {
            self.state = ButtonState::Nothing;
            self.set_pressed(ctx, false, false, false);
            return Some(ButtonControllerMsg::Triggered(ButtonPos::Middle, true));
        } else if self.right_btn.htc_got_triggered(ctx, event) {
            self.state = ButtonState::HTCNeedsRelease(PhysicalButton::Right);
            self.set_pressed(ctx, false, false, false);
            return Some(ButtonControllerMsg::Triggered(ButtonPos::Right, true));
        }
        None
    }

    fn reset_button_presses(&mut self) {
        self.left_btn.reset();
        self.middle_btn.reset();
        self.right_btn.reset();
    }

    fn got_pressed(&mut self, ctx: &mut EventCtx, pos: ButtonPos) {
        // Only one (virtual) button can be pressed at the same time
        self.reset_button_presses();
        match pos {
            ButtonPos::Left => {
                self.left_btn.got_pressed(ctx);
            }
            ButtonPos::Middle => {
                self.middle_btn.got_pressed(ctx);
            }
            ButtonPos::Right => {
                self.right_btn.got_pressed(ctx);
            }
        }
    }

    fn handle_long_press_timers(&mut self, event: Event) -> Option<ButtonPos> {
        if self.left_btn.timer_event(event) {
            return Some(ButtonPos::Left);
        }
        if self.middle_btn.timer_event(event) {
            return Some(ButtonPos::Middle);
        }
        if self.right_btn.timer_event(event) {
            return Some(ButtonPos::Right);
        }
        None
    }

    /// Resetting the state of the controller.
    pub fn reset_state(&mut self, ctx: &mut EventCtx) {
        self.state = ButtonState::Nothing;
        self.reset_button_presses();
        self.set_pressed(ctx, false, false, false);
        if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
            ignore_btn_delay.reset();
        }
    }
}

impl Component for ButtonController {
    type Msg = ButtonControllerMsg;

    fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
        // State machine for the ButtonController
        // We are matching event with `Event::Button` for a button action
        // and `Event::Timer` for getting the expiration of HTC.
        match event {
            Event::Button(button_event) => {
                let (new_state, event) = match self.state {
                    // _ _
                    ButtonState::Nothing => match button_event {
                        // ▼ * | * ▼
                        ButtonEvent::ButtonPressed(which) => {
                            // ↓ _ | _ ↓
                            // Initial button press will set the timer for second button,
                            // and after some delay, it will become un-clickable
                            if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
                                ignore_btn_delay.handle_button_press(ctx, which);
                            }
                            (
                                ButtonState::OneDown(which),
                                match which {
                                    // ▼ *
                                    PhysicalButton::Left => {
                                        self.got_pressed(ctx, ButtonPos::Left);
                                        self.left_btn.hold_started(ctx);
                                        Some(ButtonControllerMsg::Pressed(ButtonPos::Left))
                                    }
                                    // * ▼
                                    PhysicalButton::Right => {
                                        self.got_pressed(ctx, ButtonPos::Right);
                                        self.right_btn.hold_started(ctx);
                                        Some(ButtonControllerMsg::Pressed(ButtonPos::Right))
                                    }
                                },
                            )
                        }
                        _ => (self.state, None),
                    },
                    // ↓ _ | _ ↓
                    ButtonState::OneDown(which_down) => match button_event {
                        // ▲ * | * ▲
                        ButtonEvent::ButtonReleased(b) if b == which_down => match which_down {
                            // ▲ *
                            // Trigger the button and make the second one clickable in all cases
                            PhysicalButton::Left => {
                                if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
                                    ignore_btn_delay.make_button_clickable(ButtonPos::Right);
                                }
                                // _ _
                                (ButtonState::Nothing, self.left_btn.maybe_trigger(ctx))
                            }
                            // * ▲
                            PhysicalButton::Right => {
                                if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
                                    ignore_btn_delay.make_button_clickable(ButtonPos::Left);
                                }
                                // _ _
                                (ButtonState::Nothing, self.right_btn.maybe_trigger(ctx))
                            }
                        },
                        // * ▼ | ▼ *
                        ButtonEvent::ButtonPressed(b) if b != which_down => {
                            // Buttons may be non-clickable (caused by long-holding the other
                            // button)
                            if let Some(ignore_btn_delay) = &self.ignore_btn_delay {
                                if ignore_btn_delay.ignore_button(b) {
                                    return None;
                                }
                            }
                            // ↓ ↓
                            if self.handle_middle_button {
                                self.got_pressed(ctx, ButtonPos::Middle);
                                self.middle_hold_started(ctx);
                                (
                                    ButtonState::BothDown,
                                    Some(ButtonControllerMsg::Pressed(ButtonPos::Middle)),
                                )
                            } else {
                                self.got_pressed(ctx, b.into());
                                (
                                    ButtonState::BothDown,
                                    Some(ButtonControllerMsg::Pressed(b.into())),
                                )
                            }
                        }
                        _ => (self.state, None),
                    },
                    // ↓ ↓
                    ButtonState::BothDown => match button_event {
                        // ▲ * | * ▲
                        ButtonEvent::ButtonReleased(b) => {
                            self.middle_btn.hold_ended(ctx);
                            // _ ↓ | ↓ _
                            if self.handle_middle_button {
                                (ButtonState::OneReleased(b), None)
                            } else {
                                (
                                    ButtonState::OneReleased(b),
                                    Some(ButtonControllerMsg::Triggered(b.into(), false)),
                                )
                            }
                        }
                        _ => (self.state, None),
                    },
                    // ↓ _ | _ ↓
                    ButtonState::OneReleased(which_up) => match button_event {
                        // * ▼ | ▼ *
                        ButtonEvent::ButtonPressed(b) if b == which_up => {
                            self.middle_hold_started(ctx);
                            // ↓ ↓
                            (ButtonState::BothDown, None)
                        }
                        // ▲ * | * ▲
                        ButtonEvent::ButtonReleased(b) if b != which_up => {
                            // _ _
                            // Both buttons need to be clickable now
                            if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
                                ignore_btn_delay.make_button_clickable(ButtonPos::Left);
                                ignore_btn_delay.make_button_clickable(ButtonPos::Right);
                            }
                            if self.handle_middle_button {
                                (ButtonState::Nothing, self.middle_btn.maybe_trigger(ctx))
                            } else {
                                (
                                    ButtonState::Nothing,
                                    Some(ButtonControllerMsg::Triggered(b.into(), false)),
                                )
                            }
                        }
                        _ => (self.state, None),
                    },
                    // ↓ _ | _ ↓
                    ButtonState::HTCNeedsRelease(needs_release) => match button_event {
                        // Only going out of this state if correct button was released
                        // ▲ * | * ▲
                        ButtonEvent::ButtonReleased(released) if needs_release == released => {
                            // _ _
                            (ButtonState::Nothing, None)
                        }
                        _ => (self.state, None),
                    },
                };

                // Updating the visual feedback for the buttons
                match new_state {
                    // Not showing anything also when we wait for a release
                    ButtonState::Nothing | ButtonState::HTCNeedsRelease(_) => {
                        self.set_pressed(ctx, false, false, false);
                    }
                    ButtonState::OneDown(down_button) => match down_button {
                        PhysicalButton::Left => {
                            self.set_pressed(ctx, true, false, false);
                        }
                        PhysicalButton::Right => {
                            self.set_pressed(ctx, false, false, true);
                        }
                    },
                    ButtonState::BothDown | ButtonState::OneReleased(_) => {
                        self.set_pressed(ctx, false, true, false);
                    }
                };

                self.state = new_state;
                event
            }
            // Timer - handle clickable properties and HoldToConfirm expiration
            Event::Timer(_) => {
                if let Some(ignore_btn_delay) = &mut self.ignore_btn_delay {
                    ignore_btn_delay.handle_timers(event);
                }
                if let Some(pos) = self.handle_long_press_timers(event) {
                    return Some(ButtonControllerMsg::LongPressed(pos));
                }
                self.handle_htc_expiration(ctx, event)
            }
            _ => None,
        }
    }

    fn paint(&mut self) {
        self.pad.paint();
        self.left_btn.paint();
        self.middle_btn.paint();
        self.right_btn.paint();
    }

    fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
        self.pad.render(target);
        self.left_btn.render(target);
        self.middle_btn.render(target);
        self.right_btn.render(target);
    }

    fn place(&mut self, bounds: Rect) -> Rect {
        // Saving button area so that we can re-place the buttons
        // when they get updated
        self.button_area = bounds;

        self.pad.place(bounds);
        self.left_btn.place(bounds);
        self.middle_btn.place(bounds);
        self.right_btn.place(bounds);

        bounds
    }
}

/// When one button is pressed, the other one becomes un-clickable after some
/// small time period. This is to prevent accidental clicks when user is holding
/// the button to automatically move through items.
struct IgnoreButtonDelay {
    /// How big is the delay after the button is inactive
    delay: Duration,
    /// Whether left button is currently clickable
    left_clickable: bool,
    /// Whether right button is currently clickable
    right_clickable: bool,
    /// Timer for setting the left_clickable
    left_clickable_timer: Timer,
    /// Timer for setting the right_clickable
    right_clickable_timer: Timer,
}

impl IgnoreButtonDelay {
    pub fn new(delay_ms: u32) -> Self {
        Self {
            delay: Duration::from_millis(delay_ms),
            left_clickable: true,
            right_clickable: true,
            left_clickable_timer: Timer::new(),
            right_clickable_timer: Timer::new(),
        }
    }

    pub fn make_button_clickable(&mut self, pos: ButtonPos) {
        match pos {
            ButtonPos::Left => {
                self.left_clickable = true;
                self.left_clickable_timer.stop();
            }
            ButtonPos::Right => {
                self.right_clickable = true;
                self.right_clickable_timer.stop();
            }
            ButtonPos::Middle => {}
        }
    }

    pub fn handle_button_press(&mut self, ctx: &mut EventCtx, button: PhysicalButton) {
        if matches!(button, PhysicalButton::Left) {
            self.right_clickable_timer.start(ctx, self.delay);
        }
        if matches!(button, PhysicalButton::Right) {
            self.left_clickable_timer.start(ctx, self.delay);
        }
    }

    pub fn ignore_button(&self, button: PhysicalButton) -> bool {
        if matches!(button, PhysicalButton::Left) && !self.left_clickable {
            return true;
        }
        if matches!(button, PhysicalButton::Right) && !self.right_clickable {
            return true;
        }
        false
    }

    pub fn handle_timers(&mut self, event: Event) {
        if self.left_clickable_timer.expire(event) {
            self.left_clickable = false;
        }
        if self.right_clickable_timer.expire(event) {
            self.right_clickable = false;
        }
    }

    pub fn reset(&mut self) {
        self.left_clickable = true;
        self.right_clickable = true;
        self.left_clickable_timer.stop();
        self.right_clickable_timer.stop();
    }
}

/// Component allowing for automatically moving through items (e.g. Choice
/// items).
///
/// Users are in full control of starting/stopping the movement.
///
/// Can be started e.g. by holding left/right button.
pub struct AutomaticMover {
    /// For requesting timer events repeatedly
    timer: Timer,
    /// Which direction should we go (which button is down)
    moving_direction: Option<ButtonPos>,
    /// How many screens were moved automatically
    auto_moved_screens: usize,
    /// Function to get duration of each movement according to the already moved
    /// steps
    duration_func: fn(usize) -> u32,
}

impl AutomaticMover {
    pub fn new() -> Self {
        fn default_duration_func(steps: usize) -> u32 {
            match steps {
                x if x < 3 => 200,
                x if x < 10 => 150,
                _ => 100,
            }
        }

        Self {
            timer: Timer::new(),
            moving_direction: None,
            auto_moved_screens: 0,
            duration_func: default_duration_func,
        }
    }

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

    /// Determines how long to wait between automatic movements.
    /// Moves quicker with increasing number of screens moved.
    /// Can be forced to be always the same (e.g. for animation purposes).
    fn get_auto_move_duration(&self) -> Duration {
        // Calculating duration from function
        let ms_duration = (self.duration_func)(self.auto_moved_screens);
        Duration::from_millis(ms_duration)
    }

    /// In which direction we are moving, if any
    pub fn moving_direction(&self) -> Option<ButtonPos> {
        self.moving_direction
    }

    // Whether we are currently moving.
    pub fn is_moving(&self) -> bool {
        self.moving_direction.is_some()
    }

    /// Whether we have done at least one automatic movement.
    pub fn was_moving(&self) -> bool {
        self.auto_moved_screens > 0
    }

    pub fn start_moving(&mut self, ctx: &mut EventCtx, button: ButtonPos) {
        self.auto_moved_screens = 0;
        self.moving_direction = Some(button);
        self.timer.start(ctx, self.get_auto_move_duration());
    }

    pub fn stop_moving(&mut self) {
        self.moving_direction = None;
        self.timer.stop();
    }
}

impl Component for AutomaticMover {
    type Msg = ButtonPos;

    fn place(&mut self, bounds: Rect) -> Rect {
        bounds
    }

    fn paint(&mut self) {}

    fn render<'s>(&'s self, _target: &mut impl Renderer<'s>) {}

    fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
        if self.timer.expire(event) && self.moving_direction.is_some() {
            // Restart timer and send the appropriate button trigger event
            self.timer.start(ctx, self.get_auto_move_duration());
            self.auto_moved_screens += 1;
            return self.moving_direction;
        }
        None
    }
}

// DEBUG-ONLY SECTION BELOW

#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ButtonContainer {
    fn trace(&self, t: &mut dyn crate::trace::Tracer) {
        if let ButtonType::Button(btn) = &self.button_type {
            btn.trace(t);
        } else if let ButtonType::HoldToConfirm(htc) = &self.button_type {
            htc.trace(t);
        }
    }
}

#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for ButtonController {
    fn trace(&self, t: &mut dyn crate::trace::Tracer) {
        t.component("ButtonController");
        t.child("left_btn", &self.left_btn);
        t.child("middle_btn", &self.middle_btn);
        t.child("right_btn", &self.right_btn);
    }
}

#[cfg(feature = "ui_debug")]
impl crate::trace::Trace for AutomaticMover {
    fn trace(&self, t: &mut dyn crate::trace::Tracer) {
        t.component("AutomaticMover");
    }
}
