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

//! Example scenes for Vello Sparse Strips.

pub mod blend;
pub mod blurred_rounded_rect;
pub mod clip;
pub mod emoji_grid;
pub mod filter;
pub mod filter_blur_circle;
pub mod filter_elements;
pub mod gradient;
pub mod image;
pub mod multi_image;
pub mod path;
pub mod random_text;
pub mod simple;
pub mod spritesheet;
pub mod svg;
pub mod text;

use glifo::GlyphRunBackend;
use vello_common::filter_effects::Filter;
use vello_common::kurbo::Affine;
pub use vello_common::kurbo::{BezPath, Rect, Shape, Stroke};
pub use vello_common::mask::Mask;
use vello_common::paint::ImageSource;
pub use vello_common::paint::{Paint, PaintType};
pub use vello_common::peniko::{BlendMode, Fill, FontData, ImageQuality};
#[cfg(feature = "cpu")]
use vello_cpu::{RenderContext, Resources as CpuResources};
use vello_hybrid::{Resources as HybridResources, Scene};
pub use vello_hybrid::{SampleRect, TextureId};

/// Renderer capability flags controlling which scenes are listed by [`get_example_scenes`].
///
/// Use this to pass the features the host renderer supports. Scenes requiring an unsupported
/// feature are omitted. Defaults to everything off.
#[derive(Default, Clone, Copy, Debug)]
pub struct Capabilities {
    /// Whether the renderer supports externally bound textures and
    /// [`RenderingContext::draw_texture_rects`].
    pub external_textures: bool,
}

/// A generic rendering context.
pub trait RenderingContext: Sized {
    /// Backend-specific resource bundle required during rendering.
    type Resources;
    /// Backend-specific glyph backend used by [`glifo::GlyphRunBuilder`].
    type GlyphRunBackend<'a>: GlyphRunBackend<'a>
    where
        Self: 'a;

    /// Width of the render target in pixels.
    fn width(&self) -> u16;
    /// Height of the render target in pixels.
    fn height(&self) -> u16;

    /// Set the current transform.
    fn set_transform(&mut self, transform: Affine);
    /// Set the current paint transform.
    fn set_paint_transform(&mut self, transform: Affine);
    /// Set the current fill rule.
    fn set_fill_rule(&mut self, fill_rule: Fill);
    /// Set the current paint.
    fn set_paint(&mut self, paint: impl Into<PaintType>);
    /// Set the current filter effect.
    fn set_filter_effect(&mut self, filter: Filter);
    /// Reset the current filter effect.
    fn reset_filter_effect(&mut self);
    /// Push a filter layer.
    fn push_filter_layer(&mut self, filter: Filter);
    /// Set the current stroke style.
    fn set_stroke(&mut self, stroke: Stroke);
    /// Fill a path with the current paint.
    fn fill_path(&mut self, path: &BezPath);
    /// Stroke a path with the current paint and stroke style.
    fn stroke_path(&mut self, path: &BezPath);
    /// Fill a rectangle with the current paint.
    fn fill_rect(&mut self, rect: &Rect);
    /// Fill a blurred rounded rectangle with the current solid paint.
    fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32);
    /// Create a glyph run builder for text rendering.
    fn glyph_run<'a>(
        &'a mut self,
        resources: &'a mut Self::Resources,
        font: &FontData,
    ) -> glifo::GlyphRunBuilder<'a, Self::GlyphRunBackend<'a>>;
    /// Push a clip layer.
    fn push_clip_layer(&mut self, path: &BezPath);
    /// Push a clip path.
    fn push_clip_path(&mut self, path: &BezPath);
    /// Push a layer with blend mode, alpha, etc.
    fn push_layer(
        &mut self,
        clip: Option<&BezPath>,
        blend_mode: Option<BlendMode>,
        alpha: Option<f32>,
        mask: Option<Mask>,
        filter: Option<Filter>,
    );
    /// Pop the current layer.
    fn pop_layer(&mut self);
    /// Pop the last clip path.
    fn pop_clip_path(&mut self);
    /// Sample rectangular regions from an externally bound texture and draw them with the
    /// corresponding transforms.
    fn draw_texture_rects(
        &mut self,
        texture_id: TextureId,
        quality: ImageQuality,
        rects: impl IntoIterator<Item = SampleRect>,
    );
}

