cube-tui 0.1.9

Terminal UI timer and session manager for speedcubing, with optional web dashboard and BLE (GAN) timer support.
use super::MAX_SESSIONS;
use crate::model::Model;
#[cfg(feature = "bluetooth")]
use crate::model::bluetooth::BluetoothState;
use crate::model::help::HelpState;
use crate::model::main_focus::{MainFocus, MainStatsSelection};
use crate::model::screen::Screen;
use crate::scramble::{self, Scramble, WcaEvent};
use crate::widgets::confirmation::Selection;
use crate::widgets::history::History;
use std::time::Instant;

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TimerState {
    Idle,
    Pulsed,
    Inspection(InspectionState),
    Running(Instant),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InspectionState {
    Pulsed(Instant),
    Running(Instant),
}

pub struct Session {
    pub timer_state: TimerState,
    pub history: History,
    pub scramble: Option<Scramble>,
    pub last_time_ms: u64,
    pub event: WcaEvent,
}

impl Session {
    pub const fn new() -> Self {
        let event = WcaEvent::Cube3x3;
        let scramble = None;
        Self {
            timer_state: TimerState::Idle,
            history: History::new(),
            scramble,
            last_time_ms: 0,
            event,
        }
    }

    pub const fn reset_timer(&mut self) {
        self.timer_state = TimerState::Idle;
        self.last_time_ms = 0;
    }

    pub fn start_inspection(&mut self) {
        self.timer_state = TimerState::Inspection(InspectionState::Running(Instant::now()));
    }

    pub fn start_timer(&mut self) {
        self.timer_state = TimerState::Running(Instant::now());
    }

    pub const fn stop_timer(&mut self) {
        self.timer_state = TimerState::Idle;
    }

    pub const fn pulse_timer(&mut self) {
        if let TimerState::Inspection(InspectionState::Running(start)) = self.timer_state {
            self.timer_state = TimerState::Inspection(InspectionState::Pulsed(start));
        }
    }

    pub fn elapsed_ms(&self) -> u64 {
        match self.timer_state {
            TimerState::Inspection(state) => match state {
                InspectionState::Running(start) | InspectionState::Pulsed(start) => {
                    u64::try_from(start.elapsed().as_millis()).unwrap()
                }
            },
            TimerState::Running(start) => u64::try_from(start.elapsed().as_millis()).unwrap(),
            TimerState::Idle | TimerState::Pulsed => self.last_time_ms,
        }
    }

    pub fn next_scramble(&mut self) {
        self.scramble = Some(scramble::generate_scramble(self.event));
    }

    pub fn next_event(&mut self) {
        self.event = self.event.next();
        self.next_scramble();
    }

    pub fn prev_event(&mut self) {
        self.event = self.event.prev();
        self.next_scramble();
    }
}

impl Default for Session {
    fn default() -> Self {
        Self::new()
    }
}

pub struct SessionState {
    pub sessions: Vec<Session>,
    pub current_session_index: usize,
}

impl SessionState {
    pub fn new() -> Self {
        Self {
            sessions: vec![Session::new()],
            current_session_index: 0,
        }
    }
}

impl Default for SessionState {
    fn default() -> Self {
        Self::new()
    }
}

impl Model {
    pub const fn current_session_index(&self) -> usize {
        self.session_state.current_session_index
    }

    pub const fn session_count(&self) -> usize {
        self.session_state.sessions.len()
    }

    pub const fn is_at_max_sessions(&self) -> bool {
        self.session_state.sessions.len() >= MAX_SESSIONS
    }

    pub fn current_session(&self) -> &Session {
        &self.session_state.sessions[self.session_state.current_session_index]
    }

    pub fn current_session_mut(&mut self) -> &mut Session {
        &mut self.session_state.sessions[self.session_state.current_session_index]
    }

    pub fn add_session(&mut self) -> bool {
        if self.is_at_max_sessions() {
            return false;
        }
        self.session_state.sessions.push(Session::new());
        self.session_state.current_session_index = self.session_state.sessions.len() - 1;
        true
    }

    pub const fn open_confirm_delete_session(&mut self) {
        if self.session_state.sessions.len() > 1 {
            self.screen = Screen::ConfirmDeleteSession {
                selection: Selection::No,
            };
        }
    }

    pub const fn close_confirm_delete_session(&mut self) {
        if self.screen.show_confirm_delete_session() {
            self.screen = Screen::Main;
        }
    }

    pub const fn confirm_delete_session_left(&mut self) {
        if let Screen::ConfirmDeleteSession { selection } = &mut self.screen {
            *selection = Selection::No;
        }
    }

    pub const fn confirm_delete_session_right(&mut self) {
        if let Screen::ConfirmDeleteSession { selection } = &mut self.screen {
            *selection = Selection::Yes;
        }
    }

    pub const fn get_confirm_delete_session_selection(&self) -> Selection {
        if let Screen::ConfirmDeleteSession { selection } = self.screen {
            selection
        } else {
            Selection::No
        }
    }

    pub fn delete_current_session(&mut self) -> bool {
        if self.session_state.sessions.len() <= 1 {
            return false;
        }

        self.session_state
            .sessions
            .remove(self.session_state.current_session_index);
        if self.session_state.current_session_index >= self.session_state.sessions.len() {
            self.session_state.current_session_index = self.session_state.sessions.len() - 1;
        }

        self.screen = Screen::default();
        #[cfg(feature = "bluetooth")]
        {
            self.bluetooth_state = BluetoothState::default();
        }

        true
    }

    pub const fn next_session(&mut self) {
        if self.session_state.sessions.is_empty() {
            return;
        }
        self.session_state.current_session_index =
            (self.session_state.current_session_index + 1) % self.session_state.sessions.len();
    }

    pub const fn prev_session(&mut self) {
        if self.session_state.sessions.is_empty() {
            return;
        }
        if self.session_state.current_session_index == 0 {
            self.session_state.current_session_index = self.session_state.sessions.len() - 1;
        } else {
            self.session_state.current_session_index -= 1;
        }
    }

    pub fn all_sessions_history(&self) -> Vec<History> {
        self.session_state
            .sessions
            .iter()
            .map(|s| s.history.clone())
            .collect()
    }

    pub fn restore_from_history(&mut self, data: Vec<History>) {
        self.session_state.sessions.clear();
        for (index, history) in data.into_iter().enumerate() {
            let mut session = Session::new();
            if let Some(last_time) = history.last() {
                session.event = last_time.event();
                if index == 0 {
                    session.scramble = Some(scramble::generate_scramble(session.event));
                }
            }
            session.history = history;
            session.history.select_last();
            self.session_state.sessions.push(session);
        }
        if self.session_state.sessions.is_empty() {
            self.session_state.sessions.push(Session::new());
        }
        self.session_state.current_session_index = 0;
        self.help_state = HelpState::default();
        self.screen = Screen::default();
        #[cfg(feature = "bluetooth")]
        {
            self.bluetooth_state = BluetoothState::default();
        }
        self.main_focus = MainFocus::History;
        self.main_stats_selection = MainStatsSelection::default();
    }
}