#include "rive/tess/sokol/sokol_tess_renderer.hpp"
#include "rive/tess/sokol/sokol_factory.hpp"
#include "rive/tess/tess_render_path.hpp"
#include "rive/tess/contour_stroke.hpp"
#include "generated/shader.h"
#include <unordered_set>

using namespace rive;

static void fillColorBuffer(float* buffer, ColorInt value)
{
    buffer[0] = (float)colorRed(value) / 0xFF;
    buffer[1] = (float)colorGreen(value) / 0xFF;
    buffer[2] = (float)colorBlue(value) / 0xFF;
    buffer[3] = colorOpacity(value);
}

class SokolRenderPath
    : public LITE_RTTI_OVERRIDE(TessRenderPath, SokolRenderPath)
{
public:
    SokolRenderPath() {}
    SokolRenderPath(RawPath& rawPath, FillRule fillRule) :
        lite_rtti_override(rawPath, fillRule)
    {}

    ~SokolRenderPath()
    {
        sg_destroy_buffer(m_vertexBuffer);
        sg_destroy_buffer(m_indexBuffer);
    }

private:
    std::vector<Vec2D> m_vertices;
    std::vector<uint16_t> m_indices;

    sg_buffer m_vertexBuffer = {0};
    sg_buffer m_indexBuffer = {0};

    std::size_t m_boundsIndex = 0;

protected:
    void addTriangles(rive::Span<const rive::Vec2D> vts,
                      rive::Span<const uint16_t> idx) override
    {
        m_vertices.insert(m_vertices.end(), vts.begin(), vts.end());
        m_indices.insert(m_indices.end(), idx.begin(), idx.end());
    }

    void setTriangulatedBounds(const AABB& value) override
    {
        m_boundsIndex = m_vertices.size();
        m_vertices.emplace_back(Vec2D(value.minX, value.minY));
        m_vertices.emplace_back(Vec2D(value.maxX, value.minY));
        m_vertices.emplace_back(Vec2D(value.maxX, value.maxY));
        m_vertices.emplace_back(Vec2D(value.minX, value.maxY));
    }

public:
    void rewind() override
    {
        TessRenderPath::rewind();
        m_vertices.clear();
        m_indices.clear();
    }

    void drawStroke(ContourStroke* stroke)
    {
        if (isContainer())
        {
            for (auto& subPath : m_subPaths)
            {
                LITE_RTTI_CAST_OR_CONTINUE(sokolPath,
                                           SokolRenderPath*,
                                           subPath.path());
                sokolPath->drawStroke(stroke);
            }
            return;
        }
        std::size_t start, end;
        stroke->nextRenderOffset(start, end);
        sg_draw(start < 2 ? 0 : (start - 2) * 3,
                end - start < 2 ? 0 : (end - start - 2) * 3,
                1);
    }

    void drawFill()
    {
        if (triangulate())
        {
            sg_destroy_buffer(m_vertexBuffer);
            sg_destroy_buffer(m_indexBuffer);
            if (m_indices.size() == 0 || m_vertices.size() == 0)
            {
                m_vertexBuffer = {0};
                m_indexBuffer = {0};
                return;
            }

            m_vertexBuffer = sg_make_buffer((sg_buffer_desc){
                .type = SG_BUFFERTYPE_VERTEXBUFFER,
                .data =
                    {
                        m_vertices.data(),
                        m_vertices.size() * sizeof(Vec2D),
                    },
            });

            m_indexBuffer = sg_make_buffer((sg_buffer_desc){
                .type = SG_BUFFERTYPE_INDEXBUFFER,
                .data =
                    {
                        m_indices.data(),
                        m_indices.size() * sizeof(uint16_t),
                    },
            });
        }

        if (m_vertexBuffer.id == 0)
        {
            return;
        }

        sg_bindings bind = {
            .vertex_buffers[0] = m_vertexBuffer,
            .index_buffer = m_indexBuffer,
        };

        sg_apply_bindings(&bind);
        sg_draw(0, m_indices.size(), 1);
    }

    void drawBounds(const sg_buffer& boundsIndexBuffer)
    {
        if (m_vertexBuffer.id == 0)
        {
            return;
        }
        sg_bindings bind = {
            .vertex_buffers[0] = m_vertexBuffer,
            .vertex_buffer_offsets[0] = (int)(m_boundsIndex * sizeof(Vec2D)),
            .index_buffer = boundsIndexBuffer,
        };

        sg_apply_bindings(&bind);
        sg_draw(0, 6, 1);
    }
};

