#include "shell/instrument/editor_stroke.h"

#ifdef _WIN32

#include <cstddef>

namespace reasampler::vst {
namespace {

// Draw-thread-only scratch. It lives here rather than on the editor because a stroke is a leaf
// draw call reached from eight paint sites — threading a canvas through every one of them would
// grow those signatures to carry an allocation detail. Reuse is the point: after the first paint
// the mask and the point list are resized, never reallocated.
thread_local ui::StrokeCanvas g_canvas;
thread_local std::vector<ui::StrokePoint> g_arcPoints;
thread_local std::vector<ui::StrokePoint> g_scratchPoints;

// Per-pixel fallback through LICE_PutPixel, which itself reads LICE_EXT_GET_SCALING and scales
// its target coordinate (lice.cpp's LICE_PutPixel) — unlike the raw-bits path below, this is
// correct under a scaled bitmap. Only taken when scaling is active (see blendCanvas), so it
// costs nothing at the common unscaled call site.
void blendScaledFallback(LICE_IBitmap* bmp, const ui::StrokeCanvas& canvas, LICE_pixel color,
                         float alpha) {
    const ui::Rect& b = canvas.bounds();
    for (int y = b.y; y < b.bottom(); ++y) {
        const int lo = canvas.rowLo(y);
        const int hi = canvas.rowHi(y);
        if (hi <= lo) continue;
        const float* const cov = canvas.rowData(y);
        for (int i = lo; i < hi; ++i) {
            const float a = cov[i] * alpha;
            if (a <= 0.0f) continue;
            LICE_PutPixel(bmp, b.x + i, y, color, a, LICE_BLIT_MODE_COPY);
        }
    }
}

// One blend of the finished mask. The arithmetic matches LICE's own mode-0 combine
// (src + (dst-src)*(256-a)/256 on all four channels, alpha in .8 fixed point) so a stroke
// composites identically to every other kit draw on the same surface — written straight to the
// bitmap's bits rather than through LICE_PutPixel, which re-derives the row pointer per pixel.
//
// That raw write assumes getWidth()/getHeight() (LOGICAL) and getRowSpan() (the DIB's PHYSICAL
// stride) agree — true only when unscaled. LICE_Arc/LICE_Line read LICE_EXT_GET_SCALING and
// scale their coordinates before touching the DIB (lice_arc.cpp:543, lice_line.cpp:1932);
// LICE_SysBitmap::__resize keeps m_width logical while sizing the DIB by
// (w*m_draw_scaling)>>8 (lice.cpp:165-173). Under a scale this loop's geometry and its target
// stride would disagree — a scale >256 lands the stroke in the wrong quadrant, a scale <256
// runs the write past the DIB allocation. Nothing calls SET_SCALING today, but the guard has to
// stay ahead of the day something does.
void blendCanvas(LICE_IBitmap* bmp, const ui::StrokeCanvas& canvas, LICE_pixel color,
                 float alpha) {
    const ui::Rect& b = canvas.bounds();
    if (b.empty() || alpha <= 0.0f) return;
    if (bmp->Extended(LICE_EXT_GET_SCALING, nullptr) != 0) {
        blendScaledFallback(bmp, canvas, color, alpha);
        return;
    }
    LICE_pixel* const bits = bmp->getBits();
    const int span = bmp->getRowSpan();
    if (bits == nullptr || span <= 0) return;
    const bool flipped = bmp->isFlipped();
    const int height = bmp->getHeight();

    const int sr = LICE_GETR(color);
    const int sg = LICE_GETG(color);
    const int sb = LICE_GETB(color);
    const int sa = LICE_GETA(color);
    for (int y = b.y; y < b.bottom(); ++y) {
        const int lo = canvas.rowLo(y);
        const int hi = canvas.rowHi(y);
        if (hi <= lo) continue;
        const float* const cov = canvas.rowData(y);
        LICE_pixel* const row =
            bits + ui::rasterRowOffset(y, height, span, flipped) + static_cast<std::size_t>(b.x);
        for (int i = lo; i < hi; ++i) {
            const int ia = static_cast<int>(cov[i] * alpha * 256.0f);
            if (ia <= 0) continue;
            if (ia >= 256) {
                row[i] = color;
                continue;
            }
            const int sc = 256 - ia;
            LICE_pixel_chan* const d = reinterpret_cast<LICE_pixel_chan*>(row + i);
            d[LICE_PIXEL_R] = static_cast<LICE_pixel_chan>(sr + ((d[LICE_PIXEL_R] - sr) * sc) / 256);
            d[LICE_PIXEL_G] = static_cast<LICE_pixel_chan>(sg + ((d[LICE_PIXEL_G] - sg) * sc) / 256);
            d[LICE_PIXEL_B] = static_cast<LICE_pixel_chan>(sb + ((d[LICE_PIXEL_B] - sb) * sc) / 256);
            d[LICE_PIXEL_A] = static_cast<LICE_pixel_chan>(sa + ((d[LICE_PIXEL_A] - sa) * sc) / 256);
        }
    }
}

ui::Rect bitmapRect(LICE_IBitmap* bmp) {
    return ui::Rect{0, 0, bmp->getWidth(), bmp->getHeight()};
}

}  // namespace

void strokePolylineAA(LICE_IBitmap* bmp, const ui::StrokePoint* pts, std::size_t count,
                      float widthPx, LICE_pixel color, float alpha) {
    if (bmp == nullptr || pts == nullptr || count == 0 || widthPx <= 0.0f) return;
    ui::strokePolyline(g_canvas, pts, count, widthPx * 0.5f, bitmapRect(bmp));
    blendCanvas(bmp, g_canvas, color, alpha);
}

void strokeArcAA(LICE_IBitmap* bmp, float cx, float cy, float radius, float startRad, float endRad,
                 float widthPx, LICE_pixel color, float alpha) {
    if (bmp == nullptr || widthPx <= 0.0f) return;
    g_arcPoints.clear();
    ui::appendArc(g_arcPoints, cx, cy, radius, startRad, endRad);
    strokePolylineAA(bmp, g_arcPoints.data(), g_arcPoints.size(), widthPx, color, alpha);
}

std::vector<ui::StrokePoint>& scratchPoints() { return g_scratchPoints; }

}  // namespace reasampler::vst

#endif  // _WIN32
