#![cfg(feature = "alloc")]

mod cache;
mod state;
mod wrap;

use core::fmt::Debug;
use core::iter;
use core::marker::PhantomData;

use self::cache::{CacheItem, CacheKey};
use self::state::State;
use embedded_graphics::draw_target::DrawTarget;
use ratatui_core::backend::{Backend, ClearType, WindowSize};
use ratatui_core::buffer::Cell;
use ratatui_core::layout::{Position, Size};
use ratatui_core::style::Modifier;

use crate::backend::DrawTargetBackend;
use crate::blink::{Blink, Blinked, ControlBlinking};
use crate::cursor::{Colors, Extent, Symbol};
use crate::error::Error;

use super::traits;
use super::{WrapTrait, Wrapper};

/// Wrapper that is required in order for text that has [`slow_blink`] or [`rapid_blink`] modifiers
/// added to its style, to appear to be blinking, driving the animation by redrawing cells that are
/// stored in a cache until new content with intersecting positions and without blinking are drawn.
///
/// [`slow_blink`]: ratatui_core::style::Stylize::slow_blink
/// [`rapid_blink`]: ratatui_core::style::Stylize::rapid_blink
#[derive(Debug, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    backend: B,
    slow_blink: Blink,
    rapid_blink: Blink,
    state: State,
    phantom: PhantomData<D>,
}

/// Backend configuration retrieval and modification of the [`BlinkWrapper`] layer.
///
/// A backend wrapper that implements this trait allows the fields of a [`BlinkWrapper`] that are
/// configurable to have their values read or have new values assigned.
pub trait ConfigureBlinkWrapper {
    /// Returns the blink animation cycle for slow blinking.
    fn slow_blink(&self) -> Blink;

    /// Sets the blink animation cycle for slow blinking.
    fn set_slow_blink(&mut self, slow_blink: Blink);

    /// Returns the blink animation cycle for rapid blinking.
    fn rapid_blink(&self) -> Blink;

    /// Sets the blink animation cycle for rapid blinking.
    fn set_rapid_blink(&mut self, rapid_blink: Blink);
}

impl<B, D> BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    /// Creates a new wrapper around the specified backend, configuring the slow and rapid blinking
    /// animation cycles.
    pub const fn new(backend: B, slow_blink: Blink, rapid_blink: Blink) -> Self {
        Self {
            backend,
            slow_blink,
            rapid_blink,
            state: State::new(),
            phantom: PhantomData,
        }
    }
}

