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

//! Path rendering example scenes.
//! Scenes demonstrating various path rendering techniques including strokes, fills, and tricky paths.
//! Adapted from Vello Classic test scenes:
//! - `stroke_styles`, `stroke_styles_non_uniform`, `stroke_styles_skew` methods
//! - `funky_paths` method
//! - `tricky_strokes` method  
//! - `fill_types` method
//! - `robust_paths` method

use crate::{ExampleScene, RenderingContext};
use vello_common::color::palette::css::{AQUA, BLUE, GRAY, LIME, YELLOW};
use vello_common::kurbo::{Affine, BezPath, Cap, Join, Point, Rect, Shape, Stroke};
use vello_common::peniko::{Color, Fill};

/// Stroke styles scene state
#[derive(Debug, Default)]
pub struct StrokeStylesScene {
    transform: Affine,
}

impl StrokeStylesScene {
    /// Create a new stroke styles scene
    pub fn new() -> Self {
        Self {
            transform: Affine::IDENTITY,
        }
    }

    /// Create a new stroke styles scene with non-uniform scale
    pub fn new_non_uniform() -> Self {
        Self {
            transform: Affine::scale_non_uniform(1.2, 0.7),
        }
    }

    /// Create a new stroke styles scene with skew
    pub fn new_skew() -> Self {
        Self {
            transform: Affine::skew(1.0, 0.0),
        }
    }
}

const Y_OFFSET: f64 = 180.0;
const X_OFFSET: f64 = 450.0;

impl ExampleScene for StrokeStylesScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        let colors = [
            Color::from_rgb8(140, 181, 236),
            Color::from_rgb8(246, 236, 202),
            Color::from_rgb8(201, 147, 206),
            Color::from_rgb8(150, 195, 160),
        ];

        // Create path data using BezPath
        let mut simple_stroke = BezPath::new();
        simple_stroke.move_to((0.0, 0.0));
        simple_stroke.line_to((100.0, 0.0));

        let mut join_stroke = BezPath::new();
        join_stroke.move_to((0.0, 0.0));
        join_stroke.curve_to((20.0, 0.0), (42.5, 5.0), (50.0, 25.0));
        join_stroke.curve_to((57.5, 5.0), (80.0, 0.0), (100.0, 0.0));

        let mut miter_stroke = BezPath::new();
        miter_stroke.move_to((0.0, 0.0));
        miter_stroke.line_to((90.0, 16.0));
        miter_stroke.line_to((0.0, 31.0));
        miter_stroke.line_to((90.0, 46.0));

        let mut closed_strokes = BezPath::new();
        closed_strokes.move_to((0.0, 0.0));
        closed_strokes.line_to((90.0, 21.0));
        closed_strokes.line_to((0.0, 42.0));
        closed_strokes.close_path();
        closed_strokes.move_to((200.0, 0.0));
        closed_strokes.curve_to((100.0, 72.0), (300.0, 72.0), (200.0, 0.0));
        closed_strokes.close_path();
        closed_strokes.move_to((290.0, 0.0));
        closed_strokes.curve_to((200.0, 72.0), (400.0, 72.0), (310.0, 0.0));
        closed_strokes.close_path();

        let cap_styles = [Cap::Butt, Cap::Square, Cap::Round];
        let join_styles = [Join::Bevel, Join::Miter, Join::Round];
        let miter_limits = [4.0, 6.0, 0.1, 10.0];

        // Simple strokes with cap combinations
        let t = Affine::translate((60.0, 40.0)) * Affine::scale(2.0);
        let mut y = 0.0;
        let mut color_idx = 0;
        for start in cap_styles {
            for end in cap_styles {
                let stroke = Stroke::new(20.0).with_start_cap(start).with_end_cap(end);
                ctx.set_transform(
                    root_transform * Affine::translate((0.0, y + 30.0)) * t * self.transform,
                );
                ctx.set_paint(colors[color_idx]);
                ctx.set_stroke(stroke);
                ctx.stroke_path(&simple_stroke);
                y += Y_OFFSET;
                color_idx = (color_idx + 1) % colors.len();
            }
        }

        // Dashed strokes with cap combinations
        let t = Affine::translate((X_OFFSET, 0.0)) * t;
        y = 0.0;
        for start in cap_styles {
            for end in cap_styles {
                let stroke = Stroke::new(20.0)
                    .with_start_cap(start)
                    .with_end_cap(end)
                    .with_dashes(0.0, [10.0, 21.0]);
                ctx.set_transform(
                    root_transform * Affine::translate((0.0, y + 30.0)) * t * self.transform,
                );
                ctx.set_paint(colors[color_idx]);
                ctx.set_stroke(stroke);
                ctx.stroke_path(&simple_stroke);
                y += Y_OFFSET;
                color_idx = (color_idx + 1) % colors.len();
            }
        }

        // Cap and join combinations
        let t = Affine::translate((X_OFFSET, 0.0)) * t;
        y = 0.0;
        for cap in cap_styles {
            for join in join_styles {
                let stroke = Stroke::new(20.0).with_caps(cap).with_join(join);
                ctx.set_transform(
                    root_transform * Affine::translate((0.0, y + 30.0)) * t * self.transform,
                );
                ctx.set_paint(colors[color_idx]);
                ctx.set_stroke(stroke);
                ctx.stroke_path(&join_stroke);
                y += Y_OFFSET;
                color_idx = (color_idx + 1) % colors.len();
            }
        }

        // Miter limit
        let t = Affine::translate((X_OFFSET, 0.0)) * t;
        y = 0.0;
        for ml in miter_limits {
            let stroke = Stroke::new(10.0)
                .with_caps(Cap::Butt)
                .with_join(Join::Miter)
                .with_miter_limit(ml);
            ctx.set_transform(
                root_transform * Affine::translate((0.0, y + 30.0)) * t * self.transform,
            );
            ctx.set_paint(colors[color_idx]);
            ctx.set_stroke(stroke);
            ctx.stroke_path(&miter_stroke);
            y += Y_OFFSET;
            color_idx = (color_idx + 1) % colors.len();
        }

        // Closed paths
        for (i, join) in join_styles.iter().enumerate() {
            // The cap style is not important since a closed path shouldn't have any caps.
            let stroke = Stroke::new(10.0)
                .with_caps(cap_styles[i])
                .with_join(*join)
                .with_miter_limit(5.0);
            ctx.set_transform(
                root_transform * Affine::translate((0.0, y + 30.0)) * t * self.transform,
            );
            ctx.set_paint(colors[color_idx]);
            ctx.set_stroke(stroke);
            ctx.stroke_path(&closed_strokes);
            y += Y_OFFSET;
            color_idx = (color_idx + 1) % colors.len();
        }
    }
}

