// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT

use crate::renderer::Renderer;
use vello_common::color::palette::css::{BLUE, GREEN, LIME, PURPLE, RED, YELLOW};
use vello_common::kurbo::{Affine, Circle, Point, Rect, Shape};
use vello_common::peniko::{BlendMode, Color, Compose, Mix};
use vello_dev_macros::vello_test;

fn compose(ctx: &mut impl Renderer, compose: Compose) {
    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcOver));

    // Draw the destination layer.
    ctx.set_paint(YELLOW.with_alpha(1.0));
    ctx.fill_rect(&Rect::new(10.0, 10.0, 70.0, 70.0));
    // Draw the source layer.
    ctx.push_blend_layer(BlendMode::new(Mix::Normal, compose));
    ctx.set_paint(BLUE.with_alpha(1.0));
    ctx.fill_rect(&Rect::new(30.0, 30.0, 90.0, 90.0));
    // Compose.
    ctx.pop_layer();
    ctx.pop_layer();
}

#[vello_test]
fn compose_src_over(ctx: &mut impl Renderer) {
    compose(ctx, Compose::SrcOver);
}

#[vello_test]
fn compose_xor(ctx: &mut impl Renderer) {
    compose(ctx, Compose::Xor);
}

#[vello_test]
fn compose_clear(ctx: &mut impl Renderer) {
    compose(ctx, Compose::Clear);
}

#[vello_test(width = 100, height = 100)]
fn compose_clear_empty_layer(ctx: &mut impl Renderer) {
    ctx.push_layer(None, None, None, None, None);
    ctx.set_paint(BLUE);
    ctx.fill_rect(&Rect::new(10.0, 10.0, 90.0, 90.0));

    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::Clear));
    ctx.pop_layer();
    ctx.pop_layer();
}

#[vello_test(width = 100, height = 100)]
fn compose_clear_empty_nested_layer(ctx: &mut impl Renderer) {
    ctx.push_layer(None, None, None, None, None);
    ctx.set_paint(BLUE);
    ctx.fill_rect(&Rect::new(10.0, 10.0, 90.0, 90.0));

    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::Clear));
    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::Clear));
    ctx.pop_layer();
    ctx.pop_layer();
    ctx.pop_layer();
}

#[vello_test]
fn compose_copy(ctx: &mut impl Renderer) {
    compose(ctx, Compose::Copy);
}

#[vello_test]
fn compose_dest(ctx: &mut impl Renderer) {
    compose(ctx, Compose::Dest);
}

#[vello_test]
fn compose_dest_over(ctx: &mut impl Renderer) {
    compose(ctx, Compose::DestOver);
}

#[vello_test]
fn compose_src_in(ctx: &mut impl Renderer) {
    compose(ctx, Compose::SrcIn);
}

#[vello_test]
fn compose_src_out(ctx: &mut impl Renderer) {
    compose(ctx, Compose::SrcOut);
}

#[vello_test]
fn compose_dest_in(ctx: &mut impl Renderer) {
    compose(ctx, Compose::DestIn);
}

#[vello_test]
fn compose_dest_out(ctx: &mut impl Renderer) {
    compose(ctx, Compose::DestOut);
}

#[vello_test]
fn compose_src_atop(ctx: &mut impl Renderer) {
    compose(ctx, Compose::SrcAtop);
}

#[vello_test]
fn compose_dest_atop(ctx: &mut impl Renderer) {
    compose(ctx, Compose::DestAtop);
}

#[vello_test]
fn compose_plus(ctx: &mut impl Renderer) {
    compose(ctx, Compose::Plus);
}

#[vello_test(height = 8, width = 100)]
fn composed_layers_nesting(ctx: &mut impl Renderer) {
    ctx.push_blend_layer(BlendMode {
        mix: Mix::Normal,
        compose: Compose::SrcOver,
    });
    {
        ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcOver));
        {
            ctx.set_paint(BLUE);
            ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 4.0));
        }
        ctx.pop_layer();

        ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::DestOut));
        {
            ctx.set_paint(RED);
            ctx.fill_rect(&Rect::new(33.0, 0.0, 66.0, 4.0));
        }
        ctx.pop_layer();
    }
    ctx.pop_layer();
}

#[vello_test(height = 8, width = 100)]
fn repeatedly_compose_to_bottom_layer(ctx: &mut impl Renderer) {
    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcOver));
    {
        ctx.set_paint(BLUE);
        ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 4.0));
    }
    ctx.pop_layer();

    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::DestOut));
    {
        ctx.set_paint(RED);
        ctx.fill_rect(&Rect::new(33.0, 0.0, 66.0, 4.0));
    }
    ctx.pop_layer();
}

