#include "pch.h"

#include "legacy_node_stroke_preview_draw_services.h"
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_canvas_stroke_execution_services.h"
#include "legacy_ui_gl_dispatch.h"
#include "renderer_gl/opengl_capabilities.h"
#include "util.h"

#include <array>

namespace pp::panopainter {

namespace {

namespace stroke_preview_composite_slots {
constexpr std::uint32_t kBackground = 0U;
constexpr std::uint32_t kStroke = 1U;
constexpr std::uint32_t kDual = 3U;
constexpr std::uint32_t kPattern = 4U;
}

pp::panopainter::LegacyStrokeCompositeUniforms make_stroke_preview_mix_composite_uniforms(
    const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan& shader_plan) noexcept
{
    return {
        .resolution = shader_plan.resolution,
        .pattern = {
            .scale = shader_plan.pattern_scale,
            .invert = shader_plan.pattern_invert,
            .brightness = shader_plan.pattern_brightness,
            .contrast = shader_plan.pattern_contrast,
            .depth = shader_plan.pattern_depth,
            .blend_mode = shader_plan.pattern_blend_mode,
            .offset = shader_plan.pattern_offset,
        },
        .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
        .layer_alpha = 1.0f,
        .alpha_lock = false,
        .mask_enabled = false,
        .use_fragcoord = false,
        .blend_mode = shader_plan.blend_mode,
        .use_dual = shader_plan.use_dual,
        .dual_blend_mode = shader_plan.dual_blend_mode,
        .dual_alpha = shader_plan.dual_alpha,
        .use_pattern = shader_plan.use_pattern,
    };
}

pp::panopainter::LegacyCanvasStrokeMixPassRequest make_stroke_preview_mix_pass_execution_request(
    const pp::panopainter::LegacyNodeStrokePreviewMixPassPlan::ShaderPlan& shader,
    RTT& mixer_rtt,
    const Brush& brush,
    Sampler& linear_sampler,
    Texture2D& background_texture,
    Texture2D& stroke_texture,
    Texture2D& dual_texture,
    std::span<const pp::panopainter::LegacyCanvasStrokeMixPassPlane> mix_planes,
    std::function<void()> draw_mix)
{
    return pp::panopainter::make_legacy_canvas_stroke_mix_pass_request(
        "NodeStrokePreview::stroke_draw_mix",
        glm::vec2(static_cast<float>(mixer_rtt.getWidth()), static_cast<float>(mixer_rtt.getHeight())),
        mix_planes,
        [&] {
            linear_sampler.bind(stroke_preview_composite_slots::kBackground);
            linear_sampler.bind(stroke_preview_composite_slots::kStroke);
            linear_sampler.bind(stroke_preview_composite_slots::kDual);
            linear_sampler.bind(stroke_preview_composite_slots::kPattern);
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kBackground, "NodeStrokePreview");
            background_texture.bind();
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kStroke, "NodeStrokePreview");
            stroke_texture.bind();
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kDual, "NodeStrokePreview");
            dual_texture.bind();
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kPattern, "NodeStrokePreview");
            brush.m_pattern_texture ? brush.m_pattern_texture->bind() : pp::legacy::ui_gl::unbind_texture_2d("NodeStrokePreview");
        },
        [] {},
        [&](int, const glm::mat4& plane_mvp) {
            auto uniforms = make_stroke_preview_mix_composite_uniforms(shader);
            uniforms.mvp = plane_mvp;
            pp::panopainter::setup_legacy_stroke_composite_shader(uniforms);
        },
        [&](int) {
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kBackground, "NodeStrokePreview");
            background_texture.bind();
        },
        [&](int) {
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kStroke, "NodeStrokePreview");
            stroke_texture.bind();
        },
        [&](int) {
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kDual, "NodeStrokePreview");
            dual_texture.bind();
        },
        std::move(draw_mix),
        [&](int) {
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kDual, "NodeStrokePreview");
            dual_texture.unbind();
        },
        [&](int) {
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kStroke, "NodeStrokePreview");
            stroke_texture.unbind();
        },
        [&](int) {
            pp::legacy::ui_gl::activate_texture_unit(stroke_preview_composite_slots::kBackground, "NodeStrokePreview");
            background_texture.unbind();
        });
}

} // namespace

