use crate::color::{Color, WHITE};
use crate::renderable_image::RenderableImage;
use crate::renderable_macros::DrawOffset;
use crate::scaling::*;
use crate::{Graphics, GraphicsError, Tint};
use graphics_shapes::coord::Coord;
use ici_files::image::IndexedImage;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Formatter};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Eq, PartialEq)]
pub struct Image {
    pub(crate) bytes: Vec<u8>,
    width: usize,
    height: usize,
    is_transparent: bool,
}
impl Debug for Image {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Image: {}x{}", self.width, self.height)
    }
}
impl Image {
    pub fn new(pixels: Vec<Color>, width: usize, height: usize) -> Result<Self, GraphicsError> {
        let is_transparent = pixels.iter().any(|c| c.is_transparent());
        if width * height != pixels.len() {
            Err(GraphicsError::ImageInitSize(width * height, pixels.len()))
        } else {
            let bytes = pixels.into_iter().flat_map(|c| c.as_array()).collect();
            Ok(Image {
                bytes,
                width,
                height,
                is_transparent,
            })
        }
    }
    pub fn new_blank(width: usize, height: usize) -> Self {
        let pixels = vec![WHITE; width * height];
        Image::new(pixels, width, height).expect(
            "Failed to create blank image, please create GitHub issue for buffer-graphics-lib",
        )
    }
    pub fn from_indexed(indexed_image: &IndexedImage) -> Image {
        let mut pixels =
            vec![0; indexed_image.width() as usize * indexed_image.height() as usize * 4];
        let mut graphics = Graphics::new(&mut pixels, indexed_image.width() as usize, indexed_image.height() as usize)
            .expect("Creating buffer to make image from indexed image, please raise an issue on GitHub buffer-graphics-lib");
        graphics.draw_indexed_image((0, 0), indexed_image);
        graphics.copy_to_image()
    }
}
impl Image {
    #[inline]
    pub fn width(&self) -> usize {
        self.width
    }
    #[inline]
    pub fn height(&self) -> usize {
        self.height
    }
    #[inline]
    pub fn is_transparent(&self) -> bool {
        self.is_transparent
    }
    pub fn pixels(&self) -> Vec<Color> {
        self.bytes
            .chunks_exact(4)
            .map(|chunk| Color {
                r: chunk[0],
                g: chunk[1],
                b: chunk[2],
                a: chunk[3],
            })
            .collect()
    }
    fn recalc_transparency(&mut self) {
        self.is_transparent = self.pixels().iter().any(|c| c.is_transparent());
    }
    #[inline]
    pub fn get_pixel(&self, x: usize, y: usize) -> Color {
        let addr = (y * self.width + x) * 4;
        Color {
            r: self.bytes[addr],
            g: self.bytes[addr + 1],
            b: self.bytes[addr + 2],
            a: self.bytes[addr + 3],
        }
    }
    #[inline]
    pub fn set_pixel(&mut self, x: usize, y: usize, value: Color) {
        let addr = (y * self.width + x) * 4;
        self.bytes[addr] = value.r;
        self.bytes[addr + 1] = value.g;
        self.bytes[addr + 2] = value.b;
        self.bytes[addr + 3] = value.a;
        self.recalc_transparency();
    }
    #[inline]
    pub fn blend_pixel(&mut self, x: usize, y: usize, value: Color) {
        let new_color = self.get_pixel(x, y).blend(value);
        let addr = (y * self.width + x) * 4;
        self.bytes[addr] = new_color.r;
        self.bytes[addr + 1] = new_color.g;
        self.bytes[addr + 2] = new_color.b;
        self.bytes[addr + 3] = new_color.a;
        self.recalc_transparency();
    }
    pub fn flip_horizontal(&mut self) {
        let half_width = (self.width as f32 / 2.).floor() as usize;
        for y in 0..self.height {
            for x in 0..half_width {
                let y = y * self.width;
                unsafe {
                    std::ptr::swap_nonoverlapping(
                        &mut self.bytes[(y + x) * 4],
                        &mut self.bytes[(y + self.width - 1 - x) * 4],
                        4,
                    );
                }
            }
        }
    }
    pub fn flip_vertical(&mut self) {
        let half_height = (self.height as f32 / 2.).floor() as usize;
        for y in 0..half_height {
            unsafe {
                std::ptr::swap_nonoverlapping(
                    &mut self.bytes[(y * self.width) * 4],
                    &mut self.bytes[((self.height - 1 - y) * self.width) * 4],
                    self.width * 4,
                );
            }
        }
    }
    pub fn rotate_cw(&mut self) -> Image {
        let mut output = Image::new_blank(self.height, self.width);
        for y in 0..self.height {
            for x in 0..self.width {
                let new_y = x;
                let new_x = output.width - y - 1;
                output.set_pixel(new_x, new_y, self.get_pixel(x, y));
            }
        }
        output
    }
    pub fn rotate_ccw(&mut self) -> Image {
        let mut output = Image::new_blank(self.height, self.width);
        for y in 0..self.height {
            for x in 0..self.width {
                let new_y = output.height - x - 1;
                let new_x = y;
                output.set_pixel(new_x, new_y, self.get_pixel(x, y));
            }
        }
        output
    }
    pub fn blend(&mut self, other: &Image) -> Result<(), GraphicsError> {
        if self.width != other.width || self.height != other.height {
            return Err(GraphicsError::ImageBlendSize(
                self.width,
                self.height,
                other.width,
                other.height,
            ));
        }
        for y in 0..self.height {
            for x in 0..self.width {
                self.blend_pixel(x, y, other.get_pixel(x, y));
            }
        }
        self.recalc_transparency();
        Ok(())
    }
    pub fn scale(&self, algo: Scaling) -> Image {
        match algo {
            Scaling::NearestNeighbour { x_scale, y_scale } => {
                scale_nearest_neighbor(self, usize::from(x_scale), usize::from(y_scale))
            }
            Scaling::Epx2x => scale_epx(self),
            Scaling::Epx4x => scale_epx(&scale_epx(self)),
        }
    }
    pub fn to_renderable<P: Into<Coord>>(self, xy: P, draw_offset: DrawOffset) -> RenderableImage {
        RenderableImage::new(self, xy, draw_offset)
    }
}
impl Tint for Image {
    fn tint_add(&mut self, r_diff: isize, g_diff: isize, b_diff: isize, a_diff: isize) {
        for pixel in self.bytes.chunks_exact_mut(4) {
            let mut color = Color {
                r: pixel[0],
                g: pixel[1],
                b: pixel[2],
                a: pixel[3],
            };
            color.tint_add(r_diff, g_diff, b_diff, a_diff);
            pixel[0] = color.r;
            pixel[1] = color.g;
            pixel[2] = color.b;
            pixel[3] = color.a;
        }
        self.recalc_transparency();
    }
    fn tint_mul(&mut self, r_diff: f32, g_diff: f32, b_diff: f32, a_diff: f32) {
        for pixel in self.bytes.chunks_exact_mut(4) {
            let mut color = Color {
                r: pixel[0],
                g: pixel[1],
                b: pixel[2],
                a: pixel[3],
            };
            color.tint_mul(r_diff, g_diff, b_diff, a_diff);
            pixel[0] = color.r;
            pixel[1] = color.g;
            pixel[2] = color.b;
            pixel[3] = color.a;
        }
        self.recalc_transparency();
    }
}
#[cfg(test)]
mod test {
    use crate::color::Color;
    use crate::image::Image;
    use crate::scaling::Scaling;
    use crate::Tint;
    fn make_image() -> Image {
        Image::new(
            vec![
                Color::gray(1),
                Color::gray(2),
                Color::gray(3),
                Color::gray(4),
                Color::gray(5),
                Color::gray(6),
                Color::gray(7),
                Color::gray(8),
                Color::gray(9),
            ],
            3,
            3,
        )
        .unwrap()
    }
    #[test]
    fn constructor() {
        let image = make_image();
        assert_eq!(image.width, 3);
        assert_eq!(image.height, 3);
        assert_eq!(image.bytes.len(), 9 * 4);
        assert!(!image.is_transparent);
        assert_eq!(
            image.pixels(),
            vec![
                Color::gray(1),
                Color::gray(2),
                Color::gray(3),
                Color::gray(4),
                Color::gray(5),
                Color::gray(6),
                Color::gray(7),
                Color::gray(8),
                Color::gray(9),
            ]
        );
    }
    #[test]
    fn test_flip() {
        let image = make_image();
        assert_eq!(
            image.pixels(),
            vec![
                Color::gray(1),
                Color::gray(2),
                Color::gray(3),
                Color::gray(4),
                Color::gray(5),
                Color::gray(6),
                Color::gray(7),
                Color::gray(8),
                Color::gray(9),
            ]
        );
        let mut horz = make_image();
        horz.flip_horizontal();
        assert_eq!(
            horz.pixels(),
            vec![
                Color::gray(3),
                Color::gray(2),
                Color::gray(1),
                Color::gray(6),
                Color::gray(5),
                Color::gray(4),
                Color::gray(9),
                Color::gray(8),
                Color::gray(7),
            ]
        );
        let mut vert = make_image();
        vert.flip_vertical();
        assert_eq!(
            vert.pixels(),
            vec![
                Color::gray(7),
                Color::gray(8),
                Color::gray(9),
                Color::gray(4),
                Color::gray(5),
                Color::gray(6),
                Color::gray(1),
                Color::gray(2),
                Color::gray(3),
            ]
        );
        let mut horz_vert = make_image();
        horz_vert.flip_horizontal();
        horz_vert.flip_vertical();
        assert_eq!(
            horz_vert.pixels(),
            vec![
                Color::gray(9),
                Color::gray(8),
                Color::gray(7),
                Color::gray(6),
                Color::gray(5),
                Color::gray(4),
                Color::gray(3),
                Color::gray(2),
                Color::gray(1),
            ]
        );
        let mut vert_horz = make_image();
        vert_horz.flip_horizontal();
        vert_horz.flip_vertical();
        assert_eq!(
            vert_horz.pixels(),
            vec![
                Color::gray(9),
                Color::gray(8),
                Color::gray(7),
                Color::gray(6),
                Color::gray(5),
                Color::gray(4),
                Color::gray(3),
                Color::gray(2),
                Color::gray(1),
            ]
        );
    }
    #[test]
    fn tint_add() {
        let mut image = make_image();
        image.tint_add(10, 20, 30, -50);
        assert_eq!(
            image.pixels(),
            vec![
                Color::rgba(11, 21, 31, 205),
                Color::rgba(12, 22, 32, 205),
                Color::rgba(13, 23, 33, 205),
                Color::rgba(14, 24, 34, 205),
                Color::rgba(15, 25, 35, 205),
                Color::rgba(16, 26, 36, 205),
                Color::rgba(17, 27, 37, 205),
                Color::rgba(18, 28, 38, 205),
                Color::rgba(19, 29, 39, 205),
            ]
        );
    }
    #[test]
    fn tint_mul() {
        let mut image = make_image();
        image.tint_mul(0.5, 1.0, 2.0, 1.0);
        assert_eq!(
            image.pixels(),
            vec![
                Color::rgba(1, 1, 2, 255),
                Color::rgba(1, 2, 4, 255),
                Color::rgba(2, 3, 6, 255),
                Color::rgba(2, 4, 8, 255),
                Color::rgba(3, 5, 10, 255),
                Color::rgba(3, 6, 12, 255),
                Color::rgba(4, 7, 14, 255),
                Color::rgba(4, 8, 16, 255),
                Color::rgba(5, 9, 18, 255),
            ]
        );
    }
    #[test]
    fn rect_scaling() {
        let image = Image::new(
            vec![
                Color::gray(1),
                Color::gray(2),
                Color::gray(3),
                Color::gray(4),
                Color::gray(5),
                Color::gray(6),
            ],
            3,
            2,
        )
        .unwrap();
        let epx2 = image.scale(Scaling::Epx2x);
        let epx4 = image.scale(Scaling::Epx4x);
        let nn2 = image.scale(Scaling::nearest_neighbour(2, 2));
        let nn3 = image.scale(Scaling::nearest_neighbour(3, 3));
        assert_eq!(image.width, 3);
        assert_eq!(image.height, 2);
        assert_eq!(epx2.width, 6);
        assert_eq!(epx2.height, 4);
        assert_eq!(epx4.width, 12);
        assert_eq!(epx4.height, 8);
        assert_eq!(nn2.width, 6);
        assert_eq!(nn2.height, 4);
        assert_eq!(nn3.width, 9);
        assert_eq!(nn3.height, 6);
    }
    #[test]
    fn square_scaling() {
        let image = make_image();
        let epx2 = image.scale(Scaling::Epx2x);
        let epx4 = image.scale(Scaling::Epx4x);
        let nn_double = image.scale(Scaling::nn_double());
        let nn2 = image.scale(Scaling::nearest_neighbour(2, 2));
        let nn3 = image.scale(Scaling::nearest_neighbour(3, 3));
        let nn1_1 = image.scale(Scaling::nearest_neighbour(1, 1));
        let nn1_2 = image.scale(Scaling::nearest_neighbour(1, 2));
        let nn2_1 = image.scale(Scaling::nearest_neighbour(2, 1));
        assert_eq!(image.width, 3);
        assert_eq!(image.height, 3);
        assert_eq!(epx2.width, 6);
        assert_eq!(epx2.height, 6);
        assert_eq!(nn_double.width, 6);
        assert_eq!(nn_double.height, 6);
        assert_eq!(nn2.width, 6);
        assert_eq!(nn2.height, 6);
        assert_eq!(nn3.width, 9);
        assert_eq!(nn3.height, 9);
        assert_eq!(epx4.width, 12);
        assert_eq!(epx4.height, 12);
        assert_eq!(nn1_1.width, 3);
        assert_eq!(nn1_1.height, 3);
        assert_eq!(nn1_2.width, 3);
        assert_eq!(nn1_2.height, 6);
        assert_eq!(nn2_1.width, 6);
        assert_eq!(nn2_1.height, 3);
    }
}