// Returns a full-formed RenderPath -- can be treated as immutable
rcp<RenderPath> SokolFactory::makeRenderPath(RawPath& rawPath, FillRule rule)
{
    return make_rcp<SokolRenderPath>(rawPath, rule);
}

rcp<RenderPath> SokolFactory::makeEmptyRenderPath()
{
    return make_rcp<SokolRenderPath>();
}

class SokolBuffer : public LITE_RTTI_OVERRIDE(RenderBuffer, SokolBuffer)
{
public:
    SokolBuffer(RenderBufferType type,
                RenderBufferFlags renderBufferFlags,
                size_t sizeInBytes) :
        lite_rtti_override(type, renderBufferFlags, sizeInBytes),
        m_mappedMemory(new char[sizeInBytes])
    {
        // If the buffer will be immutable, defer creation until the client
        // unmaps for the only time and we have our initial data.
        if (!(flags() & RenderBufferFlags::mappedOnceAtInitialization))
        {
            m_buffer = sg_make_buffer(makeNoDataBufferDesc());
        }
    }
    ~SokolBuffer() { sg_destroy_buffer(m_buffer); }

    sg_buffer buffer()
    {
        // In the case of RenderBufferFlags::mappedOnceAtInitialization, the
        // client is expected to map()/unmap() before we need to access this
        // buffer for rendering.
        assert(m_buffer.id);
        return m_buffer;
    }

protected:
    void* onMap() override
    {
        // An immutable buffer is only mapped once, and then we delete
        // m_mappedMemory.
        assert(m_mappedMemory);
        return m_mappedMemory.get();
    }

    void onUnmap() override
    {
        if (flags() & RenderBufferFlags::mappedOnceAtInitialization)
        {
            // We are an immutable buffer. Creation was deferred until now.
            assert(!m_buffer.id);
            assert(m_mappedMemory);
            sg_buffer_desc bufferDesc = makeNoDataBufferDesc();
            bufferDesc.data = {m_mappedMemory.get(), sizeInBytes()};
            m_buffer = sg_make_buffer(bufferDesc);
            m_mappedMemory.reset(); // This buffer will never be mapped again.
        }
        else
        {
            assert(m_buffer.id);
            sg_update_buffer(
                m_buffer,
                sg_range{.ptr = m_mappedMemory.get(), .size = sizeInBytes()});
        }
    }

private:
    sg_buffer_desc makeNoDataBufferDesc() const
    {
        return {
            .size = sizeInBytes(),
            .usage = (flags() & RenderBufferFlags::mappedOnceAtInitialization)
                         ? SG_USAGE_IMMUTABLE
                         : SG_USAGE_STREAM,
            .type = (type() == RenderBufferType::index)
                        ? SG_BUFFERTYPE_INDEXBUFFER
                        : SG_BUFFERTYPE_VERTEXBUFFER,
        };
    };

    sg_buffer m_buffer{};
    std::unique_ptr<char[]> m_mappedMemory;
};

rcp<RenderBuffer> SokolFactory::makeRenderBuffer(RenderBufferType type,
                                                 RenderBufferFlags flags,
                                                 size_t sizeInBytes)
{
    return make_rcp<SokolBuffer>(type, flags, sizeInBytes);
}

sg_pipeline vectorPipeline(sg_shader shader,
                           sg_blend_state blend,
                           sg_stencil_state stencil,
                           sg_color_mask colorMask = SG_COLORMASK_RGBA)
{
    return sg_make_pipeline((sg_pipeline_desc){
        .layout =
            {
                .attrs =
                    {
                        [ATTR_vs_path_position] =
                            {
                                .format = SG_VERTEXFORMAT_FLOAT2,
                                .buffer_index = 0,
                            },
                    },
            },
        .shader = shader,
        .index_type = SG_INDEXTYPE_UINT16,
        .cull_mode = SG_CULLMODE_NONE,
        .depth =
            {
                .compare = SG_COMPAREFUNC_ALWAYS,
                .write_enabled = false,
            },
        .colors =
            {
                [0] =
                    {
                        .write_mask = colorMask,
                        .blend = blend,
                    },
            },
        .stencil = stencil,
        .label = "path-pipeline",
    });
}