bool execute_legacy_node_stroke_preview_mix_pass(
    const LegacyNodeStrokePreviewMixPassExecutionRequest& request)
{
    if (!request.draw_mix) {
        return false;
    }

    const auto mix_pass = pp::panopainter::plan_legacy_node_stroke_preview_mix_pass(
        pp::panopainter::LegacyNodeStrokePreviewMixPassRequest {
            .resolution = request.preview_size,
            .pattern_scale = request.brush.m_pattern_scale,
            .pattern_flipx = request.brush.m_pattern_flipx,
            .pattern_flipy = request.brush.m_pattern_flipy,
            .pattern_invert = request.brush.m_pattern_invert,
            .pattern_brightness = request.brush.m_pattern_brightness,
            .pattern_contrast = request.brush.m_pattern_contrast,
            .pattern_depth = request.brush.m_pattern_depth,
            .pattern_rand_offset = request.brush.m_pattern_rand_offset,
            .pattern_enabled = request.brush.m_pattern_enabled,
            .pattern_eachsample = request.brush.m_pattern_eachsample,
            .tip_wet = request.brush.m_tip_wet,
            .tip_mix = request.brush.m_tip_mix,
            .tip_noise = request.brush.m_tip_noise,
            .dual_enabled = request.brush.m_dual_enabled,
            .dual_blend_mode = request.brush.m_dual_blend_mode,
            .pattern_blend_mode = request.brush.m_pattern_blend_mode,
            .dual_opacity = request.brush.m_dual_opacity,
            .blend_mode = request.brush.m_blend_mode,
        });
    const auto mix_planes = std::array {
        pp::panopainter::LegacyCanvasStrokeMixPassPlane {
            .index = 0,
            .visible = true,
            .has_target = true,
            .opacity = 1.0f,
            .mvp = glm::ortho(-.5f, .5f, -.5f, .5f, -1.f, 1.f),
        },
    };
    gl_state gl;
    const auto mix_result = pp::panopainter::execute_legacy_canvas_stroke_mix_pass_with_setup(
        [&] {
            pp::legacy::ui_gl::apply_viewport(
                0,
                0,
                request.mixer_rtt.getWidth(),
                request.mixer_rtt.getHeight(),
                "NodeStrokePreview");
            pp::legacy::ui_gl::set_capability(pp::renderer::gl::depth_test_state(), false, "NodeStrokePreview");
            pp::legacy::ui_gl::set_capability(pp::renderer::gl::scissor_test_state(), true, "NodeStrokePreview");
            pp::legacy::ui_gl::set_capability(pp::renderer::gl::blend_state(), false, "NodeStrokePreview");
            pp::legacy::ui_gl::apply_scissor_rect(
                static_cast<std::int32_t>(request.bb_min.x),
                static_cast<std::int32_t>(request.bb_min.y),
                static_cast<std::int32_t>(request.bb_sz.x),
                static_cast<std::int32_t>(request.bb_sz.y),
                "NodeStrokePreview");
            gl.save();
            request.mixer_rtt.bindFramebuffer();
        },
        [&] {
            request.mixer_rtt.unbindFramebuffer();
            gl.restore();
        },
        make_stroke_preview_mix_pass_execution_request(
            mix_pass.shader,
            request.mixer_rtt,
            request.brush,
            request.linear_sampler,
            request.background_texture,
            request.stroke_texture,
            request.dual_texture,
            mix_planes,
            request.draw_mix));
    return mix_result.ok;
}

bool execute_legacy_node_stroke_preview_mix_pass(
    const Brush& brush,
    glm::vec2 preview_size,
    RTT& mixer_rtt,
    glm::vec2 bb_min,
    glm::vec2 bb_sz,
    Sampler& linear_sampler,
    Texture2D& background_texture,
    Texture2D& stroke_texture,
    Texture2D& dual_texture,
    std::function<void()> draw_mix)
{
    return execute_legacy_node_stroke_preview_mix_pass(
        LegacyNodeStrokePreviewMixPassExecutionRequest {
            .brush = brush,
            .preview_size = preview_size,
            .mixer_rtt = mixer_rtt,
            .bb_min = bb_min,
            .bb_sz = bb_sz,
            .linear_sampler = linear_sampler,
            .background_texture = background_texture,
            .stroke_texture = stroke_texture,
            .dual_texture = dual_texture,
            .draw_mix = std::move(draw_mix),
        });
}

