use embedded_graphics::{
    geometry::Point,
    pixelcolor::{Gray8, GrayColor},
    prelude::{DrawTarget, Pixel},
    primitives::Rectangle,
};

#[derive(Clone, Copy)]
pub(crate) struct Glyph {
    pub codepoint: u32,
    pub width: u8,
    pub height: u8,
    pub bitmap_offset: u32,
}

pub(crate) struct BitmapFont {
    pub cell_height: u8,
    pub glyphs: &'static [Glyph],
    pub bitmap: &'static [u8],
}

#[derive(Clone, Copy)]
pub(crate) struct TextStyle {
    font: &'static BitmapFont,
    color: Gray8,
    line_height: u8,
}

pub(crate) struct TextRenderer<'a, D>
where
    D: DrawTarget<Color = Gray8>,
{
    target: &'a mut D,
}

struct WrapCursor {
    left: i32,
    right: i32,
    bottom: i32,
    x: i32,
    y: i32,
    has_text: bool,
    pending_space: bool,
}

struct GlyphPixels<'a> {
    bitmap: &'a [u8],
    origin: Point,
    color: Gray8,
    width: usize,
    height: usize,
    next_pixel: usize,
}

impl TextStyle {
    pub const fn new(font: &'static BitmapFont, color: Gray8, line_height: u8) -> Self {
        Self {
            font,
            color,
            line_height,
        }
    }

    fn char_width(&self, character: char) -> i32 {
        if character == '\t' {
            return self.space_width() * 4;
        }

        self.font
            .glyph_for(character)
            .map(|glyph| i32::from(glyph.width))
            .unwrap_or(0)
    }

    pub(crate) fn measure_width(&self, text: &str) -> i32 {
        text.chars()
            .map(|character| self.char_width(character))
            .sum()
    }

    pub(crate) fn line_height(&self) -> u8 {
        self.line_height
    }

    fn space_width(&self) -> i32 {
        self.char_width(' ')
    }
}

impl BitmapFont {
    fn glyph_for(&self, character: char) -> Option<&Glyph> {
        let codepoint = match character {
            '\u{00a0}' => ' ' as u32,
            _ => character as u32,
        };

        self.glyph(codepoint).or_else(|| self.glyph('?' as u32))
    }

    fn glyph(&self, codepoint: u32) -> Option<&Glyph> {
        for glyph in self.glyphs {
            if glyph.codepoint == codepoint {
                return Some(glyph);
            }

            if glyph.codepoint > codepoint {
                return None;
            }
        }

        None
    }
}

impl<'a, D> TextRenderer<'a, D>
where
    D: DrawTarget<Color = Gray8>,
{
    pub fn new(target: &'a mut D) -> Self {
        Self { target }
    }

    pub fn draw_text(
        &mut self,
        text: &str,
        position: Point,
        style: TextStyle,
    ) -> Result<(), D::Error> {
        let mut x = position.x;
        let mut y = position.y;

        for character in text.chars() {
            match character {
                '\r' => {}
                '\n' => {
                    x = position.x;
                    y += i32::from(style.line_height);
                }
                '\t' => {
                    x += style.space_width() * 4;
                }
                ' ' | '\u{00a0}' => {
                    x += style.space_width();
                }
                _ => {
                    if let Some(glyph) = style.font.glyph_for(character) {
                        self.draw_glyph(glyph, Point::new(x, y), style)?;
                        x += i32::from(glyph.width);
                    }
                }
            }
        }

        Ok(())
    }

    pub fn draw_wrapped_text(
        &mut self,
        text: &str,
        bounds: Rectangle,
        style: TextStyle,
    ) -> Result<(), D::Error> {
        let mut cursor = WrapCursor::new(bounds);
        let mut index = 0;

        while index < text.len() {
            let Some(character) = text[index..].chars().next() else {
                break;
            };

            if character == '\r' {
                index += character.len_utf8();
                continue;
            }

            if character == '\n' {
                cursor.new_line(style);
                index += character.len_utf8();
                continue;
            }

            if character.is_whitespace() {
                cursor.mark_space();
                index += character.len_utf8();
                while index < text.len() {
                    let Some(next) = text[index..].chars().next() else {
                        break;
                    };
                    if next == '\n' || !next.is_whitespace() {
                        break;
                    }
                    index += next.len_utf8();
                }
                continue;
            }

            let word_start = index;
            while index < text.len() {
                let Some(next) = text[index..].chars().next() else {
                    break;
                };
                if next.is_whitespace() {
                    break;
                }
                index += next.len_utf8();
            }

            let word = &text[word_start..index];
            self.draw_wrapped_word(word, &mut cursor, style)?;
        }

        Ok(())
    }

    fn draw_wrapped_word(
        &mut self,
        word: &str,
        cursor: &mut WrapCursor,
        style: TextStyle,
    ) -> Result<(), D::Error> {
        let word_width = style.measure_width(word);
        let space_width = cursor.pending_space_width(style);

        if cursor.has_text && cursor.x + space_width + word_width > cursor.right {
            cursor.new_line(style);
        }

        if word_width <= cursor.line_width() {
            if !cursor.can_draw(style) {
                return Ok(());
            }

            cursor.apply_pending_space(style);
            self.draw_text(word, Point::new(cursor.x, cursor.y), style)?;
            cursor.x += word_width;
            cursor.has_text = true;
            cursor.pending_space = false;
            return Ok(());
        }

        cursor.apply_pending_space(style);
        for character in word.chars() {
            let character_width = style.char_width(character);
            if cursor.has_text && cursor.x + character_width > cursor.right {
                cursor.new_line(style);
            }

            if !cursor.can_draw(style) {
                return Ok(());
            }

            if let Some(glyph) = style.font.glyph_for(character) {
                self.draw_glyph(glyph, Point::new(cursor.x, cursor.y), style)?;
            }
            cursor.x += character_width;
            cursor.has_text = true;
        }
        cursor.pending_space = false;

        Ok(())
    }

    fn draw_glyph(
        &mut self,
        glyph: &Glyph,
        position: Point,
        style: TextStyle,
    ) -> Result<(), D::Error> {
        if glyph.width == 0 || glyph.height == 0 {
            return Ok(());
        }

        let bitmap_offset = glyph.bitmap_offset as usize;
        let bitmap_len = (usize::from(glyph.width) * usize::from(glyph.height)).div_ceil(2);
        let bitmap = &style.font.bitmap[bitmap_offset..bitmap_offset + bitmap_len];
        self.target.draw_iter(GlyphPixels::new(
            bitmap,
            position,
            style.color,
            glyph.width,
            glyph.height,
        ))
    }
}

