use std::path::PathBuf;

use bone_jig::baker::{BakedPage, bake};
use bone_render::{AtlasGrid, AtlasPage, encode_png_rgba};
use bone_types::IconId;

const UPDATE_ENV: &str = "BONE_UPDATE_ICON_ATLAS";

fn committed_path(page: AtlasPage) -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(format!(
        "../bone-render/assets/icons_{}.png",
        page.side_px()
    ))
}

fn baked() -> Vec<BakedPage> {
    match bake() {
        Ok(pages) => pages,
        Err(error) => panic!("icon bake failed, is a software adapter available: {error}"),
    }
}

#[test]
fn committed_atlas_pages_match_generator() {
    let pages = baked();
    let updating = std::env::var(UPDATE_ENV).is_ok();

    pages.iter().for_each(|page| {
        let Ok(png) = encode_png_rgba(page.extent, &page.rgba) else {
            panic!("encode_png_rgba failed for page {:?}", page.page);
        };
        let path = committed_path(page.page);
        if updating {
            let Ok(()) = std::fs::write(&path, &png) else {
                panic!("write atlas page {}", path.display());
            };
            return;
        }
        let Ok(committed) = std::fs::read(&path) else {
            panic!(
                "committed {} missing; rerun with {UPDATE_ENV}=1 to create it",
                path.display(),
            );
        };
        assert_eq!(
            committed,
            png,
            "committed {} drifted from the baker; rerun with {UPDATE_ENV}=1 to refresh",
            path.display(),
        );
    });
}

#[test]
fn every_tile_has_visible_pixels() {
    let grid = AtlasGrid::DEFAULT;
    baked().iter().for_each(|page| {
        let empty: Vec<IconId> = IconId::ALL
            .iter()
            .copied()
            .filter(|id| tile_alpha_sum(page, grid, *id) == 0)
            .collect();
        assert!(
            empty.is_empty(),
            "page {:?} baked empty tiles: {empty:?}",
            page.page,
        );
    });
}

fn tile_alpha_sum(page: &BakedPage, grid: AtlasGrid, id: IconId) -> u32 {
    let (width, _) = grid.pixel_size(page.page);
    let width = usize_of(width);
    let side = usize_of(page.page.side_px());
    let (ox, oy) = grid.tile_origin(grid.cell(id.tile()), page.page);
    let (ox, oy) = (usize_of(ox), usize_of(oy));
    (0..side)
        .flat_map(|dy| (0..side).map(move |dx| (dx, dy)))
        .map(|(dx, dy)| u32::from(page.rgba[((oy + dy) * width + (ox + dx)) * 4 + 3]))
        .sum()
}

fn usize_of(value: u32) -> usize {
    let Ok(out) = usize::try_from(value) else {
        panic!("atlas dimension fits usize");
    };
    out
}
