use std::fs::File;
use std::io::BufWriter;
use std::path::Path;

use png;
use thiserror::Error;

use crate::tuples::Color;

#[derive(Error, Debug)]
pub enum CanvasError {
    #[error("faile to write canvas")]
    IOError(#[from] std::io::Error),
    #[error("faile to encode canvas to png")]
    EncodingError(#[from] png::EncodingError),
}
pub struct Canvas {
    pub height: usize,
    pub width: usize,
    pub pixels: Vec<Color>,
}

impl Canvas {
    pub fn new(width: usize, height: usize, background: Color) -> Canvas {
        let pixels = vec![background; width * height];
        Canvas {
            width,
            height,
            pixels,
        }
    }

    pub fn set(&mut self, x: usize, y: usize, c: Color) {
        if x > self.width {
            return;
        }
        if y > self.height {
            return;
        }
        self.pixels[x + y * self.width] = c;
    }
    pub fn get(&self, x: usize, y: usize) -> Color {
        self.pixels[x + y * self.width]
    }
    pub fn write_to_file<P>(&self, path: P) -> Result<(), CanvasError>
    where
        P: AsRef<Path>,
    {
        let path = Path::new(path.as_ref());
        let file = File::create(path)?;
        let ref mut w = BufWriter::new(file);

        let mut encoder = png::Encoder::new(w, self.width as u32, self.height as u32);
        encoder.set_color(png::ColorType::RGB);
        encoder.set_depth(png::BitDepth::Eight);
        let mut writer = encoder.write_header()?;

        let data: Vec<u8> = self
            .pixels
            .iter()
            .flat_map(|p| {
                vec![
                    (p.red.clamp(0., 1.) * 255.) as u8,
                    (p.green.clamp(0., 1.) * 255.) as u8,
                    (p.blue.clamp(0., 1.) * 255.) as u8,
                ]
            })
            .collect();
        writer.write_image_data(&data)?;
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use super::Canvas;

    use crate::{tuples::Color, BLACK};

    #[test]
    fn create_canvas() {
        let bg = BLACK;
        let c = Canvas::new(10, 20, bg);
        assert_eq!(c.width, 10);
        assert_eq!(c.height, 20);
        for (i, p) in c.pixels.iter().enumerate() {
            assert_eq!(p, &BLACK, "pixel {} not {:?}: {:?}", i, &BLACK, p);
        }
    }

    #[test]
    fn write_to_canvas() {
        let bg = Color::new(0.2, 0.2, 0.2);
        let mut c = Canvas::new(10, 20, bg);
        let red = Color::new(1., 0., 0.);
        c.set(2, 3, red);
        assert_eq!(c.get(2, 3), red);
    }
}