impl WrapCursor {
    fn new(bounds: Rectangle) -> Self {
        let left = bounds.top_left.x;
        let right = left + bounds.size.width as i32;
        let bottom = bounds.top_left.y + bounds.size.height as i32;

        Self {
            left,
            right,
            bottom,
            x: left,
            y: bounds.top_left.y,
            has_text: false,
            pending_space: false,
        }
    }

    fn line_width(&self) -> i32 {
        self.right - self.left
    }

    fn can_draw(&self, style: TextStyle) -> bool {
        self.y + i32::from(style.font.cell_height) <= self.bottom
    }

    fn new_line(&mut self, style: TextStyle) {
        self.x = self.left;
        self.y += i32::from(style.line_height);
        self.has_text = false;
        self.pending_space = false;
    }

    fn mark_space(&mut self) {
        if self.has_text {
            self.pending_space = true;
        }
    }

    fn pending_space_width(&self, style: TextStyle) -> i32 {
        if self.has_text && self.pending_space {
            style.space_width()
        } else {
            0
        }
    }

    fn apply_pending_space(&mut self, style: TextStyle) {
        self.x += self.pending_space_width(style);
        self.pending_space = false;
    }
}

impl<'a> GlyphPixels<'a> {
    fn new(bitmap: &'a [u8], origin: Point, color: Gray8, width: u8, height: u8) -> Self {
        Self {
            bitmap,
            origin,
            color,
            width: usize::from(width),
            height: usize::from(height),
            next_pixel: 0,
        }
    }

    fn blend_luma(color: Gray8, alpha: u8) -> u8 {
        let foreground = u16::from(color.luma());
        let alpha = u16::from(alpha) * 17;
        let luma = (foreground * alpha + 255 * (255 - alpha)) / 255;
        luma as u8
    }
}

impl Iterator for GlyphPixels<'_> {
    type Item = Pixel<Gray8>;

    fn next(&mut self) -> Option<Self::Item> {
        while self.next_pixel < self.width * self.height {
            let pixel = self.next_pixel;
            self.next_pixel += 1;

            let x = pixel % self.width;
            let y = pixel / self.width;
            let byte = self.bitmap[pixel / 2];
            let alpha = if pixel % 2 == 0 {
                byte >> 4
            } else {
                byte & 0x0F
            };
            if alpha != 0 {
                return Some(Pixel(
                    Point::new(self.origin.x + x as i32, self.origin.y + y as i32),
                    Gray8::new(Self::blend_luma(self.color, alpha)),
                ));
            }
        }

        None
    }
}
