use crate::{
    time::Duration,
    ui::{
        component::{text::common::TextEdit, Event, EventCtx, Timer},
        display::{Color, Font},
        geometry::{Insets, Offset, Point, Rect},
        shape::{Bar, Renderer},
    },
};

use super::super::{super::component::ButtonContent, theme};

/// Contains state commonly used in implementations multi-tap keyboards.
pub struct MultiTapKeyboard {
    /// Configured timeout after which we cancel currently pending key.
    timeout: Duration,
    /// The currently pending state.
    pending: Option<Pending>,
    /// Timer for clearing the pending state.
    timer: Timer,
}

struct Pending {
    /// Index of the pending key.
    key: usize,
    /// Index of the key press (how many times the `key` was pressed, minus
    /// one).
    press: usize,
}

impl MultiTapKeyboard {
    /// Create a new, empty, multi-tap state.
    pub fn new() -> Self {
        Self {
            timeout: Duration::from_secs(1),
            pending: None,
            timer: Timer::new(),
        }
    }

    /// Return the index of the currently pending key, if any.
    pub fn pending_key(&self) -> Option<usize> {
        self.pending.as_ref().map(|p| p.key)
    }

    /// Return the index of the pending key press.
    pub fn pending_press(&self) -> Option<usize> {
        self.pending.as_ref().map(|p| p.press)
    }

    /// Returns `true` if `event` is an `Event::Timer` for the currently pending
    /// timer.
    pub fn timeout_event(&mut self, event: Event) -> bool {
        self.timer.expire(event)
    }

    /// Reset to the empty state. Takes `EventCtx` to request a paint pass (to
    /// either hide or show any pending marker our caller might want to draw
    /// later).
    pub fn clear_pending_state(&mut self, ctx: &mut EventCtx) {
        self.timer.stop();
        if self.pending.is_some() {
            self.pending = None;
            ctx.request_paint();
        }
    }

    /// Register a click to a key. `MultiTapKeyboard` itself does not have any
    /// concept of the key set, so both the key index and the key content is
    /// taken here. Returns a text editing operation the caller should apply on
    /// the output buffer. Takes `EventCtx` to request a timeout for cancelling
    /// the pending state. Caller is required to handle the timer event and
    /// call `Self::clear_pending_state` when the timer hits.
    pub fn click_key(&mut self, ctx: &mut EventCtx, key: usize, key_text: &str) -> TextEdit {
        let (is_pending, press) = match &self.pending {
            Some(pending) if pending.key == key => {
                // This key is pending. Cycle the last inserted character through the
                // key content.
                (true, pending.press.wrapping_add(1))
            }
            _ => {
                // This key is not pending. Append the first character in the key.
                (false, 0)
            }
        };

        // If the key has more then one character, we need to set it as pending, so we
        // can cycle through on the repeated clicks. We also request a timer so we can
        // reset the pending state after a timeout.
        //
        // Note: It might seem that we should make sure to `request_paint` in case we
        // progress into a pending state (to display the pending marker), but such
        // transition only happens as a result of an append op, so the painting should
        // be requested by handling the `TextEdit`.
        self.pending = if key_text.len() > 1 {
            self.timer.start(ctx, self.timeout);
            Some(Pending { key, press })
        } else {
            None
        };

        assert!(!key_text.is_empty());
        // Now we can be sure that a looped iterator will return a value
        let ch = unwrap!(key_text.chars().cycle().nth(press));
        if is_pending {
            TextEdit::ReplaceLast(ch)
        } else {
            TextEdit::Append(ch)
        }
    }
}

/// Enum keeping track of which keyboard is shown and which comes next. Keep the
/// number of values and the constant PAGE_COUNT in sync.
#[repr(u32)]
#[derive(Copy, Clone, PartialEq)]
#[cfg_attr(feature = "ui_debug", derive(ufmt::derive::uDebug))]
pub(crate) enum KeyboardLayout {
    LettersLower = 0,
    LettersUpper = 1,
    Numeric = 2,
    Special = 3,
}

impl KeyboardLayout {
    pub fn next(self) -> Self {
        match self {
            Self::LettersLower => Self::LettersUpper,
            Self::LettersUpper => Self::Numeric,
            Self::Numeric => Self::Special,
            Self::Special => Self::LettersLower,
        }
    }

    pub fn prev(self) -> Self {
        match self {
            Self::LettersLower => Self::Special,
            Self::LettersUpper => Self::LettersLower,
            Self::Numeric => Self::LettersUpper,
            Self::Special => Self::Numeric,
        }
    }
}

impl From<KeyboardLayout> for ButtonContent {
    /// Used to get content for the "next keyboard" button
    fn from(kl: KeyboardLayout) -> Self {
        match kl {
            KeyboardLayout::LettersLower => ButtonContent::single_line_text("abc".into()),
            KeyboardLayout::LettersUpper => ButtonContent::single_line_text("ABC".into()),
            KeyboardLayout::Numeric => ButtonContent::single_line_text("123".into()),
            KeyboardLayout::Special => ButtonContent::Icon(theme::ICON_ASTERISK),
        }
    }
}

/// Create a visible "underscoring" of the last letter of a text.
pub fn render_pending_marker<'s>(
    target: &mut impl Renderer<'s>,
    text_baseline: Point,
    text: &str,
    font: Font,
    color: Color,
) {
    // Measure the width of the last character of input.
    if let Some(last) = text.chars().last() {
        let width = font.text_width(text);
        let last_width = font.char_width(last);
        // Draw the marker 2px under the start of the baseline of the last character.
        let marker_origin = text_baseline + Offset::new(width - last_width, 2);
        // Draw the marker 1px longer than the last character, and 3px thick.
        let marker_rect =
            Rect::from_top_left_and_size(marker_origin, Offset::new(last_width + 1, 3));
        Bar::new(marker_rect).with_bg(color).render(target);
    }
}

/// The number and colors of fading icons to display.
pub const FADING_ICON_COUNT: usize = 4;
pub const FADING_ICON_COLORS: [Color; FADING_ICON_COUNT] = [
    theme::GREY_SUPER_DARK,
    theme::GREY_EXTRA_DARK,
    theme::GREY_DARK,
    theme::GREY,
];

/// Visible area of the keypad. The touchable area is smaller
pub const KEYPAD_VISIBLE_HEIGHT: i16 = 440;
/// Area of the input field that is touchable
pub const INPUT_TOUCH_HEIGHT: i16 = 96;

const TEXTBOX_HEIGHT: i16 = 72;
const INPUT_TOP_PADDING: i16 = 16;
const PROMPT_HEIGHT: i16 = 44;
const PROMPT_TOP_PADDING: i16 = 35;
const PROMPT_RIGHT_PADDING: i16 = 38;

pub const KEYBOARD_INPUT_RADIUS: i16 = 12;
pub const KEYBOARD_INPUT_INSETS: Insets = Insets::new(
    INPUT_TOP_PADDING,
    theme::PADDING,
    INPUT_TOUCH_HEIGHT - INPUT_TOP_PADDING - TEXTBOX_HEIGHT,
    theme::PADDING,
);
pub const KEYBOARD_PROMPT_INSETS: Insets = Insets::new(
    PROMPT_TOP_PADDING,
    PROMPT_RIGHT_PADDING,
    INPUT_TOUCH_HEIGHT - PROMPT_TOP_PADDING - PROMPT_HEIGHT,
    theme::PADDING,
);

pub const SHOWN_INSETS: Insets = Insets::new(
    INPUT_TOP_PADDING,
    theme::PADDING,
    INPUT_TOP_PADDING,
    theme::PADDING,
);