/// Funky paths scene state
#[derive(Debug, Default)]
pub struct FunkyPathsScene {}

impl FunkyPathsScene {
    /// Create a new funky paths scene
    pub fn new() -> Self {
        Self {}
    }
}

// TODO: fix issue https://github.com/linebender/vello/issues/1240
impl ExampleScene for FunkyPathsScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        // Missing movetos path
        let mut missing_movetos = BezPath::new();
        missing_movetos.move_to((0.0, 0.0));
        missing_movetos.line_to((100.0, 100.0));
        missing_movetos.line_to((100.0, 200.0));
        missing_movetos.close_path();
        missing_movetos.line_to((0.0, 400.0));
        missing_movetos.line_to((100.0, 400.0));

        // Only movetos path
        let mut only_movetos = BezPath::new();
        only_movetos.move_to((0.0, 0.0));
        only_movetos.move_to((100.0, 100.0));

        // Empty path
        let empty = BezPath::new();

        ctx.set_transform(root_transform * Affine::translate((100.0, 100.0)));
        ctx.set_paint(BLUE);
        ctx.fill_path(&missing_movetos);

        ctx.set_transform(root_transform);
        ctx.set_paint(BLUE);
        ctx.fill_path(&empty);

        ctx.set_transform(root_transform);
        ctx.set_paint(BLUE);
        ctx.fill_path(&only_movetos);

        ctx.set_transform(root_transform * Affine::translate((100.0, 100.0)));
        ctx.set_paint(AQUA);
        ctx.set_stroke(Stroke::new(8.0));
        ctx.stroke_path(&missing_movetos);
    }
}

