use crate::palette::Palette;
use std::io::Result as IoResult;

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Bpp {
    One,
    Four(Palette),
}

impl Bpp {
    pub fn palette(&self) -> &'static [u8] {
        const PALETTE_1BPP: [u8; 6] = [0x00, 0x00, 0x00, 0xff, 0xff, 0xff];

        match self {
            Bpp::One => &PALETTE_1BPP,
            Bpp::Four(palette) => palette.into_slice().as_flattened(),
        }
    }
}

impl From<Bpp> for usize {
    fn from(bpp: Bpp) -> Self {
        match bpp {
            Bpp::One => 1,
            Bpp::Four(_) => 4,
        }
    }
}

impl From<Bpp> for png::BitDepth {
    fn from(bpp: Bpp) -> Self {
        match bpp {
            Bpp::One => png::BitDepth::One,
            Bpp::Four(_) => png::BitDepth::Four,
        }
    }
}

pub struct Gfx {
    pixels: Vec<u8>,
    bpp: Bpp,
}

impl Gfx {
    pub fn new(pixels: Vec<u8>, bpp: Bpp) -> Gfx {
        let tile_size = match bpp {
            Bpp::One => 8,
            Bpp::Four(_) => 32,
        };
        assert!(pixels.len() % tile_size == 0);

        Gfx { pixels, bpp }
    }

    pub fn png(&self, columns: u8) -> IoResult<Vec<u8>> {
        assert!(columns > 0 && columns <= 32);

        let buffer = vec![];
        let cursor = std::io::Cursor::new(buffer);
        let mut bufwriter = std::io::BufWriter::new(cursor);

        let bpp_size: usize = self.bpp.into();
        let height = (self.pixels.len() / bpp_size / columns as usize) as u32;
        let mut encoder = png::Encoder::new(&mut bufwriter, columns as u32 * 8, height);
        encoder.set_color(png::ColorType::Indexed);
        encoder.set_depth(self.bpp.into());
        encoder.set_palette(self.bpp.palette());
        // Make the first color transparent.
        encoder.set_trns(&[0]);
        let mut writer = encoder.write_header().unwrap();
        writer.write_image_data(&self.pixels).unwrap();
        writer.finish()?;

        Ok(bufwriter.into_inner()?.into_inner())
    }

    // FIXME Only works for 32 tiles and Bpp::Four at the moment.
    /// Rearranges the tiles to be drawn columned.
    ///
    /// This moves the tiles to occupy the given number of columns top to bottom first, then left to
    /// right.
    pub fn columned(&mut self, columns: u8) {
        const ROWS_PER_TILE: usize = 8;
        let tile_row_size = match self.bpp {
            Bpp::One => 1,
            Bpp::Four(_) => 4,
        };
        let tile_size = tile_row_size * ROWS_PER_TILE;

        assert!((self.pixels.len() / tile_size) % columns as usize == 0);

        let mut columned = Vec::new();

        let stride = columns as usize * tile_row_size;
        let rows = 32 / columns as usize;
        for i in (0..1024).step_by(4) {
            let tile_y = (i / stride) / ROWS_PER_TILE;
            let tile_x = (i % stride) * rows / tile_row_size;
            let tile = tile_x + tile_y;
            let row = (i / stride) % ROWS_PER_TILE;

            let offset = (tile) * 32 + row * tile_row_size;
            columned.extend_from_slice(&self.pixels[offset..(offset + 4)]);
        }

        self.pixels = columned;
    }

    pub fn reduce(&mut self, other: &mut Gfx) -> &mut Gfx {
        assert!(self.bpp == other.bpp);
        self.pixels.append(&mut other.pixels);
        self
    }
}

// FIXME Remove.
pub fn as_png(bytes: &[u8], width: u32, bpp: Bpp) -> IoResult<Vec<u8>> {
    assert!(width != 0);
    assert!(width % 8 == 0);

    let buffer = vec![];
    let cursor = std::io::Cursor::new(buffer);
    let mut bufwriter = std::io::BufWriter::new(cursor);

    let bpp_size: usize = bpp.into();
    let height = (bytes.len() / bpp_size / (width / 8) as usize) as u32;
    let mut encoder = png::Encoder::new(&mut bufwriter, width, height);
    encoder.set_color(png::ColorType::Indexed);
    encoder.set_depth(bpp.into());
    encoder.set_palette(bpp.palette());
    // Make the first color transparent.
    encoder.set_trns(&[0]);
    let mut writer = encoder.write_header().unwrap();
    writer.write_image_data(bytes).unwrap();
    writer.finish()?;

    Ok(bufwriter.into_inner()?.into_inner())
}