SokolTessRenderer::SokolTessRenderer()
{
    m_meshPipeline = sg_make_pipeline((sg_pipeline_desc){
        .layout =
            {
                .attrs =
                    {
                        [ATTR_vs_position] = {.format = SG_VERTEXFORMAT_FLOAT2,
                                              .buffer_index = 0},
                        [ATTR_vs_texcoord0] = {.format = SG_VERTEXFORMAT_FLOAT2,
                                               .buffer_index = 1},
                    },
            },
        .shader = sg_make_shader(rive_tess_shader_desc(sg_query_backend())),
        .index_type = SG_INDEXTYPE_UINT16,
        .cull_mode = SG_CULLMODE_NONE,
        .depth =
            {
                .compare = SG_COMPAREFUNC_ALWAYS,
                .write_enabled = false,
            },
        .colors =
            {
                [0] =
                    {
                        .write_mask = SG_COLORMASK_RGBA,
                        .blend =
                            {
                                .enabled = true,
                                .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                                .dst_factor_rgb =
                                    SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                            },
                    },
            },
        .label = "mesh-pipeline",
    });

    auto uberShader =
        sg_make_shader(rive_tess_path_shader_desc(sg_query_backend()));

    assert(maxClippingPaths < 256);

    // Src Over Pipelines
    {
        m_pathPipeline[0] = vectorPipeline(
            uberShader,
            {
                .enabled = true,
                .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
            },
            {
                .enabled = false,
            });

        for (std::size_t i = 1; i <= maxClippingPaths; i++)
        {
            m_pathPipeline[i] = vectorPipeline(
                uberShader,
                {
                    .enabled = true,
                    .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                    .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                },
                {
                    .enabled = true,
                    .ref = (uint8_t)i,
                    .read_mask = 0xFF,
                    .write_mask = 0x00,
                    .front =
                        {
                            .compare = SG_COMPAREFUNC_EQUAL,
                        },
                    .back =
                        {
                            .compare = SG_COMPAREFUNC_EQUAL,
                        },
                });
        }
    }

    // Screen Pipelines
    {
        m_pathScreenPipeline[0] = vectorPipeline(
            uberShader,
            {
                .enabled = true,
                .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                .dst_factor_rgb = SG_BLENDFACTOR_ONE,
                .src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA,
                .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
            },
            {
                .enabled = false,
            });

        for (std::size_t i = 1; i <= maxClippingPaths; i++)
        {
            m_pathScreenPipeline[i] = vectorPipeline(
                uberShader,
                {
                    .enabled = true,
                    .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                    .dst_factor_rgb = SG_BLENDFACTOR_ONE,
                    .src_factor_alpha = SG_BLENDFACTOR_SRC_ALPHA,
                    .dst_factor_alpha = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                },
                {
                    .enabled = true,
                    .ref = (uint8_t)i,
                    .read_mask = 0xFF,
                    .write_mask = 0x00,
                    .front =
                        {
                            .compare = SG_COMPAREFUNC_EQUAL,
                        },
                    .back =
                        {
                            .compare = SG_COMPAREFUNC_EQUAL,
                        },
                });
        }
    }

    // Additive Pipelines
    {
        m_pathAdditivePipeline[0] =
            vectorPipeline(uberShader,
                           {
                               .enabled = true,
                               .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                               .dst_factor_rgb = SG_BLENDFACTOR_ONE,
                           },
                           {
                               .enabled = false,
                           });

        for (std::size_t i = 1; i <= maxClippingPaths; i++)
        {
            m_pathAdditivePipeline[i] =
                vectorPipeline(uberShader,
                               {
                                   .enabled = true,
                                   .src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
                                   .dst_factor_rgb = SG_BLENDFACTOR_ONE,
                               },
                               {
                                   .enabled = true,
                                   .ref = (uint8_t)i,
                                   .read_mask = 0xFF,
                                   .write_mask = 0x00,
                                   .front =
                                       {
                                           .compare = SG_COMPAREFUNC_EQUAL,
                                       },
                                   .back =
                                       {
                                           .compare = SG_COMPAREFUNC_EQUAL,
                                       },
                               });
        }
    }

    // Multiply Pipelines
    {
        m_pathMultiplyPipeline[0] = vectorPipeline(
            uberShader,
            {
                .enabled = true,
                .src_factor_rgb = SG_BLENDFACTOR_DST_COLOR,
                .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
            },
            {
                .enabled = false,
            });

        for (std::size_t i = 1; i <= maxClippingPaths; i++)
        {
            m_pathMultiplyPipeline[i] = vectorPipeline(
                uberShader,
                {
                    .enabled = true,
                    .src_factor_rgb = SG_BLENDFACTOR_DST_COLOR,
                    .dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
                },
                {
                    .enabled = true,
                    .ref = (uint8_t)i,
                    .read_mask = 0xFF,
                    .write_mask = 0x00,
                    .front =
                        {
                            .compare = SG_COMPAREFUNC_EQUAL,
                        },
                    .back =
                        {
                            .compare = SG_COMPAREFUNC_EQUAL,
                        },
                });
        }
    }

    m_incClipPipeline =
        vectorPipeline(uberShader,
                       {
                           .enabled = false,
                       },
                       {
                           .enabled = true,
                           .read_mask = 0xFF,
                           .write_mask = 0xFF,
                           .front =
                               {
                                   .compare = SG_COMPAREFUNC_ALWAYS,
                                   .depth_fail_op = SG_STENCILOP_KEEP,
                                   .fail_op = SG_STENCILOP_KEEP,
                                   .pass_op = SG_STENCILOP_INCR_CLAMP,
                               },
                           .back =
                               {
                                   .compare = SG_COMPAREFUNC_ALWAYS,
                                   .depth_fail_op = SG_STENCILOP_KEEP,
                                   .fail_op = SG_STENCILOP_KEEP,
                                   .pass_op = SG_STENCILOP_INCR_CLAMP,
                               },
                       },
                       SG_COLORMASK_NONE);

    m_decClipPipeline =
        vectorPipeline(uberShader,
                       {
                           .enabled = false,
                       },
                       {
                           .enabled = true,
                           .read_mask = 0xFF,
                           .write_mask = 0xFF,
                           .front =
                               {
                                   .compare = SG_COMPAREFUNC_ALWAYS,
                                   .depth_fail_op = SG_STENCILOP_KEEP,
                                   .fail_op = SG_STENCILOP_KEEP,
                                   .pass_op = SG_STENCILOP_DECR_CLAMP,
                               },
                           .back =
                               {
                                   .compare = SG_COMPAREFUNC_ALWAYS,
                                   .depth_fail_op = SG_STENCILOP_KEEP,
                                   .fail_op = SG_STENCILOP_KEEP,
                                   .pass_op = SG_STENCILOP_DECR_CLAMP,
                               },
                       },
                       SG_COLORMASK_NONE);

    uint16_t indices[] = {0, 1, 2, 0, 2, 3};

    m_boundsIndices = sg_make_buffer((sg_buffer_desc){
        .type = SG_BUFFERTYPE_INDEXBUFFER,
        .data = SG_RANGE(indices),
    });
}

