/*
 * Copyright (c) 2021 - 2023 the ThorVG project. All rights reserved.

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

{
    float _dudx = dudx, _dvdx = dvdx;
    float _dxdya = dxdya, _dxdyb = dxdyb, _dudya = dudya, _dvdya = dvdya;
    float _xa = xa, _xb = xb, _ua = ua, _va = va;
    auto sbuf = image->buf32;
    auto dbuf = surface->buf32;
    int32_t sw = static_cast<int32_t>(image->stride);
    int32_t sh = image->h;
    int32_t dw = surface->stride;
    int32_t x1, x2, x, y, ar, ab, iru, irv, px, ay;
    int32_t vv = 0, uu = 0;
    int32_t minx = INT32_MAX, maxx = INT32_MIN;
    float dx, u, v, iptr;
    uint32_t* buf;
    SwSpan* span = nullptr;         //used only when rle based.

#ifdef TEXMAP_MASKING
    uint8_t* cmp;
    auto csize = surface->compositor->image.channelSize;
#endif

    if (!_arrange(image, region, yStart, yEnd)) return;

    //Loop through all lines in the segment
    uint32_t spanIdx = 0;

    if (region) {
        minx = region->min.x;
        maxx = region->max.x;
    } else {
        span = image->rle->spans;
        while (span->y < yStart) {
            ++span;
            ++spanIdx;
        }
    }

    y = yStart;

    while (y < yEnd) {
        x1 = (int32_t)_xa;
        x2 = (int32_t)_xb;

        if (!region) {
            minx = INT32_MAX;
            maxx = INT32_MIN;
            //one single row, could be consisted of multiple spans.
            while (span->y == y && spanIdx < image->rle->size) {
                if (minx > span->x) minx = span->x;
                if (maxx < span->x + span->len) maxx = span->x + span->len;
                ++span;
                ++spanIdx;
            }
        }
        if (x1 < minx) x1 = minx;
        if (x2 > maxx) x2 = maxx;

        //Anti-Aliasing frames
        ay = y - aaSpans->yStart;
        if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
        if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;

        //Range exception
        if ((x2 - x1) < 1 || (x1 >= maxx) || (x2 <= minx)) goto next;

        //Perform subtexel pre-stepping on UV
        dx = 1 - (_xa - x1);
        u = _ua + dx * _dudx;
        v = _va + dx * _dvdx;

        buf = dbuf + ((y * dw) + x1);

        x = x1;

#ifdef TEXMAP_MASKING
        cmp = &surface->compositor->image.buf8[(y * surface->compositor->image.stride + x1) * csize];
#endif
        //Draw horizontal line
        while (x++ < x2) {
            uu = (int) u;
            vv = (int) v;

            ar = (int)(255 * (1 - modff(u, &iptr)));
            ab = (int)(255 * (1 - modff(v, &iptr)));
            iru = uu + 1;
            irv = vv + 1;

            if (vv >= sh) continue;

            px = *(sbuf + (vv * sw) + uu);

            /* horizontal interpolate */
            if (iru < sw) {
                /* right pixel */
                int px2 = *(sbuf + (vv * sw) + iru);
                px = INTERPOLATE(ar, px, px2);
            }
            /* vertical interpolate */
            if (irv < sh) {
                /* bottom pixel */
                int px2 = *(sbuf + (irv * sw) + uu);

                /* horizontal interpolate */
                if (iru < sw) {
                    /* bottom right pixel */
                    int px3 = *(sbuf + (irv * sw) + iru);
                    px2 = INTERPOLATE(ar, px2, px3);
                }
                px = INTERPOLATE(ab, px, px2);
            }
#if defined(TEXMAP_MASKING) && defined(TEXMAP_TRANSLUCENT)
            auto src = ALPHA_BLEND(px, _multiply<uint32_t>(opacity, blender(cmp)));
#elif defined(TEXMAP_MASKING)
            auto src = ALPHA_BLEND(px, blender(cmp));
#elif defined(TEXMAP_TRANSLUCENT)
            auto src = ALPHA_BLEND(px, opacity);
#else
            auto src = px;
#endif
            *buf = src + ALPHA_BLEND(*buf, _ialpha(src));
            ++buf;
#ifdef TEXMAP_MASKING
            cmp += csize;
#endif
            //Step UV horizontally
            u += _dudx;
            v += _dvdx;
            //range over?
            if ((uint32_t)v >= image->h) break;
        }
next:
        //Step along both edges
        _xa += _dxdya;
        _xb += _dxdyb;
        _ua += _dudya;
        _va += _dvdya;

        if (!region && spanIdx >= image->rle->size) break;

        ++y;
    }
    xa = _xa;
    xb = _xb;
    ua = _ua;
    va = _va;
}
