use crate::vga_e::VGAE;
use alloc::{boxed::Box, vec, vec::Vec};
use shadeable::{
    evaluate_shader,
    pixel_format::{Rgba64},
};
use spin;
use vga::{writers::GraphicsWriter};

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

lazy_static::lazy_static! {
    pub static ref SCREEN_BUFFER: spin::Mutex<ScreenBuffer> = spin::Mutex::new(ScreenBuffer::new(640, 480));

}

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]>, // Vec<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) {
        use shadeable::pixel_format::into_vga_16;
        VGAE.lock().clear_screen(into_vga_16(self.clear_color));
    }
}

pub trait VgaBuffer {
    fn copy_to_buffer(&self) -> GraphicsReturn;
}
impl VgaBuffer for ScreenBuffer {
    fn copy_to_buffer(&self) -> GraphicsReturn {
        let mode = VGAE.lock();
        for y in 0..self.size.y {
            for x in 0..self.size.x {
                use shadeable::pixel_format::{into_vga_16};
                // let vga_color = get_color16(self.buff[y * self.size.x + x]);

                let vga_color = into_vga_16(self.buff[y * self.size.x + x]);

                if into_vga_16(self.clear_color) != vga_color {
                    mode.set_pixel(x, y, vga_color);
                }
            }
        }

        GraphicsReturn::Ok
    }
}
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
}