SokolTessRenderer::~SokolTessRenderer()
{
    sg_destroy_buffer(m_boundsIndices);
    sg_destroy_pipeline(m_meshPipeline);
    sg_destroy_pipeline(m_incClipPipeline);
    sg_destroy_pipeline(m_decClipPipeline);
    for (std::size_t i = 0; i <= maxClippingPaths; i++)
    {
        sg_destroy_pipeline(m_pathPipeline[i]);
        sg_destroy_pipeline(m_pathScreenPipeline[i]);
    }
}

void SokolTessRenderer::orthographicProjection(float left,
                                               float right,
                                               float bottom,
                                               float top,
                                               float near,
                                               float far)
{
    m_Projection[0] = 2.0f / (right - left);
    m_Projection[1] = 0.0f;
    m_Projection[2] = 0.0f;
    m_Projection[3] = 0.0f;

    m_Projection[4] = 0.0f;
    m_Projection[5] = 2.0f / (top - bottom);
    m_Projection[6] = 0.0f;
    m_Projection[7] = 0.0f;

#ifdef SOKOL_GLCORE33
    m_Projection[8] = 0.0f;
    m_Projection[9] = 0.0f;
    m_Projection[10] = 2.0f / (near - far);
    m_Projection[11] = 0.0f;

    m_Projection[12] = (right + left) / (left - right);
    m_Projection[13] = (top + bottom) / (bottom - top);
    m_Projection[14] = (far + near) / (near - far);
    m_Projection[15] = 1.0f;
#else
    // NDC are slightly different in Metal:
    // https://metashapes.com/blog/opengl-metal-projection-matrix-problem/
    m_Projection[8] = 0.0f;
    m_Projection[9] = 0.0f;
    m_Projection[10] = 1.0f / (far - near);
    m_Projection[11] = 0.0f;

    m_Projection[12] = (right + left) / (left - right);
    m_Projection[13] = (top + bottom) / (bottom - top);
    m_Projection[14] = near / (near - far);
    m_Projection[15] = 1.0f;
#endif
    // for (int i = 0; i < 4; i++) {
    //     int b = i * 4;
    //     printf("%f\t%f\t%f\t%f\n",
    //            m_Projection[b],
    //            m_Projection[b + 1],
    //            m_Projection[b + 2],
    //            m_Projection[b + 3]);
    // }
}