#[cfg(feature = "cpu")]
impl RenderingContext for RenderContext {
    type Resources = CpuResources;
    type GlyphRunBackend<'a> = vello_cpu::CpuGlyphRunBackend<'a>;

    fn width(&self) -> u16 {
        self.width()
    }

    fn height(&self) -> u16 {
        self.height()
    }

    fn set_transform(&mut self, transform: Affine) {
        self.set_transform(transform);
    }

    fn set_paint(&mut self, paint: impl Into<PaintType>) {
        self.set_paint(paint);
    }

    fn set_paint_transform(&mut self, transform: Affine) {
        self.set_paint_transform(transform);
    }

    fn set_fill_rule(&mut self, fill_rule: Fill) {
        self.set_fill_rule(fill_rule);
    }

    fn set_filter_effect(&mut self, filter: Filter) {
        self.set_filter_effect(filter);
    }

    fn reset_filter_effect(&mut self) {
        self.reset_filter_effect();
    }

    fn push_filter_layer(&mut self, filter: Filter) {
        self.push_filter_layer(filter);
    }

    fn set_stroke(&mut self, stroke: Stroke) {
        self.set_stroke(stroke);
    }

    fn fill_path(&mut self, path: &BezPath) {
        self.fill_path(path);
    }

    fn stroke_path(&mut self, path: &BezPath) {
        self.stroke_path(path);
    }

    fn fill_rect(&mut self, rect: &Rect) {
        self.fill_rect(rect);
    }

    fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32) {
        self.fill_blurred_rounded_rect(rect, radius, std_dev, false);
    }

    fn glyph_run<'a>(
        &'a mut self,
        resources: &'a mut Self::Resources,
        font: &FontData,
    ) -> glifo::GlyphRunBuilder<'a, Self::GlyphRunBackend<'a>> {
        self.glyph_run(resources, font)
    }

    fn push_clip_layer(&mut self, path: &BezPath) {
        self.push_clip_layer(path);
    }

    fn push_layer(
        &mut self,
        clip: Option<&BezPath>,
        blend_mode: Option<BlendMode>,
        alpha: Option<f32>,
        mask: Option<Mask>,
        filter: Option<Filter>,
    ) {
        self.push_layer(clip, blend_mode, alpha, mask, filter);
    }

    fn pop_layer(&mut self) {
        self.pop_layer();
    }

    fn push_clip_path(&mut self, path: &BezPath) {
        Self::push_clip_path(self, path);
    }

    fn pop_clip_path(&mut self) {
        Self::pop_clip_path(self);
    }

    fn draw_texture_rects(
        &mut self,
        _texture_id: TextureId,
        _quality: ImageQuality,
        _rects: impl IntoIterator<Item = SampleRect>,
    ) {
        unimplemented!("vello_cpu does not yet support external textures");
    }
}

impl RenderingContext for Scene {
    type Resources = HybridResources;
    type GlyphRunBackend<'a> = vello_hybrid::HybridGlyphRunBackend<'a>;

    fn width(&self) -> u16 {
        self.width()
    }

    fn height(&self) -> u16 {
        self.height()
    }

    fn set_transform(&mut self, transform: Affine) {
        self.set_transform(transform);
    }

    fn set_filter_effect(&mut self, filter: Filter) {
        self.set_filter_effect(filter);
    }

    fn reset_filter_effect(&mut self) {
        self.reset_filter_effect();
    }

    fn push_filter_layer(&mut self, filter: Filter) {
        self.push_filter_layer(filter);
    }

    fn set_paint(&mut self, paint: impl Into<PaintType>) {
        self.set_paint(paint);
    }

    fn set_paint_transform(&mut self, transform: Affine) {
        self.set_paint_transform(transform);
    }

    fn set_fill_rule(&mut self, fill_rule: Fill) {
        self.set_fill_rule(fill_rule);
    }

    fn set_stroke(&mut self, stroke: Stroke) {
        self.set_stroke(stroke);
    }

    fn fill_path(&mut self, path: &BezPath) {
        self.fill_path(path);
    }

    fn stroke_path(&mut self, path: &BezPath) {
        self.stroke_path(path);
    }

    fn fill_rect(&mut self, rect: &Rect) {
        self.fill_rect(rect);
    }

