#pragma once

#include "shader.h"

#include <functional>

namespace pp::panopainter {

struct LegacyCanvasDrawMergeCheckerboardUniforms {
    glm::mat4 mvp { 1.0f };
    bool colorize = false;
};

struct LegacyCanvasDrawMergeTextureUniforms {
    glm::mat4 mvp { 1.0f };
    int texture_slot = 0;
};

struct LegacyCanvasDrawMergeAtlasUniforms {
    glm::mat4 mvp { 1.0f };
    int texture_slot = 0;
    glm::vec2 offset {};
    glm::vec2 size { 1.0f };
};

struct LegacyCanvasDrawMergeEquirectUniforms {
    glm::mat4 mvp { 1.0f };
    int texture_slot = 0;
};

struct LegacyCanvasDrawMergeTextureMaskUniforms {
    int texture_slot = 0;
    glm::vec2 pattern_offset {};
};

struct LegacyCanvasDrawMergeTextureAlphaUniforms {
    glm::mat4 mvp { 1.0f };
    int texture_slot = 0;
    float alpha = 1.0f;
    int highlight = 0;
};

struct LegacyCanvasDrawMergeTextureBlendUniforms {
    glm::mat4 mvp { 1.0f };
    int texture_slot = 0;
    int destination_texture_slot = 2;
    bool use_destination_texture = false;
    int blend_mode = 0;
    float alpha = 1.0f;
};

struct LegacyCanvasDrawMergeTextureColorizeUniforms {
    glm::mat4 mvp { 1.0f };
    int texture_slot = 0;
    glm::vec4 color { 1.0f };
};

struct LegacyCanvasDrawMergeShaderExecution {
    std::function<void(kShader)> use_shader;
    std::function<void(kShaderUniform, int)> set_int;
    std::function<void(kShaderUniform, float)> set_float;
    std::function<void(kShaderUniform, const glm::vec2&)> set_vec2;
    std::function<void(kShaderUniform, const glm::vec4&)> set_vec4;
    std::function<void(kShaderUniform, const glm::mat4&)> set_mat4;
};

struct LegacyCanvasDrawMergeLayerBlendUniforms {
    LegacyCanvasDrawMergeTextureBlendUniforms shader;
    bool copy_destination = false;
};

struct LegacyCanvasDrawMergeLayerBlendExecution {
    std::function<void()> unbind_merge_framebuffer;
    std::function<void()> bind_samplers;
    std::function<void()> bind_merge_texture;
    std::function<void()> bind_destination_texture;
    std::function<void()> copy_destination_framebuffer;
    std::function<void()> draw;
    std::function<void()> unbind_destination_texture;
    std::function<void()> unbind_merge_texture;
};

struct LegacyCanvasDrawMergeLayerTextureExecution {
    std::function<void()> bind_sampler;
    std::function<void()> bind_layer_texture;
    std::function<void()> draw;
    std::function<void()> unbind_layer_texture;
};

struct LegacyCanvasDrawMergeLayerCompositeExecution {
    std::function<void()> execute_temporary_erase;
    std::function<void()> execute_temporary_paint;
    std::function<void()> execute_layer_texture;
    std::function<void()> execute_layer_blend;
};

struct LegacyCanvasDrawMergeTemporaryCompositeExecution {
    std::function<void()> setup;
    std::function<void()> bind_samplers;
    std::function<void()> bind_textures;
    std::function<void()> draw;
    std::function<void()> unbind_textures;
};

template <
    typename Setup,
    typename BindSamplers,
    typename BindTextures,
    typename Draw,
    typename UnbindTextures>
[[nodiscard]] inline LegacyCanvasDrawMergeTemporaryCompositeExecution make_legacy_canvas_draw_merge_temporary_erase_composite(
    Setup&& setup,
    BindSamplers&& bind_samplers,
    BindTextures&& bind_textures,
    Draw&& draw,
    UnbindTextures&& unbind_textures)
{
    return LegacyCanvasDrawMergeTemporaryCompositeExecution {
        .setup = std::forward<Setup>(setup),
        .bind_samplers = std::forward<BindSamplers>(bind_samplers),
        .bind_textures = std::forward<BindTextures>(bind_textures),
        .draw = std::forward<Draw>(draw),
        .unbind_textures = std::forward<UnbindTextures>(unbind_textures),
    };
}

template <
    typename Setup,
    typename BindSamplers,
    typename BindTextures,
    typename Draw,
    typename UnbindTextures>
[[nodiscard]] inline LegacyCanvasDrawMergeTemporaryCompositeExecution make_legacy_canvas_draw_merge_temporary_paint_composite(
    Setup&& setup,
    BindSamplers&& bind_samplers,
    BindTextures&& bind_textures,
    Draw&& draw,
    UnbindTextures&& unbind_textures)
{
    return LegacyCanvasDrawMergeTemporaryCompositeExecution {
        .setup = std::forward<Setup>(setup),
        .bind_samplers = std::forward<BindSamplers>(bind_samplers),
        .bind_textures = std::forward<BindTextures>(bind_textures),
        .draw = std::forward<Draw>(draw),
        .unbind_textures = std::forward<UnbindTextures>(unbind_textures),
    };
}

struct LegacyCanvasDrawMergePlaneSetupUniforms {
    LegacyCanvasDrawMergeCheckerboardUniforms checkerboard;
    bool use_blend = false;
    bool draw_checkerboard = false;
};

struct LegacyCanvasDrawMergePlaneSetupExecution {
    std::function<void()> clear_plane;
    std::function<void()> disable_blend;
    std::function<void()> enable_blend;
    std::function<void()> draw;
};

struct LegacyCanvasDrawMergeFinalPlaneCompositeUniforms {
    LegacyCanvasDrawMergeCheckerboardUniforms checkerboard;
    LegacyCanvasDrawMergeTextureUniforms texture;
    bool draw_checkerboard = false;
};

struct LegacyCanvasDrawMergeFinalPlaneCompositeExecution {
    std::function<void()> bind_merged_texture_copy_target;
    std::function<void()> copy_merged_framebuffer;
    std::function<void()> enable_blend;
    std::function<void()> draw;
    std::function<void()> bind_sampler;
    std::function<void()> bind_merged_texture;
    std::function<void()> unbind_merged_texture;
};

[[nodiscard]] inline LegacyCanvasDrawMergeShaderExecution legacy_shader_manager_draw_merge_execution() noexcept
{
    return {
        .use_shader = [](kShader shader) { ShaderManager::use(shader); },
        .set_int = [](kShaderUniform uniform, int value) { ShaderManager::u_int(uniform, value); },
        .set_float = [](kShaderUniform uniform, float value) { ShaderManager::u_float(uniform, value); },
        .set_vec2 = [](kShaderUniform uniform, const glm::vec2& value) { ShaderManager::u_vec2(uniform, value); },
        .set_vec4 = [](kShaderUniform uniform, const glm::vec4& value) { ShaderManager::u_vec4(uniform, value); },
        .set_mat4 = [](kShaderUniform uniform, const glm::mat4& value) { ShaderManager::u_mat4(uniform, value); },
    };
}

inline void apply_legacy_canvas_draw_merge_mvp(
    const glm::mat4& mvp,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.set_mat4(kShaderUniform::MVP, mvp);
}

inline void apply_legacy_canvas_draw_merge_mvp(const glm::mat4& mvp)
{
    apply_legacy_canvas_draw_merge_mvp(mvp, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_checkerboard_shader(
    const LegacyCanvasDrawMergeCheckerboardUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::Checkerboard);
    execution.set_int(kShaderUniform::Colorize, uniforms.colorize);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
}

inline void setup_legacy_canvas_draw_merge_checkerboard_shader(
    const LegacyCanvasDrawMergeCheckerboardUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_checkerboard_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_texture_shader(
    const LegacyCanvasDrawMergeTextureUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::Texture);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
}

inline void setup_legacy_canvas_draw_merge_texture_shader(
    const LegacyCanvasDrawMergeTextureUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_texture_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_atlas_shader(
    const LegacyCanvasDrawMergeAtlasUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::Atlas);
    execution.set_vec2(kShaderUniform::Tof, uniforms.offset);
    execution.set_vec2(kShaderUniform::Tsz, uniforms.size);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
}

inline void setup_legacy_canvas_draw_merge_atlas_shader(
    const LegacyCanvasDrawMergeAtlasUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_atlas_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_equirect_shader(
    const LegacyCanvasDrawMergeEquirectUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::Equirect);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
}

inline void setup_legacy_canvas_draw_merge_equirect_shader(
    const LegacyCanvasDrawMergeEquirectUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_equirect_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_texture_mask_shader(
    const LegacyCanvasDrawMergeTextureMaskUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::TextureMask);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
    execution.set_vec2(kShaderUniform::PatternOffset, uniforms.pattern_offset);
}

inline void setup_legacy_canvas_draw_merge_texture_mask_shader(
    const LegacyCanvasDrawMergeTextureMaskUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_texture_mask_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_texture_alpha_shader(
    const LegacyCanvasDrawMergeTextureAlphaUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::TextureAlpha);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
    execution.set_float(kShaderUniform::Alpha, uniforms.alpha);
    execution.set_int(kShaderUniform::Highlight, uniforms.highlight);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
}

inline void setup_legacy_canvas_draw_merge_texture_alpha_shader(
    const LegacyCanvasDrawMergeTextureAlphaUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_texture_alpha_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_texture_blend_shader(
    const LegacyCanvasDrawMergeTextureBlendUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::TextureBlend);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
    if (uniforms.use_destination_texture) {
        execution.set_int(kShaderUniform::TexBG, uniforms.destination_texture_slot);
    }
    execution.set_int(kShaderUniform::BlendMode, uniforms.blend_mode);
    execution.set_float(kShaderUniform::Alpha, uniforms.alpha);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
}

inline void setup_legacy_canvas_draw_merge_texture_blend_shader(
    const LegacyCanvasDrawMergeTextureBlendUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_texture_blend_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void setup_legacy_canvas_draw_merge_texture_colorize_shader(
    const LegacyCanvasDrawMergeTextureColorizeUniforms& uniforms,
    const LegacyCanvasDrawMergeShaderExecution& execution) noexcept
{
    execution.use_shader(kShader::TextureColorize);
    execution.set_int(kShaderUniform::Tex, uniforms.texture_slot);
    execution.set_vec4(kShaderUniform::Col, uniforms.color);
    execution.set_mat4(kShaderUniform::MVP, uniforms.mvp);
}

inline void setup_legacy_canvas_draw_merge_texture_colorize_shader(
    const LegacyCanvasDrawMergeTextureColorizeUniforms& uniforms)
{
    setup_legacy_canvas_draw_merge_texture_colorize_shader(uniforms, legacy_shader_manager_draw_merge_execution());
}

inline void execute_legacy_canvas_draw_merge_layer_blend(
    const LegacyCanvasDrawMergeLayerBlendUniforms& uniforms,
    const LegacyCanvasDrawMergeLayerBlendExecution& execution)
{
    execution.unbind_merge_framebuffer();
    execution.bind_samplers();
    setup_legacy_canvas_draw_merge_texture_blend_shader(uniforms.shader);
    execution.bind_merge_texture();

    if (uniforms.copy_destination) {
        execution.bind_destination_texture();
        execution.copy_destination_framebuffer();
    }

    execution.draw();

    if (uniforms.copy_destination) {
        execution.unbind_destination_texture();
    }

    execution.unbind_merge_texture();
}

inline void execute_legacy_canvas_draw_merge_layer_texture(
    const LegacyCanvasDrawMergeTextureAlphaUniforms& uniforms,
    const LegacyCanvasDrawMergeLayerTextureExecution& execution)
{
    execution.bind_sampler();
    setup_legacy_canvas_draw_merge_texture_alpha_shader(uniforms);
    execution.bind_layer_texture();
    execution.draw();
    execution.unbind_layer_texture();
}

inline void execute_legacy_canvas_draw_merge_layer_composite(
    bool is_temporary_erase,
    bool is_temporary_paint,
    bool use_blend,
    const LegacyCanvasDrawMergeLayerCompositeExecution& execution)
{
    if (is_temporary_erase) {
        execution.execute_temporary_erase();
    } else if (is_temporary_paint) {
        execution.execute_temporary_paint();
    } else {
        execution.execute_layer_texture();
    }

    if (use_blend) {
        execution.execute_layer_blend();
    }
}

inline void execute_legacy_canvas_draw_merge_temporary_composite(
    const LegacyCanvasDrawMergeTemporaryCompositeExecution& execution)
{
    execution.setup();
    execution.bind_samplers();
    execution.bind_textures();
    execution.draw();
    execution.unbind_textures();
}

inline void execute_legacy_canvas_draw_merge_plane_setup(
    const LegacyCanvasDrawMergePlaneSetupUniforms& uniforms,
    const LegacyCanvasDrawMergePlaneSetupExecution& execution)
{
    execution.clear_plane();

    if (uniforms.use_blend) {
        execution.disable_blend();
        execution.clear_plane();
        return;
    }

    if (uniforms.draw_checkerboard) {
        setup_legacy_canvas_draw_merge_checkerboard_shader(uniforms.checkerboard);
        execution.draw();
    }

    execution.enable_blend();
}

inline void execute_legacy_canvas_draw_merge_final_plane_composite(
    const LegacyCanvasDrawMergeFinalPlaneCompositeUniforms& uniforms,
    const LegacyCanvasDrawMergeFinalPlaneCompositeExecution& execution)
{
    execution.bind_merged_texture_copy_target();
    execution.copy_merged_framebuffer();

    execution.enable_blend();

    if (uniforms.draw_checkerboard) {
        setup_legacy_canvas_draw_merge_checkerboard_shader(uniforms.checkerboard);
        execution.draw();
    }

    execution.bind_sampler();
    execution.bind_merged_texture();
    setup_legacy_canvas_draw_merge_texture_shader(uniforms.texture);
    execution.draw();
    execution.unbind_merged_texture();
}

} // namespace pp::panopainter