void SokolTessRenderer::drawImage(const RenderImage* image,
                                  ImageSampler,
                                  BlendMode,
                                  float opacity)
{
    LITE_RTTI_CAST_OR_RETURN(sokolImage, const SokolRenderImage*, image);

    vs_params_t vs_params;

    const Mat2D& world = transform();
    vs_params.mvp = m_Projection * world;

    setPipeline(m_meshPipeline);
    sg_bindings bind = {
        .vertex_buffers[0] = sokolImage->vertexBuffer(),
        .vertex_buffers[1] = sokolImage->uvBuffer(),
        .index_buffer = m_boundsIndices,
        .fs_images[SLOT_tex] = sokolImage->image(),
    };

    sg_apply_bindings(&bind);
    sg_apply_uniforms(SG_SHADERSTAGE_VS,
                      SLOT_vs_params,
                      SG_RANGE_REF(vs_params));
    sg_draw(0, 6, 1);
}

void SokolTessRenderer::drawImageMesh(const RenderImage* renderImage,
                                      ImageSampler,
                                      rcp<RenderBuffer> vertices_f32,
                                      rcp<RenderBuffer> uvCoords_f32,
                                      rcp<RenderBuffer> indices_u16,
                                      uint32_t vertexCount,
                                      uint32_t indexCount,
                                      BlendMode blendMode,
                                      float opacity)
{
    LITE_RTTI_CAST_OR_RETURN(sokolVertices, SokolBuffer*, vertices_f32.get());
    LITE_RTTI_CAST_OR_RETURN(sokolUVCoords, SokolBuffer*, uvCoords_f32.get());
    LITE_RTTI_CAST_OR_RETURN(sokolIndices, SokolBuffer*, indices_u16.get());
    LITE_RTTI_CAST_OR_RETURN(sokolRenderImage,
                             const SokolRenderImage*,
                             renderImage);

    vs_params_t vs_params;

    const Mat2D& world = transform();
    vs_params.mvp = m_Projection * world;

    setPipeline(m_meshPipeline);
    sg_bindings bind = {
        .vertex_buffers[0] = sokolVertices->buffer(),
        .vertex_buffers[1] = sokolUVCoords->buffer(),
        .index_buffer = sokolIndices->buffer(),
        .fs_images[SLOT_tex] = sokolRenderImage->image(),
    };

    sg_apply_bindings(&bind);
    sg_apply_uniforms(SG_SHADERSTAGE_VS,
                      SLOT_vs_params,
                      SG_RANGE_REF(vs_params));
    sg_draw(0, indexCount, 1);
}

class SokolGradient : public LITE_RTTI_OVERRIDE(RenderShader, SokolGradient)
{
private:
    Vec2D m_start;
    Vec2D m_end;
    int m_type;
    std::vector<float> m_colors;
    std::vector<float> m_stops;
    bool m_isVisible = false;

private:
    // General gradient
    SokolGradient(int type,
                  const ColorInt colors[],
                  const float stops[],
                  size_t count) :
        m_type(type)
    {
        m_stops.resize(count);
        m_colors.resize(count * 4);
        for (int i = 0, colorIndex = 0; i < count; i++, colorIndex += 4)
        {
            fillColorBuffer(&m_colors[colorIndex], colors[i]);
            m_stops[i] = stops[i];
            if (m_colors[colorIndex + 3] > 0.0f)
            {
                m_isVisible = true;
            }
        }
    }

public:
    // Linear gradient
    SokolGradient(float sx,
                  float sy,
                  float ex,
                  float ey,
                  const ColorInt colors[],
                  const float stops[],
                  size_t count) :
        SokolGradient(1, colors, stops, count)
    {
        m_start = Vec2D(sx, sy);
        m_end = Vec2D(ex, ey);
    }

    SokolGradient(float cx,
                  float cy,
                  float radius,
                  const ColorInt colors[], // [count]
                  const float stops[],     // [count]
                  size_t count) :
        SokolGradient(2, colors, stops, count)
    {
        m_start = Vec2D(cx, cy);
        m_end = Vec2D(cx + radius, cy);
    }

    void bind(vs_path_params_t& vertexUniforms,
              fs_path_uniforms_t& fragmentUniforms)
    {
        auto stopCount = m_stops.size();
        vertexUniforms.fillType = fragmentUniforms.fillType = m_type;
        vertexUniforms.gradientStart = m_start;
        vertexUniforms.gradientEnd = m_end;
        fragmentUniforms.stopCount = stopCount;
        for (int i = 0; i < stopCount; i++)
        {
            auto colorBufferIndex = i * 4;
            for (int j = 0; j < 4; j++)
            {
                fragmentUniforms.colors[i][j] = m_colors[colorBufferIndex + j];
            }
            fragmentUniforms.stops[i / 4][i % 4] = m_stops[i];
        }
    }
};