/// Tricky strokes scene state
#[derive(Debug, Default)]
pub struct TrickyStrokesScene {}

impl TrickyStrokesScene {
    /// Create a new tricky strokes scene
    pub fn new() -> Self {
        Self {}
    }
}

impl ExampleScene for TrickyStrokesScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        let colors = [
            Color::from_rgb8(140, 181, 236),
            Color::from_rgb8(246, 236, 202),
            Color::from_rgb8(201, 147, 206),
            Color::from_rgb8(150, 195, 160),
        ];

        const CELL_SIZE: f64 = 200.0;
        const STROKE_WIDTH: f64 = 30.0;
        const NUM_COLS: usize = 5;

        fn stroke_bounds(pts: &[(f64, f64); 4]) -> Rect {
            use vello_common::kurbo::CubicBez;
            CubicBez::new(pts[0], pts[1], pts[2], pts[3])
                .bounding_box()
                .inflate(STROKE_WIDTH, STROKE_WIDTH)
        }

        fn map_rect_to_rect(src: &Rect, dst: &Rect) -> (Affine, f64) {
            let (scale, x_larger) = {
                let sx = dst.width() / src.width();
                let sy = dst.height() / src.height();
                (sx.min(sy), sx > sy)
            };
            let tx = dst.x0 - src.x0 * scale;
            let ty = dst.y0 - src.y0 * scale;
            let (tx, ty) = if x_larger {
                (tx + 0.5 * (dst.width() - src.width() * scale), ty)
            } else {
                (tx, ty + 0.5 * (dst.height() - src.height() * scale))
            };
            (Affine::new([scale, 0.0, 0.0, scale, tx, ty]), scale)
        }

        let tricky_cubics = [
            [(122., 737.), (348., 553.), (403., 761.), (400., 760.)],
            [(244., 520.), (244., 518.), (1141., 634.), (394., 688.)],
            [(550., 194.), (138., 130.), (1035., 246.), (288., 300.)],
            [(226., 733.), (556., 779.), (-43., 471.), (348., 683.)],
            [(268., 204.), (492., 304.), (352., 23.), (433., 412.)],
            [(172., 480.), (396., 580.), (256., 299.), (338., 677.)],
            [(731., 340.), (318., 252.), (1026., -64.), (367., 265.)],
            [(475., 708.), (62., 620.), (770., 304.), (220., 659.)],
            [(0., 0.), (128., 128.), (128., 0.), (0., 128.)], // Perfect cusp
            [(0., 0.01), (128., 127.999), (128., 0.01), (0., 127.99)], // Near-cusp
            [(0., -0.01), (128., 128.001), (128., -0.01), (0., 128.001)], // Near-cusp
            [(0., 0.), (0., -10.), (0., -10.), (0., 10.)],    // Flat line with 180
            [(10., 0.), (0., 0.), (20., 0.), (10., 0.)],      // Flat line with 2 180s
            [(39., -39.), (40., -40.), (40., -40.), (0., 0.)], // Flat diagonal with 180
            [(40., 40.), (0., 0.), (200., 200.), (0., 0.)],   // Diag w/ an internal 180
            [(0., 0.), (1e-2, 0.), (-1e-2, 0.), (0., 0.)],    // Circle
            // Flat line with no turns:
            [
                (400.75, 100.05),
                (400.75, 100.05),
                (100.05, 300.95),
                (100.05, 300.95),
            ],
            [(0.5, 0.), (0., 0.), (20., 0.), (10., 0.)], // Flat line with 2 180s
            [(10., 0.), (0., 0.), (10., 0.), (10., 0.)], // Flat line with a 180
        ];

        let mut idx = 0;
        let mut color_idx = 0;
        for (i, cubic) in tricky_cubics.into_iter().enumerate() {
            idx += 1;
            let x = (i % NUM_COLS) as f64 * CELL_SIZE;
            let y = (i / NUM_COLS) as f64 * CELL_SIZE;
            let cell = Rect::new(x, y, x + CELL_SIZE, y + CELL_SIZE);
            let bounds = stroke_bounds(&cubic);
            let (t, s) = map_rect_to_rect(&bounds, &cell);

            let mut path = BezPath::new();
            path.move_to(cubic[0]);
            path.curve_to(cubic[1], cubic[2], cubic[3]);

            ctx.set_transform(root_transform * t);
            ctx.set_paint(colors[color_idx]);
            ctx.set_stroke(
                Stroke::new(STROKE_WIDTH / s)
                    .with_caps(Cap::Butt)
                    .with_join(Join::Miter),
            );
            ctx.stroke_path(&path);
            color_idx = (color_idx + 1) % colors.len();
        }

        // Add some flat curves as well
        let flat_quad = [[(2., 1.), (1., 1.)]];
        let flat_curves = [flat_quad.as_slice()];

        for quads in flat_curves.iter() {
            let mut path = BezPath::new();
            path.move_to((1.0, 1.0));
            for quad in quads.iter() {
                path.quad_to(quad[0], quad[1]);
            }
            let x = (idx % NUM_COLS) as f64 * CELL_SIZE;
            let y = (idx / NUM_COLS) as f64 * CELL_SIZE;
            let cell = Rect::new(x, y, x + CELL_SIZE, y + CELL_SIZE);
            let bounds = path.bounding_box().inflate(STROKE_WIDTH, STROKE_WIDTH);
            let (t, s) = map_rect_to_rect(&bounds, &cell);

            ctx.set_transform(root_transform * t);
            ctx.set_paint(colors[color_idx]);
            ctx.set_stroke(
                Stroke::new(STROKE_WIDTH / s)
                    .with_caps(Cap::Butt)
                    .with_join(Join::Miter),
            );
            ctx.stroke_path(&path);
            color_idx = (color_idx + 1) % colors.len();
            idx += 1;
        }
    }
}

