use ab_glyph::{Font, FontRef, Glyph};
use shadeable::{evaluate_shader, pixel_format::Rgba64};
use spin::Lazy;

pub static SCREEN_BUFFER: Lazy<spin::Mutex<ScreenBuffer>> =
    Lazy::new(|| spin::Mutex::new(ScreenBuffer::new(640, 480)));

const FONT_SCALE: f32 = 1.6;
const GLYPH_HEIGHT: f32 = 18.0;
const GLYPH_WIDTH: f32 = 10.0;

#[derive(Debug)]
pub struct ScreenSize {
    pub x: usize,
    pub y: usize,
}

impl ScreenSize {
    pub fn new(x: usize, y: usize) -> Self {
        Self { x, y }
    }
}

pub enum GraphicsReturn {
    Ok,
    ImproperScreenSize,
}

pub struct ScreenBuffer {
    pub size: ScreenSize,
    pub clear_color: Rgba64,
    pub buff: Box<[Rgba64]>,
}

impl ScreenBuffer {
    // Add optional size later
    pub fn new(x: usize, y: usize) -> Self {
        Self {
            size: ScreenSize::new(x, y),
            clear_color: 0,
            buff: vec![0u64; x * y].into_boxed_slice(),
        }
    }

    pub fn draw_filled_circle(&mut self, cx: i32, cy: i32, radius: usize, color: Rgba64) {
        let r = radius as i32 * 2;
        for y in 0..640 {
            for x in 0..480 {
                let dx = cx - x as i32 * 2 - 1;
                let dy = cy - y as i32 * 2 - 1;

                if dx * dx + dy * dy <= r * r {
                    self.set_pixel(x, y, color);
                };
            }
        }
    }

    #[inline]
    pub fn set_pixel(&mut self, x: usize, y: usize, color: Rgba64) {
        self.buff[y * self.size.x + x] = color;
    }

    pub fn clear(&mut self) {
        self.buff = vec![0u64; self.buff.len()].into_boxed_slice();
    }

    pub fn blit(&mut self, _width: usize, _height: usize) {}

    pub fn draw_filled_rect(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: Rgba64) {
        for y in y1..y2 {
            for x in x1..x2 {
                self.set_pixel(x, y, color);
            }
        }
    }
    pub fn draw_unfilled_rect(
        &mut self,
        x1: usize,
        y1: usize,
        x2: usize,
        y2: usize,
        color: Rgba64,
    ) {
        // x1 y1 => x2 y1 => x2 y2 => x1 y2 => x1 y1
        self.draw_line(x1, y1, x2, y1, color);
        self.draw_line(x2, y1, x2, y2, color);
        self.draw_line(x2, y2, x1, y2, color);
        self.draw_line(x1, y2, x1, y1, color);
    }

    #[inline]
    pub fn draw_line(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, color: Rgba64) {
        let x = crate::graphics::get_coordinates(
            x1.try_into().unwrap(),
            y1.try_into().unwrap(),
            x2.try_into().unwrap(),
            y2.try_into().unwrap(),
        );

        for coord in x {
            self.set_pixel(coord.0, coord.1, color);
        }
    }

    pub fn shade(&mut self) {
        for y in 0..100 {
            for x in 0..100 {
                let rgba_ret = evaluate_shader(x, y, self.buff[y * self.size.x + x]);
                match rgba_ret {
                    Ok(pixel) => {
                        // info!("{:?}", pixel);
                        self.set_pixel(x, y, pixel);
                    }

                    Err(err) => error!("{}", err),
                }
            }
        }

        info!("Shaders done");
    }

    // TODO force clear
    pub fn force_redraw(&mut self) {
        // VGAE.lock().clear_screen(vga::colors::Color16::Black);
    }

    /// Draw a glyph on the screen at the given position
    ///
    /// # Arguments
    /// * `x` - the x position of the glyph
    /// * `y` - the y position of the glyph
    /// * `glyph` - the glyph to draw
    /// * `color` - the color of the glyph
    pub fn draw_char(&mut self, mut x: u32, mut y: u32, character: char, color: Rgba64) {
        // trace!["Judy Hopps is thicc af"];
        // let mode = *VGAE.lock();
        // trace!["She got them bouncy bunny buns"];

        let basic_multingual_plane = FontRef::try_from_slice(include_bytes!(
            "../../../ableos/assets/fonts/unifont-14.0.01.ttf"
        ))
        .unwrap();

        let supplementary_multilingual_plane = FontRef::try_from_slice(include_bytes!(
            "../../../ableos/assets/fonts/unifont_upper-14.0.01.ttf"
        ))
        .unwrap();

        // let mut has_char = false;
        // for x in font.codepoint_ids() {
        //    if x.1 == character {
        //       has_char = true;
        //       break;
        //    }
        // }

        let in_supp_plane = character as u32 > 0xffff;

        let plane = match in_supp_plane {
            false => basic_multingual_plane,
            true => supplementary_multilingual_plane,
        };

        match character {
            '\n' => {
                // x = 0;
                // y += (GLYPH_HEIGHT * FONT_SCALE) as u32;
            }
            _ => {
                let q_glyph: Glyph = plane.glyph_id(character).with_scale_and_position(
                    20.0 * FONT_SCALE,
                    ab_glyph::point(GLYPH_WIDTH * FONT_SCALE, GLYPH_HEIGHT * FONT_SCALE),
                );

                // elf: I don't know if GLYPH_HEIGHT is in the right units here. I'm just guessing.
                if x as usize > self.size.x {
                    x = 0;
                    y += (GLYPH_HEIGHT * FONT_SCALE) as u32;
                }

                if let Some(q) = plane.outline_glyph(q_glyph) {
                    q.draw(|gx, gy, c| {
                        if c > 0.015 {
                            let corner = q.px_bounds().min;
                            self.set_pixel(
                                gx as usize + corner.x as usize + x as usize,
                                gy as usize + corner.y as usize + y as usize,
                                color,
                            );
                        }
                    });
                }
            }
        }
    }
}

pub fn get_coordinates(x1: i32, y1: i32, x2: i32, y2: i32) -> Vec<(usize, usize)> {
    let mut coordinates: Vec<(usize, usize)> = vec![];
    let dx: i32 = i32::abs(x2 - x1);
    let dy: i32 = i32::abs(y2 - y1);
    let sx: i32 = {
        if x1 < x2 {
            1
        } else {
            -1
        }
    };
    let sy: i32 = {
        if y1 < y2 {
            1
        } else {
            -1
        }
    };
    let mut error: i32 = (if dx > dy { dx } else { -dy }) / 2;
    let mut current_x: i32 = x1;
    let mut current_y: i32 = y1;
    loop {
        coordinates.push((current_x as usize, current_y as usize));
        // info!("0  {:?}", (current_x, current_y));

        if current_x == x2 && current_y == y2 {
            break;
        }

        let error2: i32 = error;

        if error2 > -dx {
            error -= dy;
            current_x += sx;
        }
        if error2 < dy {
            error += dx;
            current_y += sy;
        }
    }

    coordinates
}