    fn fill_blurred_rounded_rect(&mut self, rect: &Rect, radius: f32, std_dev: f32) {
        self.fill_blurred_rounded_rect(rect, radius, std_dev, false);
    }

    fn glyph_run<'a>(
        &'a mut self,
        resources: &'a mut Self::Resources,
        font: &FontData,
    ) -> glifo::GlyphRunBuilder<'a, Self::GlyphRunBackend<'a>> {
        self.glyph_run(resources, font)
    }

    fn push_clip_layer(&mut self, path: &BezPath) {
        self.push_clip_layer(path);
    }

    fn push_layer(
        &mut self,
        clip: Option<&BezPath>,
        blend_mode: Option<BlendMode>,
        alpha: Option<f32>,
        mask: Option<Mask>,
        filter: Option<Filter>,
    ) {
        self.push_layer(clip, blend_mode, alpha, mask, filter);
    }

    fn pop_layer(&mut self) {
        self.pop_layer();
    }

    fn push_clip_path(&mut self, path: &BezPath) {
        Self::push_clip_path(self, path);
    }

    fn pop_clip_path(&mut self) {
        Self::pop_clip_path(self);
    }

    fn draw_texture_rects(
        &mut self,
        texture_id: TextureId,
        quality: ImageQuality,
        rects: impl IntoIterator<Item = SampleRect>,
    ) {
        self.draw_texture_rects(texture_id, quality, rects);
    }
}

/// Example scene that can maintain state between renders.
pub trait ExampleScene {
    /// Render the scene using the current state.
    fn render<T: RenderingContext>(
        &mut self,
        ctx: &mut T,
        resources: &mut T::Resources,
        root_transform: Affine,
    );

    /// Handle key press events (optional).
    /// Returns true if the key was handled, false otherwise.
    fn handle_key(&mut self, _key: &str) -> bool {
        false
    }

    /// Optional status string shown in the window title (e.g. element count).
    fn status(&self) -> Option<String> {
        None
    }
}

/// A type-erased example scene.
pub struct AnyScene<T: RenderingContext> {
    /// The render function that calls the wrapped scene's render method.
    render_fn: RenderFn<T>,
    /// The key handler function.
    key_handler_fn: KeyHandlerFn,
    /// The status query function.
    status_fn: StatusFn,
}

/// A type-erased render function.
type RenderFn<T> = Box<dyn FnMut(&mut T, &mut <T as RenderingContext>::Resources, Affine)>;

/// A type-erased key handler function.
type KeyHandlerFn = Box<dyn FnMut(&str) -> bool>;

/// A type-erased status function.
type StatusFn = Box<dyn Fn() -> Option<String>>;

impl<T: RenderingContext> std::fmt::Debug for AnyScene<T> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AnyScene").finish_non_exhaustive()
    }
}

impl<T> AnyScene<T>
where
    T: RenderingContext,
{
    /// Create a new `AnyScene` from any type that implements `ExampleScene`.
    pub fn new<S: ExampleScene + 'static>(scene: S) -> Self {
        let scene = std::rc::Rc::new(std::cell::RefCell::new(scene));
        let scene_clone = scene.clone();
        let scene_status = scene.clone();

        Self {
            render_fn: Box::new(move |s, resources, transform| {
                scene.borrow_mut().render(s, resources, transform);
            }),
            key_handler_fn: Box::new(move |key| scene_clone.borrow_mut().handle_key(key)),
            status_fn: Box::new(move || scene_status.borrow().status()),
        }
    }

    /// Render the scene.
    pub fn render(&mut self, ctx: &mut T, resources: &mut T::Resources, root_transform: Affine) {
        (self.render_fn)(ctx, resources, root_transform);
    }

    /// Handle key press events.
    /// Returns true if the key was handled, false otherwise.
    pub fn handle_key(&mut self, key: &str) -> bool {
        (self.key_handler_fn)(key)
    }

    /// Get an optional status string from the scene.
    pub fn status(&self) -> Option<String> {
        (self.status_fn)()
    }
}

