//! Integral image tests

const std = @import("std");
const expectEqual = std.testing.expectEqual;
const Image = @import("../../image.zig").Image;
const color = @import("../../color.zig");
const Rgb = color.Rgb(u8);
const Rgba = color.Rgba(u8);
const Integral = @import("../integral.zig").Integral;

test "integral image scalar" {
    var image: Image(u8) = try .init(std.testing.allocator, 21, 13);
    defer image.deinit(std.testing.allocator);
    for (image.data) |*i| i.* = 1;
    var planes = Image(u8).Integral.Planes.init();
    defer planes.deinit(std.testing.allocator);
    try image.integral(std.testing.allocator, &planes);
    const integral = planes.planes[0];
    try expectEqual(image.rows, integral.rows);
    try expectEqual(image.cols, integral.cols);
    try expectEqual(image.data.len, integral.data.len);
    for (0..image.rows) |r| {
        for (0..image.cols) |c| {
            const area_at_pos: f32 = @floatFromInt((r + 1) * (c + 1));
            try expectEqual(area_at_pos, integral.at(r, c).*);
        }
    }
}

test "integral image view scalar" {
    var image: Image(u8) = try .init(std.testing.allocator, 21, 13);
    defer image.deinit(std.testing.allocator);
    for (image.data) |*i| i.* = 1;
    const view = image.view(.{ .l = 2, .t = 3, .r = 8, .b = 10 });
    var planes = Image(u8).Integral.Planes.init();
    defer planes.deinit(std.testing.allocator);
    try view.integral(std.testing.allocator, &planes);
    const integral = planes.planes[0];
    try expectEqual(view.rows, integral.rows);
    try expectEqual(view.cols, integral.cols);
    for (0..view.rows) |r| {
        for (0..view.cols) |c| {
            const area_at_pos: f32 = @floatFromInt((r + 1) * (c + 1));
            try expectEqual(area_at_pos, integral.at(r, c).*);
        }
    }
}

test "integral image struct" {
    var image: Image(Rgba) = try .init(std.testing.allocator, 21, 13);
    defer image.deinit(std.testing.allocator);
    for (image.data) |*i| i.* = .{ .r = 1, .g = 1, .b = 1, .a = 1 };
    var planes = Image(Rgba).Integral.Planes.init();
    defer planes.deinit(std.testing.allocator);
    try image.integral(std.testing.allocator, &planes);

    for (0..4) |ch| {
        const integral = planes.planes[ch];
        try expectEqual(image.rows, integral.rows);
        try expectEqual(image.cols, integral.cols);
        for (0..image.rows) |r| {
            for (0..image.cols) |c| {
                const area_at_pos: f32 = @floatFromInt((r + 1) * (c + 1));
                try expectEqual(area_at_pos, integral.at(r, c).*);
            }
        }
    }
}

test "integral image RGB vs RGBA with full alpha produces same RGB values" {
    const test_size = 10;

    // Create RGB image
    var rgb_img = try Image(Rgb).init(std.testing.allocator, test_size, test_size);
    defer rgb_img.deinit(std.testing.allocator);

    // Create RGBA image
    var rgba_img = try Image(Rgba).init(std.testing.allocator, test_size, test_size);
    defer rgba_img.deinit(std.testing.allocator);

    // Fill both with identical RGB values
    var seed: u8 = 0;
    for (0..test_size) |r| {
        for (0..test_size) |c| {
            seed +%= 17;
            const r_val = seed;
            const g_val = seed +% 50;
            const b_val = seed +% 100;

            rgb_img.at(r, c).* = .{ .r = r_val, .g = g_val, .b = b_val };
            rgba_img.at(r, c).* = .{ .r = r_val, .g = g_val, .b = b_val, .a = 255 };
        }
    }

    // Apply integral to both
    var rgb_planes = Image(Rgb).Integral.Planes.init();
    defer rgb_planes.deinit(std.testing.allocator);
    try rgb_img.integral(std.testing.allocator, &rgb_planes);

    var rgba_planes = Image(Rgba).Integral.Planes.init();
    defer rgba_planes.deinit(std.testing.allocator);
    try rgba_img.integral(std.testing.allocator, &rgba_planes);

    // Compare RGB channels - they should be identical
    for (0..test_size) |r| {
        for (0..test_size) |c| {
            for (0..3) |ch| {
                const rgb = rgb_planes.planes[ch].at(r, c).*;
                const rgba = rgba_planes.planes[ch].at(r, c).*;
                try expectEqual(rgb, rgba);
            }
        }
    }
}

test "computeIntegralSum function" {
    const allocator = std.testing.allocator;

    // Create a simple 3x3 test image with values 1-9
    var src = try Image(u8).init(allocator, 3, 3);
    defer src.deinit(allocator);

    for (0..3) |r| {
        for (0..3) |c| {
            src.at(r, c).* = @intCast(r * 3 + c + 1);
        }
    }

    // Build integral image using the integralPlane function directly
    var integral_img = try Image(f32).init(allocator, 3, 3);
    defer integral_img.deinit(allocator);

    Integral(u8).plane(src, integral_img);

    // Test box sum computation using computeIntegralSum
    const sum_all = Integral(u8).sum(integral_img, 0, 0, 2, 2);
    try expectEqual(@as(f32, 45), sum_all); // 1+2+3+4+5+6+7+8+9 = 45

    // Test a smaller box (top-left 2x2)
    const sum_2x2 = Integral(u8).sum(integral_img, 0, 0, 1, 1);
    try expectEqual(@as(f32, 12), sum_2x2); // 1+2+4+5 = 12

    // Test bottom-right 2x2
    const sum_br = Integral(f32).sum(integral_img, 1, 1, 2, 2);
    try expectEqual(@as(f32, 28), sum_br); // 5+6+8+9 = 28

    // Test single pixel
    const sum_single = Integral(f32).sum(integral_img, 1, 1, 1, 1);
    try expectEqual(@as(f32, 5), sum_single); // Just the center pixel
}