/// Fill types scene state
#[derive(Debug, Default)]
pub struct FillTypesScene {}

impl FillTypesScene {
    /// Create a new fill types scene
    pub fn new() -> Self {
        Self {}
    }
}

impl ExampleScene for FillTypesScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        let rect = Rect::from_origin_size(Point::new(0.0, 0.0), (500.0, 500.0));

        // Create star path
        let mut star = BezPath::new();
        star.move_to((250.0, 0.0));
        star.line_to((105.0, 450.0));
        star.line_to((490.0, 175.0));
        star.line_to((10.0, 175.0));
        star.line_to((395.0, 450.0));
        star.close_path();

        // Create arcs path
        let mut arcs = BezPath::new();
        arcs.move_to((0.0, 480.0));
        arcs.curve_to((500.0, 480.0), (500.0, -10.0), (0.0, -10.0));
        arcs.close_path();
        arcs.move_to((500.0, -10.0));
        arcs.curve_to((0.0, -10.0), (0.0, 480.0), (500.0, 480.0));
        arcs.close_path();

        let scale = Affine::scale(0.6);
        let t = Affine::translate((10.0, 25.0));
        let rules = [
            (Fill::NonZero, "Non-Zero", &star),
            (Fill::EvenOdd, "Even-Odd", &star),
            (Fill::NonZero, "Non-Zero", &arcs),
            (Fill::EvenOdd, "Even-Odd", &arcs),
        ];

        // Draw basic fills
        for (i, rule) in rules.iter().enumerate() {
            let t = Affine::translate(((i % 2) as f64 * 306.0, (i / 2) as f64 * 340.0)) * t;

            let t = Affine::translate((0.0, 5.0)) * t * scale;

            // Gray background
            ctx.set_transform(root_transform * t);
            ctx.set_paint(GRAY);
            ctx.fill_rect(&rect);

            // Fill with rule
            ctx.set_transform(root_transform * Affine::translate((0.0, 10.0)) * t);
            ctx.set_paint(YELLOW);
            ctx.set_fill_rule(rule.0);
            ctx.fill_path(rule.2);
        }

        // Draw blends
        let t = Affine::translate((700.0, 0.0)) * t;
        for (i, rule) in rules.iter().enumerate() {
            let t = Affine::translate(((i % 2) as f64 * 306.0, (i / 2) as f64 * 340.0)) * t;

            let t = Affine::translate((0.0, 5.0)) * t * scale;

            // Gray background
            ctx.set_transform(root_transform * t);
            ctx.set_paint(GRAY);
            ctx.fill_rect(&rect);

            // First fill
            ctx.set_transform(root_transform * Affine::translate((0.0, 10.0)) * t);
            ctx.set_paint(YELLOW);
            ctx.set_fill_rule(rule.0);
            ctx.fill_path(rule.2);

            // Second fill with rotation
            ctx.set_transform(
                root_transform * Affine::translate((0.0, 10.0)) * t * Affine::rotate(0.06),
            );
            ctx.set_paint(Color::new([0.0, 1.0, 0.7, 0.6]));
            ctx.set_fill_rule(rule.0);
            ctx.fill_path(rule.2);

            // Third fill with opposite rotation
            ctx.set_transform(
                root_transform * Affine::translate((0.0, 10.0)) * t * Affine::rotate(-0.06),
            );
            ctx.set_paint(Color::new([0.9, 0.7, 0.5, 0.6]));
            ctx.set_fill_rule(rule.0);
            ctx.fill_path(rule.2);
        }
    }
}

