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

use std::time::{Duration, Instant};

use criterion::Criterion;
use parley::{
    Alignment, AlignmentOptions, FontContext, FontFamily, GlyphRun, Layout, LayoutContext,
    PositionedLayoutItem,
};
use vello_common::pixmap::Pixmap;
use vello_cpu::{Glyph, RenderContext, RenderSettings, Resources};

pub fn glyph(c: &mut Criterion) {
    let mut g = c.benchmark_group("glyph");

    const WIDTH: u16 = 256;
    const HEIGHT: u16 = 256;
    const TEXT: &str = "The quick brown fox jumps over the lazy dog 0123456789";

    let layout_for = |text: &str, scale: f32| {
        let mut layout_cx = LayoutContext::new();
        let mut font_cx = FontContext::new();
        let mut builder = layout_cx.ranged_builder(&mut font_cx, text, scale, true);
        builder.push_default(FontFamily::named("Roboto"));
        let mut layout: Layout<Brush> = builder.build(text);
        let max_advance = Some(WIDTH as f32);
        layout.break_all_lines(max_advance);
        layout.align(Alignment::Start, AlignmentOptions::default());
        layout
    };

    let settings = RenderSettings::default();
    let layout = layout_for(TEXT, 1.0);

    for (hint_name, hint) in [("hinted", true), ("unhinted", false)] {
        g.bench_function(format!("cached_{hint_name}"), |b| {
            let mut renderer = GlyphBenchRenderer::new(WIDTH, HEIGHT, settings);
            render_layout(&mut renderer, &layout, hint, true);

            b.iter_custom(|iters| {
                let mut total_time = Duration::from_nanos(0);
                for _ in 0..iters {
                    // Don't include `reset` time in the benchmark.
                    renderer.reset();

                    let start = Instant::now();
                    render_layout(&mut renderer, &layout, hint, true);
                    total_time += start.elapsed();
                }
                total_time
            });
        });

        // Note that even for `uncached`, the outline and hint cache will still be used. This benchmark
        // is only for testing the difference between having atlas caching enabled and disabled.

        g.bench_function(format!("uncached_{hint_name}"), |b| {
            let mut renderer = GlyphBenchRenderer::new(WIDTH, HEIGHT, settings);
            render_layout(&mut renderer, &layout, hint, false);

            b.iter_custom(|iters| {
                let mut total_time = Duration::from_nanos(0);
                for _ in 0..iters {
                    renderer.reset();

                    let start = Instant::now();
                    render_layout(&mut renderer, &layout, hint, false);
                    total_time += start.elapsed();
                }
                total_time
            });
        });
    }
}

#[derive(Clone, Copy, Default, Debug, PartialEq)]
struct Brush;

struct GlyphBenchRenderer {
    ctx: RenderContext,
    resources: Resources,
    pixmap: Pixmap,
}

impl GlyphBenchRenderer {
    fn new(width: u16, height: u16, settings: RenderSettings) -> Self {
        Self {
            ctx: RenderContext::new_with(width, height, settings),
            resources: Resources::new(),
            pixmap: Pixmap::new(width, height),
        }
    }

    fn reset(&mut self) {
        self.ctx.reset();
    }
}

fn render_layout(
    renderer: &mut GlyphBenchRenderer,
    layout: &Layout<Brush>,
    hint: bool,
    atlas_cache: bool,
) {
    for line in layout.lines() {
        for item in line.items() {
            if let PositionedLayoutItem::GlyphRun(glyph_run) = item {
                render_glyph_run(renderer, &glyph_run, hint, atlas_cache);
            }
        }
    }

    renderer
        .ctx
        .render(&mut renderer.pixmap, &mut renderer.resources);
}

fn render_glyph_run(
    renderer: &mut GlyphBenchRenderer,
    glyph_run: &GlyphRun<'_, Brush>,
    hint: bool,
    atlas_cache: bool,
) {
    let mut run_x = glyph_run.offset();
    let run_y = glyph_run.baseline();
    let glyphs = glyph_run.glyphs().map(move |glyph| {
        let glyph_x = run_x + glyph.x;
        let glyph_y = run_y - glyph.y;
        run_x += glyph.advance;

        Glyph {
            id: glyph.id,
            x: glyph_x,
            y: glyph_y,
        }
    });

    let run = glyph_run.run();
    renderer
        .ctx
        .glyph_run(&mut renderer.resources, run.font())
        .font_size(run.font_size())
        .hint(hint)
        .atlas_cache(atlas_cache)
        .fill_glyphs(glyphs);
}