impl<B, D> Backend for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    type Error = B::Error;

    fn draw<'z, I>(&mut self, content: I) -> Result<(), Self::Error>
    where
        I: Iterator<Item = (u16, u16, &'z Cell)>,
    {
        use unicode_width::UnicodeWidthStr;

        const SPARSE_BLINK: Modifier = Modifier::SLOW_BLINK.union(Modifier::RAPID_BLINK);
        const ALL_BLINK: Modifier = SPARSE_BLINK;

        let slow_blink = self.slow_blink.get(self.state.ticks);
        let rapid_blink = self.rapid_blink.get(self.state.ticks);
        let sparse_blink = Blinked(slow_blink.0 && rapid_blink.0);
        let cursor_position = self.get_cursor_position()?;

        let previous_slow_blink = self.slow_blink.get(self.state.ticks.wrapping_sub(1));
        let previous_rapid_blink = self.rapid_blink.get(self.state.ticks.wrapping_sub(1));
        let previous_sparse_blink = Blinked(previous_slow_blink.0 && previous_rapid_blink.0);

        let content = content.inspect(|&(x, y, cell)| {
            let key = CacheKey::new(x, y);
            if cell.modifier.intersects(ALL_BLINK) {
                let cell = cell.clone();
                let tickstamp = self.state.ticks;
                self.state.cache.insert_or_replace(key, cell, tickstamp);
            } else {
                self.state.cache.remove(&key);
            }
        });

        let no_blink_content = content.filter(|(_, _, cell)| !cell.modifier.intersects(ALL_BLINK));

        self.backend.draw(no_blink_content)?;

        for (blink, previous_blink, blink_flags) in [
            (slow_blink, previous_slow_blink, Modifier::SLOW_BLINK),
            (rapid_blink, previous_rapid_blink, Modifier::RAPID_BLINK),
            (sparse_blink, previous_sparse_blink, SPARSE_BLINK),
        ] {
            let blink_changed = blink != previous_blink;
            let blink_content = self.state.cache.iter().filter_map(|item| {
                let CacheItem {
                    key: CacheKey { x, y },
                    ref cell,
                    tickstamp,
                } = *item;

                let is_relevant = blink_flags == cell.modifier.intersection(ALL_BLINK);
                if is_relevant && (blink_changed || tickstamp == self.state.ticks) {
                    let end = cell.symbol().width();

                    if y == cursor_position.y {
                        for right in (0..end)
                            .filter_map(|x_offset| x_offset.try_into().ok())
                            .filter_map(|x_offset| x.checked_add(x_offset))
                        {
                            if right == cursor_position.x {
                                self.state.cursor_dirty.replace(());
                            }
                        }
                    }

                    Some((x, y, cell))
                } else {
                    None
                }
            });

            if blink == Blinked(true) {
                self.backend.draw_hidden(blink_content)?;
            } else {
                self.backend.draw(blink_content)?;
            }
        }

        if self.state.blinking {
            self.advance_blink_by(1)?;
        }

        Ok(())
    }

    fn hide_cursor(&mut self) -> Result<(), Self::Error> {
        self.backend.hide_cursor()
    }

    fn show_cursor(&mut self) -> Result<(), Self::Error> {
        self.backend.show_cursor()
    }

    fn get_cursor_position(&mut self) -> Result<Position, Self::Error> {
        self.backend.get_cursor_position()
    }

    fn set_cursor_position<P: Into<Position>>(&mut self, position: P) -> Result<(), Self::Error> {
        self.backend.set_cursor_position(position)
    }

    fn clear(&mut self) -> Result<(), Self::Error> {
        self.backend.clear()?;
        self.state.cache.clear();

        Ok(())
    }

    fn clear_region(&mut self, clear_type: ClearType) -> Result<(), Self::Error> {
        let cursor_position = self.get_cursor_position()?;

        let [x, y] = {
            let Position { x, y } = cursor_position;
            if let Some((x, y, _)) = self.state.cache.find(cursor_position) {
                [x, y]
            } else {
                [x, y]
            }
        };

        let cursor_is_below_or_right = |key: CacheKey| y > key.y || y == key.y && x > key.x;
        let cursor_is_above_or_left = |key: CacheKey| y < key.y || y == key.y && x < key.x;
        let cursor_is_above_or_below = |key: CacheKey| y != key.y;
        let cursor_is_above_or_below_or_right = |key: CacheKey| y != key.y || x > key.x;

        self.backend.clear_region(clear_type)?;

        match clear_type {
            ClearType::All => self.state.cache.clear(),
            ClearType::AfterCursor => self.state.cache.retain(cursor_is_below_or_right),
            ClearType::BeforeCursor => self.state.cache.retain(cursor_is_above_or_left),
            ClearType::CurrentLine => self.state.cache.retain(cursor_is_above_or_below),
            ClearType::UntilNewLine => self.state.cache.retain(cursor_is_above_or_below_or_right),
        };

        Ok(())
    }

    fn size(&self) -> Result<Size, Self::Error> {
        self.backend.size()
    }

    fn window_size(&mut self) -> Result<WindowSize, Self::Error> {
        self.backend.window_size()
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        self.backend.flush()
    }
}

