/*
 * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <AK/MaybeOwned.h>
#include <LibGfx/ScalingMode.h>
#include <LibWeb/Painting/AffineDisplayListPlayerCPU.h>
#include <LibWeb/Painting/DisplayListRecorder.h>

namespace Web::Painting {

class DisplayListPlayerCPU : public DisplayListPlayer {
public:
    DisplayListPlayerCPU(Gfx::Bitmap& bitmap, bool enable_affine_command_executor = false);

    ~DisplayListPlayerCPU();

private:
    template<typename Callback>
    void apply_mask_painted_from(Gfx::IntRect const& rect, Callback callback, DisplayList& display_list)
    {
        // In short, this:
        // 1. paints the background via the provided callback
        // 2. establishes a new stacking context
        // 3. runs through all the operations in the display list
        // 4. exits the stacking context
        // 5. applies a mask to the background from the bitmap generated by the operations
        // 6. blits the resulting bitmap with the original painter

        auto background_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size()).release_value_but_fixme_should_propagate_errors();
        auto mask_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect.size()).release_value_but_fixme_should_propagate_errors();

        {
            Gfx::Painter background_painter { *background_bitmap };
            background_painter.translate(-rect.location());
            // Paint the background canvas.
            callback(background_painter);
        }

        stacking_contexts.append(StackingContext {
            .painter = AK::make<Gfx::Painter>(*mask_bitmap),
            .opacity = 1.0f,
            .destination = {},
            .scaling_mode = {},
        });

        // Offset the painter to paint in the correct location.
        painter().translate(-rect.location());

        execute(display_list);

        stacking_contexts.take_last();

        background_bitmap->apply_mask(*mask_bitmap, Gfx::Bitmap::MaskKind::Alpha);

        painter().blit(rect.location(), *background_bitmap, mask_bitmap->rect());
    }

    CommandResult draw_glyph_run(DrawGlyphRun const&) override;
    CommandResult fill_rect(FillRect const&) override;
    CommandResult draw_scaled_bitmap(DrawScaledBitmap const&) override;
    CommandResult draw_scaled_immutable_bitmap(DrawScaledImmutableBitmap const&) override;
    CommandResult set_clip_rect(SetClipRect const&) override;
    CommandResult clear_clip_rect(ClearClipRect const&) override;
    CommandResult push_stacking_context(PushStackingContext const&) override;
    CommandResult pop_stacking_context(PopStackingContext const&) override;
    CommandResult paint_linear_gradient(PaintLinearGradient const&) override;
    CommandResult paint_outer_box_shadow(PaintOuterBoxShadow const&) override;
    CommandResult paint_inner_box_shadow(PaintInnerBoxShadow const&) override;
    CommandResult paint_text_shadow(PaintTextShadow const&) override;
    CommandResult fill_rect_with_rounded_corners(FillRectWithRoundedCorners const&) override;
    CommandResult fill_path_using_color(FillPathUsingColor const&) override;
    CommandResult fill_path_using_paint_style(FillPathUsingPaintStyle const&) override;
    CommandResult stroke_path_using_color(StrokePathUsingColor const&) override;
    CommandResult stroke_path_using_paint_style(StrokePathUsingPaintStyle const&) override;
    CommandResult draw_ellipse(DrawEllipse const&) override;
    CommandResult fill_ellipse(FillEllipse const&) override;
    CommandResult draw_line(DrawLine const&) override;
    CommandResult apply_backdrop_filter(ApplyBackdropFilter const&) override;
    CommandResult draw_rect(DrawRect const&) override;
    CommandResult paint_radial_gradient(PaintRadialGradient const&) override;
    CommandResult paint_conic_gradient(PaintConicGradient const&) override;
    CommandResult draw_triangle_wave(DrawTriangleWave const&) override;
    CommandResult sample_under_corners(SampleUnderCorners const&) override;
    CommandResult blit_corner_clipping(BlitCornerClipping const&) override;

    bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;

    bool needs_prepare_glyphs_texture() const override { return false; }
    void prepare_glyph_texture(HashMap<Gfx::Font const*, HashTable<u32>> const&) override { }

    virtual void prepare_to_execute(size_t corner_clip_max_depth) override;

    bool needs_update_immutable_bitmap_texture_cache() const override { return false; }
    void update_immutable_bitmap_texture_cache(HashMap<u32, Gfx::ImmutableBitmap const*>&) override { }

    DisplayListPlayer& nested_player() override
    {
        return *m_affine_display_list_player;
    }

    Gfx::Bitmap& m_target_bitmap;
    bool m_enable_affine_command_executor { false };

    Vector<RefPtr<BorderRadiusCornerClipper>> m_corner_clippers_stack;

    struct StackingContext {
        MaybeOwned<Gfx::Painter> painter;
        float opacity;
        Gfx::IntRect destination;
        Gfx::ScalingMode scaling_mode;
        Optional<StackingContextMask> mask = {};
    };

    [[nodiscard]] Gfx::Painter const& painter() const { return *stacking_contexts.last().painter; }
    [[nodiscard]] Gfx::Painter& painter() { return *stacking_contexts.last().painter; }

    Vector<StackingContext> stacking_contexts;
    Optional<AffineDisplayListPlayerCPU> m_affine_display_list_player;
};

}