#[vello_test(width = 100, height = 100, transparent)]
fn complex_composed_layers(ctx: &mut impl Renderer) {
    ctx.push_blend_layer(BlendMode {
        mix: Mix::Normal,
        compose: Compose::SrcOver,
    });

    ctx.set_paint(Color::from_rgb8(240, 240, 240));
    ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));

    // Base layer circles to shine through
    ctx.set_paint(YELLOW);
    ctx.fill_path(&Circle::new(Point::new(30.0, 30.0), 25.0).to_path(0.1));
    ctx.set_paint(BLUE);
    ctx.fill_path(&Circle::new(Point::new(70.0, 30.0), 25.0).to_path(0.1));
    ctx.set_paint(RED);
    ctx.fill_path(&Circle::new(Point::new(30.0, 70.0), 25.0).to_path(0.1));
    ctx.set_paint(GREEN);
    ctx.fill_path(&Circle::new(Point::new(70.0, 70.0), 25.0).to_path(0.1));

    // Layer 1: Simulate clip to center area using compositing
    ctx.push_layer(None, None, None, None, None);
    {
        // Draw the "clip" shape first
        ctx.set_paint(Color::WHITE);
        ctx.fill_path(&Circle::new(Point::new(50.0, 50.0), 40.0).to_path(0.1));

        // Now use SrcIn to clip content to that shape
        ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcIn));
        {
            // Layer 2: Semi-transparent purple overlay
            ctx.push_layer(None, None, Some(0.7), None, None);
            ctx.set_paint(PURPLE);
            ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));
            ctx.pop_layer();

            // Layer 3: XOR blend mode with smaller clip simulation
            ctx.push_layer(None, None, None, None, None);
            {
                // Draw the "clip" rectangle
                ctx.set_paint(Color::WHITE);
                ctx.fill_rect(&Rect::new(25.0, 25.0, 75.0, 75.0));

                // Use SrcIn to clip the XOR content
                ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcIn));
                {
                    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::Xor));

                    // Draw overlapping circles with XOR
                    ctx.set_paint(RED);
                    ctx.fill_path(&Circle::new(Point::new(40.0, 40.0), 20.0).to_path(0.1));
                    ctx.set_paint(BLUE);
                    ctx.fill_path(&Circle::new(Point::new(60.0, 40.0), 20.0).to_path(0.1));

                    ctx.pop_layer();
                }
                ctx.pop_layer();
            }
            ctx.pop_layer();

            // Layer 4: Nested opacity with Plus blend
            ctx.push_layer(None, None, Some(0.5), None, None);
            ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::Plus));
            {
                ctx.set_paint(YELLOW);
                ctx.fill_path(&Circle::new(Point::new(50.0, 60.0), 15.0).to_path(0.1));

                // Layer 5: "Clip" simulation with SrcIn
                ctx.push_layer(None, None, None, None, None);
                {
                    ctx.set_paint(Color::WHITE);
                    ctx.fill_path(&Circle::new(Point::new(50.0, 50.0), 30.0).to_path(0.1));

                    // Use SrcIn twice (once for the clip, once for the original SrcIn)
                    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcIn));
                    {
                        ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcIn));
                        {
                            ctx.set_paint(GREEN);
                            ctx.fill_rect(&Rect::new(35.0, 35.0, 65.0, 65.0));
                        }
                        ctx.pop_layer();
                    }
                    ctx.pop_layer();
                }
                ctx.pop_layer();
            }
            ctx.pop_layer();
            ctx.pop_layer();

            // Layer 6: Final overlay with DestOut to create a hole
            ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::DestOut));
            ctx.set_paint(Color::BLACK);
            ctx.fill_path(&Circle::new(Point::new(50.0, 50.0), 10.0).to_path(0.1));
            ctx.pop_layer();
        }
        ctx.pop_layer();
    }
    ctx.pop_layer();

    ctx.pop_layer();
}

#[vello_test(
    width = 100,
    height = 100,
    transparent,
    cpu_u8_tolerance = 2,
    hybrid_tolerance = 2
)]
fn deep_compose(ctx: &mut impl Renderer) {
    const INITIAL_RADIUS: f64 = 48.0;
    const RADIUS_DECREMENT: f64 = 4.5;
    const LAYER_COUNT: usize = 10;
    const CENTER: Point = Point::new(50.0, 50.0);

    const COLORS: [Color; LAYER_COUNT] = [
        Color::from_rgba8(120, 0, 0, 50),
        Color::from_rgba8(120, 60, 0, 50),
        Color::from_rgba8(120, 120, 0, 50),
        Color::from_rgba8(60, 120, 0, 50),
        Color::from_rgba8(0, 120, 0, 50),
        Color::from_rgba8(0, 120, 60, 50),
        Color::from_rgba8(0, 120, 120, 50),
        Color::from_rgba8(0, 60, 120, 50),
        Color::from_rgba8(0, 0, 120, 50),
        Color::from_rgba8(60, 0, 120, 50),
    ];

    // Composition modes are intentionally "additive".
    const COMPOSE_MODES: [Compose; LAYER_COUNT] = [
        Compose::SrcOver,
        Compose::Plus,
        Compose::SrcOver,
        Compose::DestOver,
        Compose::SrcOver,
        Compose::Plus,
        Compose::SrcAtop,
        Compose::Plus,
        Compose::SrcOver,
        Compose::SrcOver,
    ];

    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcOver));
    ctx.set_paint(Color::BLACK);
    ctx.fill_rect(&Rect::new(0.0, 0.0, 100.0, 100.0));

    let mut radius = INITIAL_RADIUS;

    for i in 0..LAYER_COUNT {
        ctx.push_blend_layer(BlendMode::new(Mix::Normal, COMPOSE_MODES[i]));

        ctx.set_paint(COLORS[i]);
        ctx.fill_path(&Circle::new(CENTER, radius).to_path(0.1));

        radius -= RADIUS_DECREMENT;
    }

    for _ in 0..=LAYER_COUNT {
        ctx.pop_layer();
    }
}