/// Get all available example scenes.
/// Unlike the Wasm version, this function allows for passing custom SVGs.
#[cfg(not(target_arch = "wasm32"))]
pub fn get_example_scenes<T: RenderingContext + 'static>(
    capabilities: Capabilities,
    svg_paths: Option<Vec<&str>>,
    img_sources: Vec<ImageSource>,
) -> Box<[AnyScene<T>]> {
    let mut scenes = Vec::new();

    // Create SVG scenes for each provided path.
    if let Some(paths) = svg_paths {
        for path in paths {
            scenes.push(AnyScene::new(
                svg::SvgScene::with_svg_file(path.into()).unwrap(),
            ));
        }
    } else {
        scenes.push(AnyScene::new(svg::SvgScene::tiger()));
    }

    scenes.push(AnyScene::new(text::TextScene::new("Hello, Vello!")));
    scenes.push(AnyScene::new(emoji_grid::EmojiGridScene::new()));
    scenes.push(AnyScene::new(random_text::RandomTextScene::new()));
    scenes.push(AnyScene::new(simple::SimpleScene::new()));
    scenes.push(AnyScene::new(
        blurred_rounded_rect::BlurredRoundedRectScene::new(),
    ));
    scenes.push(AnyScene::new(clip::ClipScene::new()));
    scenes.push(AnyScene::new(filter::FilterScene::new()));
    scenes.push(AnyScene::new(blend::BlendScene::new()));
    let flower_source = img_sources[0].clone();
    scenes.push(AnyScene::new(image::ImageScene::new(img_sources)));
    scenes.push(AnyScene::new(multi_image::MultiImageScene::new(
        flower_source,
    )));
    scenes.push(AnyScene::new(filter_elements::FilterElementsScene::new()));
    scenes.push(AnyScene::new(
        filter_blur_circle::FilterBlurCircleScene::new(),
    ));
    scenes.push(AnyScene::new(gradient::GradientExtendScene::new()));
    scenes.push(AnyScene::new(gradient::RadialScene::new()));
    scenes.push(AnyScene::new(path::FillTypesScene::new()));
    scenes.push(AnyScene::new(path::StrokeStylesScene::new()));
    scenes.push(AnyScene::new(path::StrokeStylesScene::new_non_uniform()));
    scenes.push(AnyScene::new(path::StrokeStylesScene::new_skew()));
    scenes.push(AnyScene::new(path::TrickyStrokesScene::new()));
    scenes.push(AnyScene::new(path::FunkyPathsScene::new()));
    scenes.push(AnyScene::new(path::RobustPathsScene::new()));

    if capabilities.external_textures {
        scenes.push(AnyScene::new(spritesheet::SpritesheetScene::new()));
    }

    scenes.into_boxed_slice()
}

/// Get all available example scenes (WASM version).
#[cfg(target_arch = "wasm32")]
pub fn get_example_scenes<T: RenderingContext + 'static>(
    capabilities: Capabilities,
    img_sources: Vec<ImageSource>,
) -> Box<[AnyScene<T>]> {
    let mut scenes = vec![
        AnyScene::new(svg::SvgScene::tiger()),
        AnyScene::new(text::TextScene::new("Hello, Vello!")),
        AnyScene::new(emoji_grid::EmojiGridScene::new()),
        AnyScene::new(random_text::RandomTextScene::new()),
        AnyScene::new(simple::SimpleScene::new()),
        AnyScene::new(blurred_rounded_rect::BlurredRoundedRectScene::new()),
        AnyScene::new(filter::FilterScene::new()),
        AnyScene::new(clip::ClipScene::new()),
        AnyScene::new(blend::BlendScene::new()),
        AnyScene::new(image::ImageScene::new(img_sources.clone())),
        AnyScene::new(multi_image::MultiImageScene::new(img_sources[0].clone())),
        AnyScene::new(filter_elements::FilterElementsScene::new()),
        AnyScene::new(filter_blur_circle::FilterBlurCircleScene::new()),
        AnyScene::new(gradient::GradientExtendScene::new()),
        AnyScene::new(gradient::RadialScene::new()),
        AnyScene::new(path::FillTypesScene::new()),
        AnyScene::new(path::StrokeStylesScene::new()),
        AnyScene::new(path::StrokeStylesScene::new_non_uniform()),
        AnyScene::new(path::StrokeStylesScene::new_skew()),
        AnyScene::new(path::TrickyStrokesScene::new()),
        AnyScene::new(path::FunkyPathsScene::new()),
        AnyScene::new(path::RobustPathsScene::new()),
    ];

    if capabilities.external_textures {
        scenes.push(AnyScene::new(spritesheet::SpritesheetScene::new()));
    }

    scenes.into_boxed_slice()
}