rcp<RenderShader> SokolFactory::makeLinearGradient(float sx,
                                                   float sy,
                                                   float ex,
                                                   float ey,
                                                   const ColorInt colors[],
                                                   const float stops[],
                                                   size_t count)
{
    return rcp<RenderShader>(
        new SokolGradient(sx, sy, ex, ey, colors, stops, count));
}

rcp<RenderShader> SokolFactory::makeRadialGradient(
    float cx,
    float cy,
    float radius,
    const ColorInt colors[], // [count]
    const float stops[],     // [count]
    size_t count)
{
    return rcp<RenderShader>(
        new SokolGradient(cx, cy, radius, colors, stops, count));
}

class SokolRenderPaint
    : public LITE_RTTI_OVERRIDE(RenderPaint, SokolRenderPaint)
{
private:
    fs_path_uniforms_t m_uniforms = {0};
    rcp<SokolGradient> m_shader;
    RenderPaintStyle m_style;
    std::unique_ptr<ContourStroke> m_stroke;
    bool m_strokeDirty = false;
    float m_strokeThickness = 0.0f;
    StrokeJoin m_strokeJoin;
    StrokeCap m_strokeCap;

    sg_buffer m_strokeVertexBuffer = {0};
    sg_buffer m_strokeIndexBuffer = {0};
    std::vector<std::size_t> m_StrokeOffsets;

    BlendMode m_blendMode = BlendMode::srcOver;

public:
    ~SokolRenderPaint() override
    {
        sg_destroy_buffer(m_strokeVertexBuffer);
        sg_destroy_buffer(m_strokeIndexBuffer);
    }

    void color(ColorInt value) override
    {
        fillColorBuffer(m_uniforms.colors[0], value);
        m_uniforms.fillType = 0;
    }

    void style(RenderPaintStyle value) override
    {
        m_style = value;

        switch (m_style)
        {
            case RenderPaintStyle::fill:
                m_stroke = nullptr;
                m_strokeDirty = false;
                break;
            case RenderPaintStyle::stroke:
                m_stroke = rivestd::make_unique<ContourStroke>();
                m_strokeDirty = true;
                break;
        }
    }

    RenderPaintStyle style() const { return m_style; }

    void thickness(float value) override
    {
        m_strokeThickness = value;
        m_strokeDirty = true;
    }

    void join(StrokeJoin value) override
    {
        m_strokeJoin = value;
        m_strokeDirty = true;
    }

    void cap(StrokeCap value) override
    {
        m_strokeCap = value;
        m_strokeDirty = true;
    }

    void invalidateStroke() override
    {
        if (m_stroke)
        {
            m_strokeDirty = true;
        }
    }

    void blendMode(BlendMode value) override { m_blendMode = value; }
    BlendMode blendMode() const { return m_blendMode; }

    void shader(rcp<RenderShader> shader) override
    {
        m_shader = lite_rtti_rcp_cast<SokolGradient>(std::move(shader));
    }

    void draw(vs_path_params_t& vertexUniforms, SokolRenderPath* path)
    {
        if (m_shader)
        {
            m_shader->bind(vertexUniforms, m_uniforms);
        }

        sg_apply_uniforms(SG_SHADERSTAGE_VS,
                          SLOT_vs_path_params,
                          SG_RANGE_REF(vertexUniforms));
        sg_apply_uniforms(SG_SHADERSTAGE_FS,
                          SLOT_fs_path_uniforms,
                          SG_RANGE_REF(m_uniforms));
        if (m_stroke != nullptr)
        {
            if (m_strokeDirty)
            {
                static Mat2D identity;
                m_stroke->reset();
                path->extrudeStroke(m_stroke.get(),
                                    m_strokeJoin,
                                    m_strokeCap,
                                    m_strokeThickness / 2.0f,
                                    identity);
                m_strokeDirty = false;

                const std::vector<Vec2D>& strip = m_stroke->triangleStrip();

                sg_destroy_buffer(m_strokeVertexBuffer);
                sg_destroy_buffer(m_strokeIndexBuffer);
                auto size = strip.size();
                if (size <= 2)
                {
                    m_strokeVertexBuffer = {0};
                    m_strokeIndexBuffer = {0};
                    return;
                }

                m_strokeVertexBuffer = sg_make_buffer((sg_buffer_desc){
                    .type = SG_BUFFERTYPE_VERTEXBUFFER,
                    .data =
                        {
                            strip.data(),
                            strip.size() * sizeof(Vec2D),
                        },
                });

                // Let's use a tris index buffer so we can keep the same sokol
                // pipeline.
                std::vector<uint16_t> indices;

                // Build them by stroke offsets (where each offset represents a
                // sub-path, or a move to)
                m_stroke->resetRenderOffset();
                m_StrokeOffsets.clear();
                while (true)
                {
                    std::size_t strokeStart, strokeEnd;
                    if (!m_stroke->nextRenderOffset(strokeStart, strokeEnd))
                    {
                        break;
                    }
                    std::size_t length = strokeEnd - strokeStart;
                    if (length > 2)
                    {
                        for (std::size_t i = 0, end = length - 2; i < end; i++)
                        {
                            if ((i % 2) == 1)
                            {
                                indices.push_back(i + strokeStart);
                                indices.push_back(i + 1 + strokeStart);
                                indices.push_back(i + 2 + strokeStart);
                            }
                            else
                            {
                                indices.push_back(i + strokeStart);
                                indices.push_back(i + 2 + strokeStart);
                                indices.push_back(i + 1 + strokeStart);
                            }
                        }
                        m_StrokeOffsets.push_back(indices.size());
                    }
                }

                m_strokeIndexBuffer = sg_make_buffer((sg_buffer_desc){
                    .type = SG_BUFFERTYPE_INDEXBUFFER,
                    .data =
                        {
                            indices.data(),
                            indices.size() * sizeof(uint16_t),
                        },
                });
            }
            if (m_strokeVertexBuffer.id == 0)
            {
                return;
            }

            sg_bindings bind = {
                .vertex_buffers[0] = m_strokeVertexBuffer,
                .index_buffer = m_strokeIndexBuffer,
            };

            sg_apply_bindings(&bind);

            m_stroke->resetRenderOffset();
            // path->drawStroke(m_stroke.get());
            std::size_t start = 0;
            for (auto end : m_StrokeOffsets)
            {
                sg_draw(start, end - start, 1);
                start = end;
            }
        }
        else
        {
            path->drawFill();
        }
    }
};