// Ensure that compose and mix work together in the same blend layer.
#[vello_test(width = 160, height = 160)]
fn mix_compose_combined_test_matrix(ctx: &mut impl Renderer) {
    let mix_modes = [
        Mix::Normal,
        Mix::Multiply,
        Mix::Screen,
        Mix::Overlay,
        Mix::Darken,
        Mix::Lighten,
        Mix::ColorDodge,
        Mix::ColorBurn,
        Mix::HardLight,
        Mix::SoftLight,
        Mix::Difference,
        Mix::Exclusion,
        Mix::Hue,
        Mix::Saturation,
        Mix::Color,
        Mix::Luminosity,
    ];

    let compose_modes = [
        Compose::Clear,
        Compose::Copy,
        Compose::Dest,
        Compose::SrcOver,
        Compose::DestOver,
        Compose::SrcIn,
        Compose::DestIn,
        Compose::SrcOut,
        Compose::DestOut,
        Compose::SrcAtop,
        Compose::DestAtop,
        Compose::Xor,
        Compose::Plus,
        Compose::PlusLighter,
    ];

    let cell_size = 10.0;

    ctx.set_paint(Color::from_rgb8(30, 30, 30));
    ctx.fill_rect(&Rect::new(0.0, 0.0, 160.0, 160.0));

    for (row, mix_mode) in mix_modes.iter().enumerate() {
        for (col, compose_mode) in compose_modes.iter().enumerate() {
            let x = (col as f64) * cell_size;
            let y = (row as f64) * cell_size;

            ctx.set_transform(Affine::translate((x, y)));

            ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcOver));

            // Draw magenta rectangle for destination.
            ctx.set_paint(Color::from_rgb8(200, 0, 200).with_alpha(0.7));
            ctx.fill_rect(&Rect::new(0.0, 0.0, cell_size * 0.7, cell_size * 0.7));

            // Push mix + compose blend layer with fill
            ctx.push_blend_layer(BlendMode::new(*mix_mode, *compose_mode));
            ctx.set_paint(Color::from_rgb8(10, 200, 200).with_alpha(0.7)); // Cyan
            ctx.fill_rect(&Rect::new(
                cell_size * 0.3,
                cell_size * 0.3,
                cell_size,
                cell_size,
            ));
            ctx.pop_layer();

            ctx.pop_layer();
        }
    }
}

fn compose_non_isolated(ctx: &mut impl Renderer, compose: Compose) {
    // Just to isolate from the white background.
    ctx.push_blend_layer(BlendMode::new(Mix::Normal, Compose::SrcOver));

    let rect1 = Rect::new(10.5, 10.5, 70.5, 70.5);
    ctx.set_paint(BLUE.with_alpha(0.5));
    ctx.fill_rect(&rect1);
    ctx.set_blend_mode(BlendMode::new(Mix::Normal, compose));
    let rect2 = Rect::new(30.5, 30.5, 90.5, 90.5);
    ctx.set_paint(LIME.with_alpha(0.5));
    ctx.fill_rect(&rect2);

    ctx.pop_layer();
}

#[vello_test]
fn compose_non_isolated_src_over(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::SrcOver);
}

#[vello_test]
fn compose_non_isolated_xor(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::Xor);
}

#[vello_test(skip_hybrid)]
fn compose_non_isolated_clear(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::Clear);
}

#[vello_test(skip_hybrid)]
fn compose_non_isolated_copy(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::Copy);
}

#[vello_test]
fn compose_non_isolated_dest(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::Dest);
}

#[vello_test]
fn compose_non_isolated_dest_over(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::DestOver);
}

#[vello_test(skip_hybrid)]
fn compose_non_isolated_src_in(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::SrcIn);
}

#[vello_test(skip_hybrid)]
fn compose_non_isolated_src_out(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::SrcOut);
}

#[vello_test(skip_hybrid)]
fn compose_non_isolated_dest_in(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::DestIn);
}

#[vello_test]
fn compose_non_isolated_dest_out(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::DestOut);
}

#[vello_test]
fn compose_non_isolated_src_atop(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::SrcAtop);
}

#[vello_test(skip_hybrid)]
fn compose_non_isolated_dest_atop(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::DestAtop);
}

#[vello_test]
fn compose_non_isolated_plus(ctx: &mut impl Renderer) {
    compose_non_isolated(ctx, Compose::Plus);
}