bool has_valid_live_render_callbacks(const LegacyNodeStrokePreviewLiveRenderRequest& request)
{
    return request.bind_dual_pass_textures &&
        request.capture_background &&
        request.compute_frames &&
        request.draw_samples &&
        request.draw_mix &&
        request.unbind_mixer_texture &&
        request.bind_pattern_texture &&
        request.draw_composite;
}

bool execute_legacy_node_stroke_preview_live_render_passes(
    const LegacyNodeStrokePreviewLiveRenderRequest& request)
{
    if (!has_valid_live_render_callbacks(request)) {
        return false;
    }

    return execute_legacy_node_stroke_preview_draw_immediate_shell<LegacyNodeStrokePreviewFrame>(
        request.brush,
        request.pass_orchestration,
        request.stroke_texture,
        request.mixer_rtt,
        request.render_target,
        request.background_texture,
        request.dual_texture,
        request.preview_texture,
        request.linear_sampler,
        request.repeat_sampler,
        request.copy_stroke_destination,
        request.size,
        kLegacyNodeStrokePreviewStrokeTextureSlot,
        [&] {
            if (!request.pass_orchestration.material.dual_pass.enabled) {
                return;
            }

            setup_legacy_stroke_dual_shader(request.pass_orchestration.material.dual_pass.uses_pattern);
            request.bind_dual_pass_textures(*request.prepared_strokes.dual_brush);
            execute_legacy_stroke_preview_live_pass(
                [&] {
                    request.render_target.clear();
                },
                [&] {
                    return request.compute_frames(
                        const_cast<Stroke&>(request.prepared_strokes.dual_stroke),
                        request.zoom);
                },
                [](auto& frame) {
                    frame.col = { 0, 0, 0, 1 };
                },
                [&](auto& frame) {
                    use_legacy_stroke_shader();
                    apply_legacy_stroke_sample_uniforms(
                        LegacyStrokeSampleUniforms {
                            .color = frame.col,
                            .alpha = frame.flow,
                            .opacity = frame.opacity,
                        });
                },
                [&](auto& frame) {
                    request.draw_samples(frame.shapes, request.dual_texture, request.copy_stroke_destination);
                },
                [&] {
                    copy_legacy_node_stroke_preview_framebuffer_to_texture(
                        request.dual_texture,
                        request.size,
                        kLegacyNodeStrokePreviewStrokeTextureSlot);
                });
        },
        [&] {
            request.capture_background(request.pass_orchestration.background_colorize);
        },
        [&] {
            return request.compute_frames(
                const_cast<Stroke&>(request.prepared_strokes.stroke),
                request.zoom);
        },
        [&](auto& frame) {
            if (request.brush.m_tip_mix > 0.0f) {
                request.draw_mix(xy(frame.mixer_rect), zw(frame.mixer_rect));
            }

            frame.col = request.brush.m_blend_mode != 0 || request.brush.m_tip_mix > 0.0f ?
                glm::vec4 { .7f, .4f, .1f, 1.0f } :
                glm::vec4 { 0.0f, 0.0f, 0.0f, 1.0f };
            frame.flow = glm::max(frame.flow, request.min_flow);
        },
        [&](auto& frame) {
            use_legacy_stroke_shader();
            apply_legacy_stroke_sample_uniforms(
                LegacyStrokeSampleUniforms {
                    .color = frame.col,
                    .alpha = frame.flow,
                    .opacity = frame.opacity,
                });
        },
        [&](auto& frame) {
            request.draw_samples(frame.shapes, request.stroke_texture, request.copy_stroke_destination);
        },
        [&] {
            request.unbind_mixer_texture();
        },
        [&] {
            request.bind_pattern_texture();
        },
        [&] {
            request.draw_composite();
        });
}

} // namespace pp::panopainter