rcp<RenderPaint> SokolFactory::makeRenderPaint()
{
    return make_rcp<SokolRenderPaint>();
}

void SokolTessRenderer::restore()
{
    TessRenderer::restore();
    if (m_Stack.size() == 1)
    {
        // When we've fully restored, immediately update clip to not wait for
        // next draw.
        applyClipping();
        m_currentPipeline = {0};
    }
}

void SokolTessRenderer::applyClipping()
{
    if (!m_IsClippingDirty)
    {
        return;
    }
    m_IsClippingDirty = false;
    RenderState& state = m_Stack.back();

    auto currentClipLength = m_ClipPaths.size();
    if (currentClipLength == state.clipPaths.size())
    {
        // Same length so now check if they're all the same.
        bool allSame = true;
        for (std::size_t i = 0; i < currentClipLength; i++)
        {
            if (state.clipPaths[i].path() != m_ClipPaths[i].path())
            {
                allSame = false;
                break;
            }
        }
        if (allSame)
        {
            return;
        }
    }

    vs_path_params_t vs_params = {.fillType = 0};
    fs_path_uniforms_t uniforms = {0};

    // Decr any paths from the last clip that are gone.
    std::unordered_set<RenderPath*> alreadyApplied;

    for (auto appliedPath : m_ClipPaths)
    {
        bool decr = true;
        for (auto nextClipPath : state.clipPaths)
        {
            if (nextClipPath.path() == appliedPath.path())
            {
                decr = false;
                alreadyApplied.insert(appliedPath.path());
                break;
            }
        }
        if (decr)
        {
            // Draw appliedPath.path() with decr pipeline
            LITE_RTTI_CAST_OR_CONTINUE(sokolPath,
                                       SokolRenderPath*,
                                       appliedPath.path());
            setPipeline(m_decClipPipeline);
            vs_params.mvp = m_Projection * appliedPath.transform();
            sg_apply_uniforms(SG_SHADERSTAGE_VS,
                              SLOT_vs_path_params,
                              SG_RANGE_REF(vs_params));
            sg_apply_uniforms(SG_SHADERSTAGE_FS,
                              SLOT_fs_path_uniforms,
                              SG_RANGE_REF(uniforms));
            sokolPath->drawFill();
        }
    }

    // Incr any paths that are added.
    for (auto nextClipPath : state.clipPaths)
    {
        if (alreadyApplied.count(nextClipPath.path()))
        {
            // Already applied.
            continue;
        }
        // Draw nextClipPath.path() with incr pipeline
        LITE_RTTI_CAST_OR_CONTINUE(sokolPath,
                                   SokolRenderPath*,
                                   nextClipPath.path());
        setPipeline(m_incClipPipeline);
        vs_params.mvp = m_Projection * nextClipPath.transform();
        sg_apply_uniforms(SG_SHADERSTAGE_VS,
                          SLOT_vs_path_params,
                          SG_RANGE_REF(vs_params));
        sg_apply_uniforms(SG_SHADERSTAGE_FS,
                          SLOT_fs_path_uniforms,
                          SG_RANGE_REF(uniforms));
        sokolPath->drawFill();
    }

    // Pick which pipeline to use for draw path operations.
    // TODO: something similar for draw mesh.
    m_clipCount = state.clipPaths.size();

    m_ClipPaths = state.clipPaths;
}

