use image::{ImageFormat, Rgba, RgbaImage};
use lib::{BlockPalette, StructureExporter, VoxelGrid, VoxelType};

const GAP: u32 = 1;

const COLOR_BODY: [u8; 4]    = [0x4A, 0x90, 0xD9, 0xFF];
const COLOR_OUTLINE: [u8; 4] = [0x88, 0x88, 0x88, 0xFF];
const COLOR_SHADOW: [u8; 4]  = [0xC0, 0xA0, 0x60, 0xFF];
const COLOR_BG: [u8; 4]      = [0x22, 0x22, 0x22, 0xFF];
const COLOR_TEXT: [u8; 4]    = [0xFF, 0xFF, 0xFF, 0xFF];

fn voxel_color(vt: VoxelType) -> [u8; 4] {
    match vt {
        VoxelType::Body    => COLOR_BODY,
        VoxelType::Outline => COLOR_OUTLINE,
        VoxelType::Shadow  => COLOR_SHADOW,
    }
}

fn voxel_letter(vt: VoxelType) -> char {
    match vt {
        VoxelType::Body    => 'B',
        VoxelType::Outline => 'O',
        VoxelType::Shadow  => 'S',
    }
}

/// 5×7 bitmap font indexed by ASCII code. Bit4=left, Bit0=right per row.
static FONT_5X7: [[u8; 7]; 128] = {
    const __: [u8; 7] = [0; 7];
    [
        // 0-31: control chars (blank)
        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
        __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __,
        // 32 ' '
        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
        // 33 '!'
        [0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04],
        // 34-47: unused (blank)
        __, __, __, __, __, __, __, __, __, __, __, __, __, __,
        // 48 '0'
        [0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E],
        // 49 '1'
        [0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E],
        // 50 '2'
        [0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F],
        // 51 '3'
        [0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E],
        // 52 '4'
        [0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02],
        // 53 '5'
        [0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E],
        // 54 '6'
        [0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E],
        // 55 '7'
        [0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08],
        // 56 '8'
        [0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E],
        // 57 '9'
        [0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C],
        // 58 ':'
        [0x00, 0x04, 0x04, 0x00, 0x04, 0x04, 0x00],
        // 59-64: blank
        __, __, __, __, __, __,
        // 65 'A'
        [0x04, 0x0A, 0x11, 0x11, 0x1F, 0x11, 0x11],
        // 66 'B'
        [0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E],
        // 67 'C'
        [0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E],
        // 68 'D'
        [0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C],
        // 69 'E'
        [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F],
        // 70 'F'
        [0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10],
        // 71 'G'
        [0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F],
        // 72 'H'
        [0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11],
        // 73 'I'
        [0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
        // 74 'J'
        [0x07, 0x02, 0x02, 0x02, 0x02, 0x12, 0x0C],
        // 75 'K'
        [0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11],
        // 76 'L'
        [0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F],
        // 77 'M'
        [0x11, 0x1B, 0x15, 0x11, 0x11, 0x11, 0x11],
        // 78 'N'
        [0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11],
        // 79 'O'
        [0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
        // 80 'P'
        [0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10],
        // 81 'Q'
        [0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D],
        // 82 'R'
        [0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11],
        // 83 'S'
        [0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E],
        // 84 'T'
        [0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04],
        // 85 'U'
        [0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E],
        // 86 'V'
        [0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04],
        // 87 'W'
        [0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11],
        // 88 'X'
        [0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11],
        // 89 'Y'
        [0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04],
        // 90 'Z'
        [0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F],
        // 91-94: blank
        __, __, __, __,
        // 95 '_'
        [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F],
        // 96: blank
        __,
        // 97 'a'
        [0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F],
        // 98 'b'
        [0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E],
        // 99 'c'
        [0x00, 0x00, 0x0E, 0x10, 0x10, 0x10, 0x0E],
        // 100 'd'
        [0x01, 0x01, 0x0F, 0x11, 0x11, 0x11, 0x0F],
        // 101 'e'
        [0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E],
        // 102 'f'
        [0x06, 0x09, 0x08, 0x1E, 0x08, 0x08, 0x08],
        // 103 'g'
        [0x00, 0x0F, 0x11, 0x11, 0x0F, 0x01, 0x0E],
        // 104 'h'
        [0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x11],
        // 105 'i'
        [0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E],
        // 106 'j'
        [0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0C],
        // 107 'k'
        [0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12],
        // 108 'l'
        [0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E],
        // 109 'm'
        [0x00, 0x00, 0x1A, 0x15, 0x15, 0x11, 0x11],
        // 110 'n'
        [0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11],
        // 111 'o'
        [0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E],
        // 112 'p'
        [0x00, 0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10],
        // 113 'q'
        [0x00, 0x0F, 0x11, 0x11, 0x0F, 0x01, 0x01],
        // 114 'r'
        [0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10],
        // 115 's'
        [0x00, 0x00, 0x0E, 0x10, 0x0E, 0x01, 0x0E],
        // 116 't'
        [0x08, 0x08, 0x1E, 0x08, 0x08, 0x09, 0x06],
        // 117 'u'
        [0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D],
        // 118 'v'
        [0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04],
        // 119 'w'
        [0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A],
        // 120 'x'
        [0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11],
        // 121 'y'
        [0x00, 0x11, 0x11, 0x0F, 0x01, 0x11, 0x0E],
        // 122 'z'
        [0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F],
        // 123-127: blank
        __, __, __, __, __,
    ]
};

fn draw_rect(img: &mut RgbaImage, x: u32, y: u32, w: u32, h: u32, color: [u8; 4]) {
    let c = Rgba(color);
    for dy in 0..h {
        for dx in 0..w {
            let px = x + dx;
            let py = y + dy;
            if px < img.width() && py < img.height() {
                img.put_pixel(px, py, c);
            }
        }
    }
}

/// Render one char at (x, y). Each font pixel = scale×scale actual pixels.
fn draw_char(img: &mut RgbaImage, x: u32, y: u32, ch: char, scale: u32, color: [u8; 4]) {
    let code = ch as usize;
    if code >= 128 { return; }
    let glyph = FONT_5X7[code];
    for (row, &bits) in glyph.iter().enumerate() {
        for col in 0..5u32 {
            if bits & (1 << (4 - col)) != 0 {
                draw_rect(img, x + col * scale, y + row as u32 * scale, scale, scale, color);
            }
        }
    }
}

/// Render a string left-to-right. Each char is 6*scale px wide (5px + 1px gap).
fn draw_text(img: &mut RgbaImage, x: u32, y: u32, text: &str, scale: u32, color: [u8; 4]) {
    let char_w = 6 * scale;
    for (i, ch) in text.chars().enumerate() {
        draw_char(img, x + (i as u32).saturating_mul(char_w), y, ch, scale, color);
    }
}

// Returns VoxelTypes that (1) are configured in the palette AND (2) appear in the grid.
fn active_voxel_types(palette: &BlockPalette, grid: &VoxelGrid) -> Vec<VoxelType> {
    let mut candidates = vec![VoxelType::Body];
    if palette.blocks.outline.is_some() {
        candidates.push(VoxelType::Outline);
    }
    if palette.blocks.shadow.is_some() {
        candidates.push(VoxelType::Shadow);
    }
    candidates.retain(|&vt| {
        (0..grid.depth).any(|z|
            (0..grid.height).any(|y|
                (0..grid.width).any(|x| grid.get(x, y, z) == Some(vt))
            )
        )
    });
    candidates
}

fn layer_block_height(grid: &VoxelGrid, cell_size: u32) -> u32 {
    let label_height = cell_size + 4;
    let per_layer_height = if grid.height == 0 {
        0
    } else {
        grid.height * (cell_size + GAP) - GAP
    };
    label_height + GAP + per_layer_height
}

pub fn image_dimensions(grid: &VoxelGrid, palette: &BlockPalette, cell_size: u32) -> (u32, u32) {
    let layer_block_height = layer_block_height(grid, cell_size);
    let total_grid_height = grid.depth * layer_block_height
        + grid.depth.saturating_sub(1) * GAP;

    let active = active_voxel_types(palette, grid);
    let legend_height = if active.is_empty() {
        0
    } else {
        active.len() as u32 * (cell_size + GAP) + GAP
    };

    let img_width = if grid.width == 0 {
        1
    } else {
        GAP + grid.width * (cell_size + GAP)
    };
    let img_height = total_grid_height
        + if legend_height > 0 { GAP + legend_height } else { 0 };

    (img_width, img_height)
}

pub struct PngExporter {
    palette: BlockPalette,
    cell_size: u32,
}

impl PngExporter {
    pub fn new(palette: &BlockPalette, cell_size: u32) -> Self {
        Self { palette: palette.clone(), cell_size }
    }
}

fn draw_layer_label(img: &mut RgbaImage, y: u32, z: u32, cell_size: u32) {
    let label = format!("Layer {}", z);
    let scale = (cell_size / 8).max(1);
    let label_height = cell_size + 4;
    let text_h = 7 * scale;
    let text_y = y + (label_height.saturating_sub(text_h)) / 2;
    draw_text(img, GAP, text_y, &label, scale, COLOR_TEXT);
}

fn draw_legend(img: &mut RgbaImage, y_start: u32, palette: &BlockPalette, grid: &VoxelGrid, cell_size: u32) {
    let lscale = (cell_size / 8).max(1);
    for (i, vt) in active_voxel_types(palette, grid).iter().enumerate() {
        let y = y_start + i as u32 * (cell_size + GAP);
        let color = voxel_color(*vt);
        draw_rect(img, GAP, y, cell_size, cell_size, color);
        let lw = 5 * lscale;
        let lh = 7 * lscale;
        let lx = GAP + (cell_size.saturating_sub(lw)) / 2;
        let ly = y + (cell_size.saturating_sub(lh)) / 2;
        draw_char(img, lx, ly, voxel_letter(*vt), lscale, COLOR_TEXT);
        let block_name = palette.resolve(vt);
        let text_x = GAP + cell_size + GAP * 3;
        let text_y = y + (cell_size.saturating_sub(7 * lscale)) / 2;
        draw_text(img, text_x, text_y, &block_name, lscale, COLOR_TEXT);
    }
}

impl StructureExporter for PngExporter {
    fn export(&self, grid: &VoxelGrid) -> anyhow::Result<Vec<u8>> {
        use std::io::Cursor;

        let cs = self.cell_size;
        let lbh = layer_block_height(grid, cs);
        let label_height = cs + 4;

        let (img_w, img_h) = image_dimensions(grid, &self.palette, cs);
        let mut img = RgbaImage::from_pixel(img_w, img_h, Rgba(COLOR_BG));

        for z in 0..grid.depth {
            let layer_top_y = z * (lbh + GAP);
            draw_layer_label(&mut img, layer_top_y, z, cs);

            let grid_top_y = layer_top_y + label_height + GAP;
            for y in 0..grid.height {
                let vy = grid.height - 1 - y;
                for x in 0..grid.width {
                    let px = GAP + x * (cs + GAP);
                    let py = grid_top_y + y * (cs + GAP);
                    match grid.get(x, vy, z) {
                        None => draw_rect(&mut img, px, py, cs, cs, COLOR_BG),
                        Some(vt) => {
                            draw_rect(&mut img, px, py, cs, cs, voxel_color(vt));
                            let lscale = (cs / 8).max(1);
                            let lw = 5 * lscale;
                            let lh = 7 * lscale;
                            let lx = px + (cs.saturating_sub(lw)) / 2;
                            let ly = py + (cs.saturating_sub(lh)) / 2;
                            draw_char(&mut img, lx, ly, voxel_letter(vt), lscale, COLOR_TEXT);
                        }
                    }
                }
            }
        }

        let total_grid_height = grid.depth * lbh
            + grid.depth.saturating_sub(1) * GAP;
        draw_legend(&mut img, total_grid_height + GAP, &self.palette, grid, cs);

        let mut buf = Cursor::new(Vec::new());
        img.write_to(&mut buf, ImageFormat::Png)
            .map_err(|e| anyhow::anyhow!("PNG encode failed: {}", e))?;
        Ok(buf.into_inner())
    }

    fn file_extension(&self) -> &'static str {
        "png"
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn palette_full() -> BlockPalette {
        serde_json::from_str(r#"{
            "name": "test",
            "blocks": {
                "body": "minecraft:stone",
                "outline": "minecraft:cobblestone",
                "shadow": "minecraft:gravel"
            }
        }"#).unwrap()
    }

    fn palette_body_only() -> BlockPalette {
        serde_json::from_str(r#"{
            "name": "test",
            "blocks": { "body": "minecraft:stone" }
        }"#).unwrap()
    }

    #[test]
    fn letter_for_each_voxel_type() {
        assert_eq!(voxel_letter(VoxelType::Body),    'B');
        assert_eq!(voxel_letter(VoxelType::Outline), 'O');
        assert_eq!(voxel_letter(VoxelType::Shadow),  'S');
    }

    #[test]
    fn dimensions_single_layer() {
        let grid = VoxelGrid::new(4, 3, 1);
        let palette = palette_full();
        let (w, h) = image_dimensions(&grid, &palette, 16);
        // width: 1 + 4*(16+1) = 69
        assert_eq!(w, 69);
        // label_height = 20, per_layer = 3*17-1 = 50, layer_block = 71
        // total_grid = 71
        // legend = 3*17 + 1 = 52  (BUT: all-None grid → 0 active types → legend_height=0)
        // total_h = 71 + 0 = 71
        assert_eq!(h, 71);
    }

    #[test]
    fn dimensions_multi_layer() {
        let g1 = VoxelGrid::new(4, 3, 1);
        let g3 = VoxelGrid::new(4, 3, 3);
        let palette = palette_full();
        let (_, h1) = image_dimensions(&g1, &palette, 16);
        let (_, h3) = image_dimensions(&g3, &palette, 16);
        assert!(h3 > h1);
    }

    fn make_grid_with_voxels(w: u32, h: u32, d: u32) -> VoxelGrid {
        let mut grid = VoxelGrid::new(w, h, d);
        let _ = grid.set(0, 0, 0, VoxelType::Body);
        let _ = grid.set(1, 0, 0, VoxelType::Outline);
        if d > 1 { let _ = grid.set(0, 0, 1, VoxelType::Body); }
        grid
    }

    #[test]
    fn export_produces_valid_png() {
        let grid = make_grid_with_voxels(3, 3, 1);
        let bytes = PngExporter::new(&palette_full(), 16).export(&grid).unwrap();
        assert_eq!(&bytes[..8], &[137u8, 80, 78, 71, 13, 10, 26, 10]);
    }

    #[test]
    fn export_empty_grid_no_panic() {
        let grid = VoxelGrid::new(5, 5, 1); // all None
        let bytes = PngExporter::new(&palette_full(), 16).export(&grid).unwrap();
        assert!(!bytes.is_empty());
    }

    #[test]
    fn export_multi_layer_larger_than_single() {
        let g1 = make_grid_with_voxels(4, 4, 1);
        let g3 = make_grid_with_voxels(4, 4, 3);
        let b1 = PngExporter::new(&palette_full(), 16).export(&g1).unwrap();
        let b3 = PngExporter::new(&palette_full(), 16).export(&g3).unwrap();
        assert!(b3.len() > b1.len());
    }

    #[test]
    fn export_palette_with_no_outline() {
        let mut grid = VoxelGrid::new(3, 3, 1);
        let _ = grid.set(0, 0, 0, VoxelType::Outline);
        let bytes = PngExporter::new(&palette_body_only(), 16).export(&grid).unwrap();
        assert!(!bytes.is_empty());
    }
}