impl<B, D> DrawTargetBackend<D> for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    fn call(&mut self, f: impl FnMut(&mut D) -> Result<(), D::Error>) -> Result<(), D::Error> {
        self.backend.call(f)
    }

    fn draw_hidden<'z, I>(&mut self, content: I) -> Result<(), Self::Error>
    where
        I: Iterator<Item = (u16, u16, &'z Cell)>,
    {
        self.backend.draw_hidden(content)
    }

    fn draw_cursor<'z, I>(
        &mut self,
        content: I,
        colors: Colors,
        extent: Extent,
        symbol: Symbol,
    ) -> Result<(), Self::Error>
    where
        I: Iterator<Item = (u16, u16, &'z Cell)>,
    {
        const SPARSE_BLINK: Modifier = Modifier::SLOW_BLINK.union(Modifier::RAPID_BLINK);
        const ALL_BLINK: Modifier = SPARSE_BLINK;

        let slow_blink = self.slow_blink.get(self.state.ticks);
        let rapid_blink = self.rapid_blink.get(self.state.ticks);
        let sparse_blink = Blinked(slow_blink.0 && rapid_blink.0);

        for (x, y, cell) in content {
            let blink_flags = cell.modifier.intersection(ALL_BLINK);
            let is_hidden = blink_flags == Modifier::SLOW_BLINK && slow_blink == Blinked(true)
                || blink_flags == Modifier::RAPID_BLINK && rapid_blink == Blinked(true)
                || blink_flags == SPARSE_BLINK && sparse_blink == Blinked(true);

            let clone = if is_hidden {
                let mut cell = cell.clone();
                cell.modifier = cell.modifier.union(Modifier::HIDDEN);

                Some(cell)
            } else {
                None
            };

            let cell = clone.as_ref().unwrap_or(cell);
            let content = iter::once((x, y, cell));

            self.backend.draw_cursor(content, colors, extent, symbol)?;
        }

        Ok(())
    }

    fn advance_blink_by(&mut self, ticks: usize) -> Result<(), Self::Error> {
        self.state.ticks = self.state.ticks.wrapping_add(ticks);

        self.backend.advance_blink_by(ticks)
    }

    fn take_dirty_cursor(&mut self) -> Result<Option<()>, Self::Error> {
        let units = [
            self.backend.take_dirty_cursor()?,
            self.state.cursor_dirty.take(),
        ];

        let unit = units.into_iter().flatten().next();

        Ok(unit)
    }
}

impl<B, D> ConfigureBlinkWrapper for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    fn slow_blink(&self) -> Blink {
        self.slow_blink
    }

    fn set_slow_blink(&mut self, slow_blink: Blink) {
        self.slow_blink = slow_blink;
    }

    fn rapid_blink(&self) -> Blink {
        self.rapid_blink
    }

    fn set_rapid_blink(&mut self, rapid_blink: Blink) {
        self.rapid_blink = rapid_blink;
    }
}

impl<B, D> ControlBlinking<D> for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    fn blinking(&self) -> bool {
        self.state.blinking
    }

    fn start_blinking(&mut self) {
        self.state.blinking = true;
    }

    fn stop_blinking(&mut self) {
        self.state.blinking = false;
    }
}

impl<B, D> Wrapper for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
    type Inner = B;

    fn inner(&self) -> &Self::Inner {
        &self.backend
    }

    fn inner_mut(&mut self) -> &mut Self::Inner {
        &mut self.backend
    }

    fn into_inner(self) -> Self::Inner {
        self.backend
    }
}

impl<B, D> WrapTrait<traits::ConfigureBackend> for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
}

impl<B, D> WrapTrait<traits::ConfigureCursorWrapper> for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
}

impl<B, D> WrapTrait<traits::ControlCursorBlinking> for BlinkWrapper<B, D>
where
    B: DrawTargetBackend<D, Error = Error<D::Error>>,
    D: DrawTarget,
    D::Error: Debug,
{
}

/// Blanket implementation of the [`ConfigureBlinkWrapper`] trait for function call passthrough.
impl<W, B> ConfigureBlinkWrapper for W
where
    B: ConfigureBlinkWrapper,
    W: WrapTrait<traits::ConfigureBlinkWrapper, Inner = B>,
{
    fn slow_blink(&self) -> Blink {
        self.inner().slow_blink()
    }

    fn set_slow_blink(&mut self, slow_blink: Blink) {
        self.inner_mut().set_slow_blink(slow_blink);
    }

    fn rapid_blink(&self) -> Blink {
        self.inner().rapid_blink()
    }

    fn set_rapid_blink(&mut self, rapid_blink: Blink) {
        self.inner_mut().set_rapid_blink(rapid_blink);
    }
}