void SokolTessRenderer::reset() { m_currentPipeline = {0}; }
void SokolTessRenderer::setPipeline(sg_pipeline pipeline)
{
    if (m_currentPipeline.id == pipeline.id)
    {
        return;
    }
    m_currentPipeline = pipeline;
    sg_apply_pipeline(pipeline);
}

void SokolTessRenderer::drawPath(RenderPath* path, RenderPaint* paint)
{
    LITE_RTTI_CAST_OR_RETURN(sokolPath, SokolRenderPath*, path);
    LITE_RTTI_CAST_OR_RETURN(sokolPaint, SokolRenderPaint*, paint);

    applyClipping();
    vs_path_params_t vs_params = {.fillType = 0};
    const Mat2D& world = transform();

    vs_params.mvp = m_Projection * world;
    switch (sokolPaint->blendMode())
    {
        case BlendMode::srcOver:
            setPipeline(m_pathPipeline[m_clipCount]);
            break;
        case BlendMode::screen:
            setPipeline(m_pathScreenPipeline[m_clipCount]);
            break;
        case BlendMode::colorDodge:
            setPipeline(m_pathAdditivePipeline[m_clipCount]);
            break;
        case BlendMode::multiply:
            setPipeline(m_pathMultiplyPipeline[m_clipCount]);
            break;
        default:
            setPipeline(m_pathScreenPipeline[m_clipCount]);
            break;
    }

    sokolPaint->draw(vs_params, sokolPath);
}

SokolRenderImageResource::SokolRenderImageResource(const uint8_t* bytes,
                                                   uint32_t width,
                                                   uint32_t height) :
    m_gpuResource(sg_make_image((sg_image_desc){
        .width = (int)width,
        .height = (int)height,
        .data.subimage[0][0] = {bytes, width * height * 4},
        .pixel_format = SG_PIXELFORMAT_RGBA8,
    }))
{}
SokolRenderImageResource::~SokolRenderImageResource()
{
    sg_destroy_image(m_gpuResource);
}

SokolRenderImage::SokolRenderImage(rcp<SokolRenderImageResource> image,
                                   uint32_t width,
                                   uint32_t height,
                                   const Mat2D& uvTransform) :

    lite_rtti_override(uvTransform), m_gpuImage(image)

{
    float halfWidth = width / 2.0f;
    float halfHeight = height / 2.0f;
    Vec2D points[] = {
        Vec2D(-halfWidth, -halfHeight),
        Vec2D(halfWidth, -halfHeight),
        Vec2D(halfWidth, halfHeight),
        Vec2D(-halfWidth, halfHeight),
    };
    m_vertexBuffer = sg_make_buffer((sg_buffer_desc){
        .type = SG_BUFFERTYPE_VERTEXBUFFER,
        .data = SG_RANGE(points),
    });

    Vec2D uv[] = {
        uvTransform * Vec2D(0.0f, 0.0f),
        uvTransform * Vec2D(1.0f, 0.0f),
        uvTransform * Vec2D(1.0f, 1.0f),
        uvTransform * Vec2D(0.0f, 1.0f),
    };

    m_uvBuffer = sg_make_buffer((sg_buffer_desc){
        .type = SG_BUFFERTYPE_VERTEXBUFFER,
        .data = SG_RANGE(uv),
    });
}

SokolRenderImage::~SokolRenderImage()
{
    sg_destroy_buffer(m_vertexBuffer);
    sg_destroy_buffer(m_uvBuffer);
}