/// Robust paths scene state
#[derive(Debug, Default)]
pub struct RobustPathsScene {}

impl RobustPathsScene {
    /// Create a new robust paths scene
    pub fn new() -> Self {
        Self {}
    }
}

impl ExampleScene for RobustPathsScene {
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        _resources: &mut T::Resources,
        root_transform: Affine,
    ) {
        let mut path = BezPath::new();
        path.move_to((16.0, 16.0));
        path.line_to((32.0, 16.0));
        path.line_to((32.0, 32.0));
        path.line_to((16.0, 32.0));
        path.close_path();
        path.move_to((48.0, 18.0));
        path.line_to((64.0, 23.0));
        path.line_to((64.0, 33.0));
        path.line_to((48.0, 38.0));
        path.close_path();
        path.move_to((80.0, 18.0));
        path.line_to((82.0, 16.0));
        path.line_to((94.0, 16.0));
        path.line_to((96.0, 18.0));
        path.line_to((96.0, 30.0));
        path.line_to((94.0, 32.0));
        path.line_to((82.0, 32.0));
        path.line_to((80.0, 30.0));
        path.close_path();
        path.move_to((112.0, 16.0));
        path.line_to((128.0, 16.0));
        path.line_to((128.0, 32.0));
        path.close_path();
        path.move_to((144.0, 16.0));
        path.line_to((160.0, 32.0));
        path.line_to((144.0, 32.0));
        path.close_path();
        path.move_to((168.0, 8.0));
        path.line_to((184.0, 8.0));
        path.line_to((184.0, 24.0));
        path.close_path();
        path.move_to((200.0, 8.0));
        path.line_to((216.0, 24.0));
        path.line_to((200.0, 24.0));
        path.close_path();
        path.move_to((241.0, 17.5));
        path.line_to((255.0, 17.5));
        path.line_to((255.0, 19.5));
        path.line_to((241.0, 19.5));
        path.close_path();
        path.move_to((241.0, 22.5));
        path.line_to((256.0, 22.5));
        path.line_to((256.0, 24.5));
        path.line_to((241.0, 24.5));
        path.close_path();

        // Fill with NonZero rule
        ctx.set_transform(root_transform);
        ctx.set_paint(YELLOW);
        ctx.fill_path(&path);

        // Fill with EvenOdd rule
        ctx.set_transform(root_transform * Affine::translate((300.0, 0.0)));
        ctx.set_paint(LIME);
        ctx.set_fill_rule(Fill::EvenOdd);
        ctx.fill_path(&path);

        // Add another path section
        path.move_to((8.0, 4.0));
        path.line_to((8.0, 40.0));
        path.line_to((260.0, 40.0));
        path.line_to((260.0, 4.0));
        path.close_path();

        // Fill with NonZero rule
        ctx.set_transform(root_transform * Affine::translate((0.0, 100.0)));
        ctx.set_paint(YELLOW);
        ctx.fill_path(&path);

        // Fill with EvenOdd rule
        ctx.set_transform(root_transform * Affine::translate((300.0, 100.0)));
        ctx.set_paint(LIME);
        ctx.set_fill_rule(Fill::EvenOdd);
        ctx.fill_path(&path);
    }
}
