use super::*;

impl ScreenDevice {
    pub fn draw_line(&mut self, colour: u8, layer: ScreenLayer) {
        let [p0, p1] = [self.cursor, self.vector];
        match (p0.x == p1.x, p0.y == p1.y) {
            (false, false) =>   self.draw_diagonal_line(colour, layer),
            (false,  true) => self.draw_horizontal_line(colour, layer),
            ( true, false) =>   self.draw_vertical_line(colour, layer),
            ( true,  true) =>           self.draw_pixel(colour, layer, p0),
        };
    }

    pub fn draw_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
        let [p0, p1] = [self.cursor, self.vector];
        match (p0.x == p1.x, p0.y == p1.y) {
            (false, false) =>   self.draw_diagonal_line_1bit(params, layer),
            (false,  true) => self.draw_horizontal_line_1bit(params, layer),
            ( true, false) =>   self.draw_vertical_line_1bit(params, layer),
            ( true,  true) =>           self.draw_pixel_1bit(params, layer, p0),
        };
    }

    pub fn draw_pixel_1bit(&mut self, params: u8, layer: ScreenLayer, point: ScreenPosition) {
        let dim = self.dimensions;
        let sprite = self.sprite_buffer.get_1bit_sprite(params);
        let colour = sprite[point.y as usize % 8][point.x as usize % 8];
        if !dim.contains_point(point) || colour == 0xff { return }
        let index = point.x as usize + ((dim.width as usize) * (point.y as usize));
        match layer {
            ScreenLayer::Background => self.background[index] = colour,
            ScreenLayer::Foreground => self.foreground[index] = colour,
        };
    }

    fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer) {
        if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() {
            let screen_width = self.dimensions.width as usize;
            let i = screen_width * y;
            let buffer = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            buffer[i+x0..=i+x1].fill(colour);
        }
    }

    fn draw_horizontal_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
        if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() {
            let screen_width = self.dimensions.width as usize;
            let i = screen_width * y;
            let buffer = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            let sprite = self.sprite_buffer.get_1bit_sprite(params);
            let row = sprite[y % 8];
            for x in x0..=x1 {
                let colour = row[x % 8];
                if colour != 0xff { buffer[i+x] = colour };
            }
        }
    }

    fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer) {
        if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() {
            let screen_width = self.dimensions.width as usize;
            let mut i = (screen_width * y0) + x;
            let buffer = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            for _ in y0..=y1 {
                buffer[i] = colour;
                i += screen_width;
            }
        }
    }

    fn draw_vertical_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
        if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() {
            let screen_width = self.dimensions.width as usize;
            let mut i = (screen_width * y0) + x;
            let buffer = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            let sprite = self.sprite_buffer.get_1bit_sprite(params);
            let mut column = [0u8; 8];
            for y in 0..8 { column[y] = sprite[y][x % 8] }
            for y in y0..=y1 {
                let colour = column[y % 8];
                if colour != 0xff { buffer[i] = colour };
                i += screen_width;
            }
        }
    }

    fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer) {
        fn abs_diff(v0: u16, v1: u16) -> u16 {
            let v = v1.wrapping_sub(v0);
            if v > 0x8000 { !v + 1 } else { v }
        }
        let [p0, p1] = [self.cursor, self.vector];

        // If the slope of the line is greater than 1.
        if  abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) {
            // Swap points 0 and 1 such that y0 is always smaller than y1.
            let (x0, y0, x1, y1) = match p0.y > p1.y {
                true  => (p1.x, p1.y, p0.x, p0.y),
                false => (p0.x, p0.y, p1.x, p1.y),
            };
            let dy = y1 - y0;
            let (dx, xi) = match x0 > x1 {
                true  => (x0 - x1, 0xffff),
                false => (x1 - x0, 0x0001),
            };
            let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2);
            let dx2 = dx * 2;
            let mut d = dx2.wrapping_sub(dy);
            let mut x = x0;

            for y in y0..=y1 {
                self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
                if d < 0x8000 {
                    x = x.wrapping_add(xi); d = d.wrapping_add(dxdy2);
                } else {
                    d = d.wrapping_add(dx2);
                }
            }
        // If the slope of the line is less than or equal to 1.
        } else {
            // Swap points 0 and 1 so that x0 is always smaller than x1.
            let (x0, y0, x1, y1) = match p0.x > p1.x {
                true  => (p1.x, p1.y, p0.x, p0.y),
                false => (p0.x, p0.y, p1.x, p1.y),
            };
            let dx = x1 - x0;
            let (dy, yi) = match y0 > y1 {
                true  => (y0 - y1, 0xffff),
                false => (y1 - y0, 0x0001),
            };
            let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2);
            let dy2 = dy * 2;
            let mut d = dy2.wrapping_sub(dx);
            let mut y = y0;

            for x in x0..=x1 {
                self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
                if d < 0x8000 {
                    y = y.wrapping_add(yi);
                    d = d.wrapping_add(dydx2);
                } else {
                    d = d.wrapping_add(dy2);
                }
            }
        }
    }

    fn draw_diagonal_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
        fn abs_diff(v0: u16, v1: u16) -> u16 {
            let v = v1.wrapping_sub(v0);
            if v > 0x8000 { !v + 1 } else { v }
        }
        let [p0, p1] = [self.cursor, self.vector];

        // If the slope of the line is greater than 1.
        if  abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) {
            // Swap points 0 and 1 such that y0 is always smaller than y1.
            let (x0, y0, x1, y1) = match p0.y > p1.y {
                true  => (p1.x, p1.y, p0.x, p0.y),
                false => (p0.x, p0.y, p1.x, p1.y),
            };
            let dy = y1 - y0;
            let (dx, xi) = match x0 > x1 {
                true  => (x0 - x1, 0xffff),
                false => (x1 - x0, 0x0001),
            };
            let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2);
            let dx2 = dx * 2;
            let mut d = dx2.wrapping_sub(dy);
            let mut x = x0;

            for y in y0..=y1 {
                self.draw_pixel_1bit(params, layer, ScreenPosition::new(x, y));
                if d < 0x8000 {
                    x = x.wrapping_add(xi); d = d.wrapping_add(dxdy2);
                } else {
                    d = d.wrapping_add(dx2);
                }
            }
        // If the slope of the line is less than or equal to 1.
        } else {
            // Swap points 0 and 1 so that x0 is always smaller than x1.
            let (x0, y0, x1, y1) = match p0.x > p1.x {
                true  => (p1.x, p1.y, p0.x, p0.y),
                false => (p0.x, p0.y, p1.x, p1.y),
            };
            let dx = x1 - x0;
            let (dy, yi) = match y0 > y1 {
                true  => (y0 - y1, 0xffff),
                false => (y1 - y0, 0x0001),
            };
            let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2);
            let dy2 = dy * 2;
            let mut d = dy2.wrapping_sub(dx);
            let mut y = y0;

            for x in x0..=x1 {
                self.draw_pixel_1bit(params, layer, ScreenPosition::new(x, y));
                if d < 0x8000 {
                    y = y.wrapping_add(yi);
                    d = d.wrapping_add(dydx2);
                } else {
                    d = d.wrapping_add(dy2);
                }
            }
        }
    }



}