mod sprite_data;
mod vector_points;

pub use sprite_data::*;
pub use vector_points::*;

use geometry::HasDimensions;
use phosphor::*;

use std::cmp::{min, max, Ordering};
use std::iter::zip;

pub type ScreenDimensions = geometry::Dimensions<u16>;
pub type ScreenPosition = geometry::Point<u16>;

#[derive(Copy, Clone)]
pub enum ScreenLayer { Background, Foreground }

pub struct ScreenDevice {
    pub wake_flag: bool,
    /// Each byte represents a screen pixel, left-right-top-bottom.
    // Only the bottom four bits of each byte are used.
    pub foreground: Vec<u8>,
    pub background: Vec<u8>,
    pub is_dirty: bool,
    pub is_resizable: bool,

    pub cursor: ScreenPosition,
    pub dimensions: ScreenDimensions,
    pub sprite_data: SpriteData,
    pub palette: [Colour; 16],
    pub palette_high: u8,
    pub sprite_colours: [u8; 4],
    pub vector: VectorPoints,
}

impl ScreenDevice {
    pub fn new() -> Self {
        Self {
            wake_flag: false,

            foreground: Vec::new(),
            background: Vec::new(),
            is_dirty: false,
            is_resizable: true,

            cursor: ScreenPosition::ZERO,
            dimensions: ScreenDimensions::ZERO,
            sprite_data: SpriteData::new(),
            palette: [Colour::BLACK; 16],
            palette_high: 0,
            sprite_colours: [0; 4],

            vector: VectorPoints::new(),
        }
    }

    pub fn set_size(&mut self, dimensions: ScreenDimensions) {
        self.is_resizable = false;
        self.resize(dimensions);
    }

    pub fn resize(&mut self, dimensions: ScreenDimensions) {
        let old_width  = self.dimensions.width  as usize;
        let old_height = self.dimensions.height as usize;
        let new_width  = dimensions.width  as usize;
        let new_height = dimensions.height as usize;
        let new_area = dimensions.area_usize();
        let y_range = 0..min(old_height, new_height);

        match new_width.cmp(&old_width) {
            Ordering::Less => {
                for y in y_range {
                    let from = y * old_width;
                    let to   = y * new_width;
                    let len  = new_width;
                    self.foreground.copy_within(from..from+len, to);
                    self.background.copy_within(from..from+len, to);
                }
                self.foreground.resize(new_area, 0);
                self.background.resize(new_area, 0);
            },
            Ordering::Greater => {
                self.foreground.resize(new_area, 0);
                self.background.resize(new_area, 0);
                for y in y_range.rev() {
                    let from = y * old_width;
                    let to   = y * new_width;
                    let len  = old_width;
                    self.foreground.copy_within(from..from+len, to);
                    self.background.copy_within(from..from+len, to);
                    self.foreground[to+len..to+new_width].fill(0);
                    self.background[to+len..to+new_width].fill(0);
                }
            },
            Ordering::Equal => {
                self.foreground.resize(new_area, 0);
                self.background.resize(new_area, 0);
            },
        };

        self.dimensions = dimensions;
        self.is_dirty = true;
        self.wake_flag = true;
    }

    pub fn render(&mut self, buffer: &mut Buffer) {
        // Pre-calculate a lookup table for the colour palette
        let mut palette = [Colour::BLACK; 256];
        for (i, c) in palette.iter_mut().enumerate() {
            match i > 0x0f {
                true  => *c = self.palette[i >> 4],
                false => *c = self.palette[i & 0x0f],
            }
        };
        let b_width  = buffer.width()  as usize;
        let b_height = buffer.height() as usize;
        let s_width  = self.dimensions.width()  as usize;
        let s_height = self.dimensions.height() as usize;

        // Write colours to the buffer
        if b_width == s_width && b_height == s_height {
            let screen_iter = zip(&self.background, &self.foreground);
            let buffer_iter = buffer.as_mut_slice();
            for (b, (bg, fg)) in zip(buffer_iter, screen_iter) {
                *b = palette[(fg << 4 | bg) as usize];
            }
        } else {
            let width = min(b_width, s_width);
            let height = min(b_height, s_height);
            let width_excess = b_width.saturating_sub(width);
            let b_slice = &mut buffer.as_mut_slice();
            let mut bi = 0;
            let mut si = 0;
            for _ in 0..height {
                let b_iter = &mut b_slice[bi..bi+width];
                let s_iter = zip(
                    &self.background[si..si+width],
                    &self.foreground[si..si+width],
                );
                for (b, (bg, fg)) in zip(b_iter, s_iter) {
                    *b = palette[(fg << 4 | bg) as usize];
                }
                b_slice[bi+width..bi+width+width_excess].fill(palette[0]);
                bi += b_width;
                si += s_width;
            }
            b_slice[bi..].fill(palette[0]);
        }

        // Set flags
        self.is_dirty = false;
    }

    pub fn set_palette_high(&mut self, val: u8) {
        self.palette_high = val;
    }

    pub fn set_palette_low(&mut self, val: u8) {
        let index = (self.palette_high >> 4) as usize;
        let red   = (self.palette_high & 0x0f) * 17;
        let green =                 (val >> 4) * 17;
        let blue  =               (val & 0x0f) * 17;
        self.palette[index] = Colour::from_rgb(red, green, blue);
        self.is_dirty = true;
    }

    pub fn set_sprite_colour_high(&mut self, val: u8) {
        self.sprite_colours[0] = val >> 4;
        self.sprite_colours[1] = val & 0x0f;
    }

    pub fn set_sprite_colour_low(&mut self, val: u8) {
        self.sprite_colours[2] = val >> 4;
        self.sprite_colours[3] = val & 0x0f;
    }

    pub fn shunt(&mut self, val: u8) {
        let is_negative = val & 0x80 != 0;
        let is_vertical = val & 0x40 != 0;
        let dist = (val & 0x3f) as u16;
        match (is_negative, is_vertical) {
            (false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist),
            (false,  true) => self.cursor.y = self.cursor.y.wrapping_add(dist),
            ( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist),
            ( true,  true) => self.cursor.y = self.cursor.y.wrapping_sub(dist),
        };
    }

    pub fn draw(&mut self, val: u8) {
        self.vector.push(self.cursor);
        self.is_dirty = true;
        // Parse draw byte
        let draw_mode = val & 0x70;
        let params    = val & 0x0f;
        let layer = match val & 0x80 != 0 {
            true => ScreenLayer::Foreground,
            false => ScreenLayer::Background
        };
        match draw_mode {
            0x00 =>       self.draw_pixel(params, layer, self.cursor),
            0x10 => self.draw_sprite_1bit(params, layer),
            0x20 =>       self.fill_layer(params, layer),
            0x30 => self.draw_sprite_2bit(params, layer),
            0x40 =>        self.draw_line(params, layer),
            0x50 =>        self.draw_rect(params, layer),
            0x60 =>    todo!("Draw 1-bit textured line"),
            0x70 =>   self.draw_rect_1bit(params, layer),
            _ => unreachable!(),
        };
    }

    fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) {
        let dim = self.dimensions;
        if !dim.contains_point(point) { 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 fill_layer(&mut self, colour: u8, layer: ScreenLayer) {
        match layer {
            ScreenLayer::Background => self.background.fill(colour),
            ScreenLayer::Foreground => self.foreground.fill(colour),
        }
    }

    fn draw_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) {
        let mut sprite = [0; 64];
        let mut pointer: usize = 0;
        let data = self.sprite_data.get_1bit_sprite();
        for row in data {
            for x in (0..8).rev() {
                sprite[pointer] = (row >> x) & 0x1;
                pointer += 1;
            }
        }
        self.draw_sprite(params, layer, sprite);
    }

    fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) {
        let mut sprite = [0; 64];
        let mut pointer: usize = 0;
        let data = self.sprite_data.get_2bit_sprite();
        let (spr1, spr2) = data.split_array_ref::<8>();
        for (row1, row2) in std::iter::zip(spr1, spr2) {
            for x in (0..8).rev() {
                let bit1 = (row1 >> x << 1) & 0x2;
                let bit2 = (row2 >> x)      & 0x1;
                sprite[pointer] = bit1 | bit2;
                pointer += 1;
            }
        }
        self.draw_sprite(params, layer, sprite);
    }

    fn draw_line(&mut self, colour: u8, layer: ScreenLayer) {
        let [p0, p1] = self.vector.get_pair();
        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),
        };
    }

    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.vector.get_pair();

        // 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 so 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_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 layer = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            layer[i+x0..=i+x1].fill(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 layer = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            for _ in y0..=y1 {
                layer[i] = colour;
                i += screen_width;
            }

        }
    }

    fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) {
        if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() {
            let screen_width = self.dimensions.width as usize;
            let rect_width = x1 - x0 + 1;
            let mut i = x0 + (screen_width * y0);
            let pixels = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };
            for _ in y0..=y1 {
                pixels[i..i+rect_width].fill(colour);
                i += screen_width;
            }
        }
    }

    fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) {
        if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() {
            let screen_width = self.dimensions.width as usize;
            let rect_width = x1 - x0 + 1;
            let mut i = x0 + (screen_width * y0);
            let pixels = match layer {
                ScreenLayer::Background => &mut self.background,
                ScreenLayer::Foreground => &mut self.foreground,
            };

            let sprite_data = self.sprite_data.get_1bit_sprite();
            let mut sprite_i = y0 % 8;
            let sprite_x_off = (x0 % 8) as u32;
            let transparent = params & 0x08 != 0;
            if params & 0x07 != 0 {
                todo!("Pre-treat sprite, with rotation/translation");
            }

            for _ in y0..=y1 {
                let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off);
                for _ in x0..=x1 {
                    let colour = (row >> 7) as usize;
                    if !(transparent && colour == 0) {
                        pixels[i] = self.sprite_colours[colour];
                    }
                    row = row.rotate_left(1);
                    i += 1;
                }
                sprite_i = (sprite_i + 1) % 8;
                i += screen_width - rect_width;
            }
        };
    }

    fn draw_sprite(&mut self, params: u8, layer: ScreenLayer, sprite: [u8; 64]) {
        let transparent = params & 0x08 != 0;
        let mut position = self.cursor;
        let mut pointer: usize = 0;

        macro_rules! for8 { ($block:block) => { for _ in 0..8 { $block } }; }
        macro_rules! r   { ($v:expr) => { position.x = position.x.wrapping_add($v) }; }
        macro_rules! l   { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; }
        macro_rules! d   { ($v:expr) => { position.y = position.y.wrapping_add($v) }; }
        macro_rules! u   { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; }
        macro_rules! px { () => {
            let colour = sprite[pointer];
            if !(transparent && colour == 0) {
                self.draw_pixel(self.sprite_colours[colour as usize], layer, position);
            }
            pointer += 1;
        }; }


        match params & 0x07 {
            0 => {               for8!{{ for8!{{ px!(); r!(1); }} l!(8); d!(1); }} }
            1 => { r!(7);        for8!{{ for8!{{ px!(); l!(1); }} r!(8); d!(1); }} }
            2 => {        d!(7); for8!{{ for8!{{ px!(); r!(1); }} l!(8); u!(1); }} }
            3 => { r!(7); d!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); u!(1); }} }

            4 => {               for8!{{ for8!{{ px!(); d!(1); }} u!(8); r!(1); }} }
            5 => { r!(7);        for8!{{ for8!{{ px!(); d!(1); }} u!(8); l!(1); }} }
            6 => {         d!(7);for8!{{ for8!{{ px!(); u!(1); }} d!(8); r!(1); }} }
            7 => { r!(7); d!(7); for8!{{ for8!{{ px!(); u!(1); }} d!(8); l!(1); }} }

            _ => unreachable!(),
        }
    }

    /// Returns [x0, y0, x1, y1]
    fn find_vector_bounding_box(&self) -> Option<[usize; 4]> {
        macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};}
        macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};}

        let [p0, p1] = self.vector.get_pair();
        let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ];
        let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ];
        let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ];
        let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ];
        let right  = self.dimensions.width.saturating_sub(1);
        let bottom = self.dimensions.height.saturating_sub(1);
        if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 {
            None
        } else {
            Some([
                if x0 < 0x8000 { 0 } else { min(lower!(x0), right)  } as usize,
                if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize,
                if x1 < 0x8000 { 0 } else { min(lower!(x1), right)  } as usize,
                if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize,
            ])
        }
    }
}