/*
 * Copyright (c) 2020 Samsung Electronics Co., Ltd. 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.
 */

#include "vrle.h"
#include <vrect.h>
#include <algorithm>
#include <array>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <vector>
#include "vdebug.h"
#include "vglobal.h"

V_BEGIN_NAMESPACE

using Result = std::array<VRle::Span, 255>;
using rle_view = VRle::View;
static size_t _opGeneric(rle_view &a, rle_view &b, Result &result,
                         VRle::Data::Op op);
static size_t _opIntersect(const VRect &, rle_view &, Result &);
static size_t _opIntersect(rle_view &, rle_view &, Result &);

static inline uint8_t divBy255(int x)
{
    return (x + (x >> 8) + 0x80) >> 8;
}

inline static void copy(const VRle::Span *span, size_t count,
                        std::vector<VRle::Span> &v)
{
    // make sure enough memory available
    if (v.capacity() < v.size() + count) v.reserve(v.size() + count);
    std::copy(span, span + count, back_inserter(v));
}

void VRle::Data::addSpan(const VRle::Span *span, size_t count)
{
    copy(span, count, mSpans);
    mBboxDirty = true;
}

VRect VRle::Data::bbox() const
{
    updateBbox();
    return mBbox;
}

void VRle::Data::setBbox(const VRect &bbox) const
{
    mBboxDirty = false;
    mBbox = bbox;
}

void VRle::Data::reset()
{
    mSpans.clear();
    mBbox = VRect();
    mOffset = VPoint();
    mBboxDirty = false;
}

void VRle::Data::clone(const VRle::Data &o)
{
    *this = o;
}

void VRle::Data::translate(const VPoint &p)
{
    // take care of last offset if applied
    mOffset = p - mOffset;
    int x = mOffset.x();
    int y = mOffset.y();
    for (auto &i : mSpans) {
        i.x = i.x + x;
        i.y = i.y + y;
    }
    updateBbox();
    mBbox.translate(mOffset.x(), mOffset.y());
}

void VRle::Data::addRect(const VRect &rect)
{
    int x = rect.left();
    int y = rect.top();
    int width = rect.width();
    int height = rect.height();

    mSpans.reserve(size_t(height));

    VRle::Span span;
    for (int i = 0; i < height; i++) {
        span.x = x;
        span.y = y + i;
        span.len = width;
        span.coverage = 255;
        mSpans.push_back(span);
    }
    mBbox = rect;
}

void VRle::Data::updateBbox() const
{
    if (!mBboxDirty) return;

    mBboxDirty = false;

    int               l = std::numeric_limits<int>::max();
    const VRle::Span *span = mSpans.data();

    mBbox = VRect();
    size_t sz = mSpans.size();
    if (sz) {
        int t = span[0].y;
        int b = span[sz - 1].y;
        int r = 0;
        for (size_t i = 0; i < sz; i++) {
            if (span[i].x < l) l = span[i].x;
            if (span[i].x + span[i].len > r) r = span[i].x + span[i].len;
        }
        mBbox = VRect(l, t, r - l, b - t + 1);
    }
}

void VRle::Data::operator*=(uint8_t alpha)
{
    for (auto &i : mSpans) {
        i.coverage = divBy255(i.coverage * alpha);
    }
}

void VRle::Data::opIntersect(const VRect &r, VRle::VRleSpanCb cb,
                             void *userData) const
{
    if (empty()) return;

    if (r.contains(bbox())) {
        cb(mSpans.size(), mSpans.data(), userData);
        return;
    }

    auto   obj = view();
    Result result;
    // run till all the spans are processed
    while (obj.size()) {
        auto count = _opIntersect(r, obj, result);
        if (count) cb(count, result.data(), userData);
    }
}

// res = a - b;
void VRle::Data::opSubstract(const VRle::Data &aObj, const VRle::Data &bObj)
{
    // if two rle are disjoint
    if (!aObj.bbox().intersects(bObj.bbox())) {
        mSpans = aObj.mSpans;
    } else {
        auto a = aObj.view();
        auto b = bObj.view();

        auto aPtr = a.data();
        auto aEnd = a.data() + a.size();
        auto bPtr = b.data();
        auto bEnd = b.data() + b.size();

        // 1. forward a till it intersects with b
        while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++;
        auto count = aPtr - a.data();
        if (count) copy(a.data(), count, mSpans);

        // 2. forward b till it intersects with a
        if (aPtr != aEnd)
            while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++;

        // update a and b object
        a = {aPtr, size_t(aEnd - aPtr)};
        b = {bPtr, size_t(bEnd - bPtr)};

        // 3. calculate the intersect region
        Result result;

        // run till all the spans are processed
        while (a.size() && b.size()) {
            auto count = _opGeneric(a, b, result, Op::Substract);
            if (count) copy(result.data(), count, mSpans);
        }

        // 4. copy the rest of a
        if (a.size()) copy(a.data(), a.size(), mSpans);
    }

    mBboxDirty = true;
}

void VRle::Data::opGeneric(const VRle::Data &aObj, const VRle::Data &bObj,
                           Op op)
{
    // This routine assumes, obj1(span_y) < obj2(span_y).

    auto a = aObj.view();
    auto b = bObj.view();

    // reserve some space for the result vector.
    mSpans.reserve(a.size() + b.size());

    // if two rle are disjoint
    if (!aObj.bbox().intersects(bObj.bbox())) {
        if (a.data()[0].y < b.data()[0].y) {
            copy(a.data(), a.size(), mSpans);
            copy(b.data(), b.size(), mSpans);
        } else {
            copy(b.data(), b.size(), mSpans);
            copy(a.data(), a.size(), mSpans);
        }
    } else {
        auto aPtr = a.data();
        auto aEnd = a.data() + a.size();
        auto bPtr = b.data();
        auto bEnd = b.data() + b.size();

        // 1. forward a till it intersects with b
        while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++;

        auto count = aPtr - a.data();
        if (count) copy(a.data(), count, mSpans);

        // 2. forward b till it intersects with a
        if (aPtr != aEnd)
            while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++;

        count = bPtr - b.data();
        if (count) copy(b.data(), count, mSpans);

        // update a and b object
        a = {aPtr, size_t(aEnd - aPtr)};
        b = {bPtr, size_t(bEnd - bPtr)};

        // 3. calculate the intersect region
        Result result;

        // run till all the spans are processed
        while (a.size() && b.size()) {
            auto count = _opGeneric(a, b, result, op);
            if (count) copy(result.data(), count, mSpans);
        }
        // 3. copy the rest
        if (b.size()) copy(b.data(), b.size(), mSpans);
        if (a.size()) copy(a.data(), a.size(), mSpans);
    }

    mBboxDirty = true;
}

static inline V_ALWAYS_INLINE void _opIntersectPrepare(VRle::View &a,
                                                       VRle::View &b)
{
    auto aPtr = a.data();
    auto aEnd = a.data() + a.size();
    auto bPtr = b.data();
    auto bEnd = b.data() + b.size();

    // 1. advance a till it intersects with b
    while ((aPtr != aEnd) && (aPtr->y < bPtr->y)) aPtr++;

    // 2. advance b till it intersects with a
    if (aPtr != aEnd)
        while ((bPtr != bEnd) && (bPtr->y < aPtr->y)) bPtr++;

    // update a and b object
    a = {aPtr, size_t(aEnd - aPtr)};
    b = {bPtr, size_t(bEnd - bPtr)};
}

void VRle::Data::opIntersect(VRle::View a, VRle::View b)
{
    _opIntersectPrepare(a, b);
    Result result;
    while (a.size()) {
        auto count = _opIntersect(a, b, result);
        if (count) copy(result.data(), count, mSpans);
    }

    updateBbox();
}

static void _opIntersect(rle_view a, rle_view b, VRle::VRleSpanCb cb,
                         void *userData)
{
    if (!cb) return;

    _opIntersectPrepare(a, b);
    Result result;
    while (a.size()) {
        auto count = _opIntersect(a, b, result);
        if (count) cb(count, result.data(), userData);
    }
}

/*
 * This function will clip a rle list with another rle object
 * tmp_clip  : The rle list that will be use to clip the rle
 * tmp_obj   : holds the list of spans that has to be clipped
 * result    : will hold the result after the processing
 * NOTE: if the algorithm runs out of the result buffer list
 *       it will stop and update the tmp_obj with the span list
 *       that are yet to be processed as well as the tpm_clip object
 *       with the unprocessed clip spans.
 */

static size_t _opIntersect(rle_view &obj, rle_view &clip, Result &result)
{
    auto out = result.data();
    auto available = result.max_size();
    auto spans = obj.data();
    auto end = obj.data() + obj.size();
    auto clipSpans = clip.data();
    auto clipEnd = clip.data() + clip.size();
    int  sx1, sx2, cx1, cx2, x, len;

    while (available && spans < end) {
        if (clipSpans >= clipEnd) {
            spans = end;
            break;
        }
        if (clipSpans->y > spans->y) {
            ++spans;
            continue;
        }
        if (spans->y != clipSpans->y) {
            ++clipSpans;
            continue;
        }
        // assert(spans->y == (clipSpans->y + clip_offset_y));
        sx1 = spans->x;
        sx2 = sx1 + spans->len;
        cx1 = clipSpans->x;
        cx2 = cx1 + clipSpans->len;

        if (cx1 < sx1 && cx2 < sx1) {
            ++clipSpans;
            continue;
        } else if (sx1 < cx1 && sx2 < cx1) {
            ++spans;
            continue;
        }
        x = std::max(sx1, cx1);
        len = std::min(sx2, cx2) - x;
        if (len) {
            out->x = std::max(sx1, cx1);
            out->len = (std::min(sx2, cx2) - out->x);
            out->y = spans->y;
            out->coverage = divBy255(spans->coverage * clipSpans->coverage);
            ++out;
            --available;
        }
        if (sx2 < cx2) {
            ++spans;
        } else {
            ++clipSpans;
        }
    }

    // update the obj view yet to be processed
    obj = {spans, size_t(end - spans)};

    // update the clip view yet to be processed
    clip = {clipSpans, size_t(clipEnd - clipSpans)};

    return result.max_size() - available;
}

/*
 * This function will clip a rle list with a given rect
 * clip      : The clip rect that will be use to clip the rle
 * tmp_obj   : holds the list of spans that has to be clipped
 * result    : will hold the result after the processing
 * NOTE: if the algorithm runs out of the result buffer list
 *       it will stop and update the tmp_obj with the span list
 *       that are yet to be processed
 */
static size_t _opIntersect(const VRect &clip, rle_view &obj, Result &result)
{
    auto out = result.data();
    auto available = result.max_size();
    auto ptr = obj.data();
    auto end = obj.data() + obj.size();

    const auto minx = clip.left();
    const auto miny = clip.top();
    const auto maxx = clip.right() - 1;
    const auto maxy = clip.bottom() - 1;

    while (available && ptr < end) {
        const auto &span = *ptr;
        if (span.y > maxy) {
            ptr = end;  // update spans so that we can breakout
            break;
        }
        if (span.y < miny || span.x > maxx || span.x + span.len <= minx) {
            ++ptr;
            continue;
        }
        if (span.x < minx) {
            out->len = std::min(span.len - (minx - span.x), maxx - minx + 1);
            out->x = minx;
        } else {
            out->x = span.x;
            out->len = std::min(span.len, uint16_t(maxx - span.x + 1));
        }
        if (out->len != 0) {
            out->y = span.y;
            out->coverage = span.coverage;
            ++out;
            --available;
        }
        ++ptr;
    }

    // update the span list that yet to be processed
    obj = {ptr, size_t(end - ptr)};

    return result.max_size() - available;
}

static void blitXor(VRle::Span *spans, int count, uint8_t *buffer, int offsetX)
{
    while (count--) {
        int    x = spans->x + offsetX;
        int    l = spans->len;
        uint8_t *ptr = buffer + x;
        while (l--) {
            int da = *ptr;
            *ptr = divBy255((255 - spans->coverage) * (da) +
                            spans->coverage * (255 - da));
            ptr++;
        }
        spans++;
    }
}

static void blitDestinationOut(VRle::Span *spans, int count, uint8_t *buffer,
                               int offsetX)
{
    while (count--) {
        int    x = spans->x + offsetX;
        int    l = spans->len;
        uint8_t *ptr = buffer + x;
        while (l--) {
            *ptr = divBy255((255 - spans->coverage) * (*ptr));
            ptr++;
        }
        spans++;
    }
}

static void blitSrcOver(VRle::Span *spans, int count, uint8_t *buffer,
                        int offsetX)
{
    while (count--) {
        int    x = spans->x + offsetX;
        int    l = spans->len;
        uint8_t *ptr = buffer + x;
        while (l--) {
            *ptr = spans->coverage + divBy255((255 - spans->coverage) * (*ptr));
            ptr++;
        }
        spans++;
    }
}

void blitSrc(VRle::Span *spans, int count, uint8_t *buffer, int offsetX)
{
    while (count--) {
        int    x = spans->x + offsetX;
        int    l = spans->len;
        uint8_t *ptr = buffer + x;
        while (l--) {
            *ptr = std::max(spans->coverage, *ptr);
            ptr++;
        }
        spans++;
    }
}

size_t bufferToRle(uint8_t *buffer, int size, int offsetX, int y,
                   VRle::Span *out)
{
    size_t count = 0;
    uint8_t value = buffer[0];
    int    curIndex = 0;

    // size = offsetX < 0 ? size + offsetX : size;
    for (int i = 0; i < size; i++) {
        uint8_t curValue = buffer[0];
        if (value != curValue) {
            if (value) {
                out->y = y;
                out->x = offsetX + curIndex;
                out->len = i - curIndex;
                out->coverage = value;
                out++;
                count++;
            }
            curIndex = i;
            value = curValue;
        }
        buffer++;
    }
    if (value) {
        out->y = y;
        out->x = offsetX + curIndex;
        out->len = size - curIndex;
        out->coverage = value;
        count++;
    }
    return count;
}

struct SpanMerger {
    explicit SpanMerger(VRle::Data::Op op)
    {
        switch (op) {
        case VRle::Data::Op::Add:
            _blitter = &blitSrcOver;
            break;
        case VRle::Data::Op::Xor:
            _blitter = &blitXor;
            break;
        case VRle::Data::Op::Substract:
            _blitter = &blitDestinationOut;
            break;
        }
    }
    using blitter = void (*)(VRle::Span *, int, uint8_t *, int);
    blitter                     _blitter;
    std::array<VRle::Span, 256> _result;
    std::array<uint8_t, 1024>   _buffer;
    VRle::Span *                _aStart{nullptr};
    VRle::Span *                _bStart{nullptr};

    void revert(VRle::Span *&aPtr, VRle::Span *&bPtr)
    {
        aPtr = _aStart;
        bPtr = _bStart;
    }
    VRle::Span *data() { return _result.data(); }
    size_t merge(VRle::Span *&aPtr, const VRle::Span *aEnd, VRle::Span *&bPtr,
                 const VRle::Span *bEnd);
};

size_t SpanMerger::merge(VRle::Span *&aPtr, const VRle::Span *aEnd,
                         VRle::Span *&bPtr, const VRle::Span *bEnd)
{
    assert(aPtr->y == bPtr->y);

    _aStart = aPtr;
    _bStart = bPtr;
    int lb = std::min(aPtr->x, bPtr->x);
    int y = aPtr->y;

    while (aPtr < aEnd && aPtr->y == y) aPtr++;
    while (bPtr < bEnd && bPtr->y == y) bPtr++;

    int ub = std::max((aPtr - 1)->x + (aPtr - 1)->len,
                      (bPtr - 1)->x + (bPtr - 1)->len);
    int length = (lb < 0) ? ub + lb : ub - lb;

    if (length <= 0 || size_t(length) >= _buffer.max_size()) {
        // can't handle merge . skip
        return 0;
    }

    // clear buffer
    memset(_buffer.data(), 0, length);

    // blit a to buffer
    blitSrc(_aStart, aPtr - _aStart, _buffer.data(), -lb);

    // blit b to buffer
    _blitter(_bStart, bPtr - _bStart, _buffer.data(), -lb);

    // convert buffer to span
    return bufferToRle(_buffer.data(), length, lb, y, _result.data());
}

static size_t _opGeneric(rle_view &a, rle_view &b, Result &result,
                         VRle::Data::Op op)
{
    SpanMerger merger{op};

    auto   out = result.data();
    size_t available = result.max_size();
    auto   aPtr = a.data();
    auto   aEnd = a.data() + a.size();
    auto   bPtr = b.data();
    auto   bEnd = b.data() + b.size();

    // only logic change for substract operation.
    const bool keep = op != (VRle::Data::Op::Substract);

    while (available && aPtr < aEnd && bPtr < bEnd) {
        if (aPtr->y < bPtr->y) {
            *out++ = *aPtr++;
            available--;
        } else if (bPtr->y < aPtr->y) {
            if (keep) {
                *out++ = *bPtr;
                available--;
            }
            bPtr++;
        } else {  // same y
            auto count = merger.merge(aPtr, aEnd, bPtr, bEnd);
            if (available >= count) {
                if (count) {
                    memcpy(out, merger.data(), count * sizeof(VRle::Span));
                    out += count;
                    available -= count;
                }
            } else {
                // not enough space try next time.
                merger.revert(aPtr, bPtr);
                break;
            }
        }
    }
    // update the span list that yet to be processed
    a = {aPtr, size_t(aEnd - aPtr)};
    b = {bPtr, size_t(bEnd - bPtr)};

    return result.max_size() - available;
}

/*
 * this api makes use of thread_local temporary
 * buffer to avoid creating intermediate temporary rle buffer
 * the scratch buffer object will grow its size on demand
 * so that future call won't need any more memory allocation.
 * this function is thread safe as it uses thread_local variable
 * which is unique per thread.
 */
static vthread_local VRle::Data Scratch_Object;

VRle VRle::opGeneric(const VRle &o, Data::Op op) const
{
    if (empty()) return o;
    if (o.empty()) return *this;

    Scratch_Object.reset();
    Scratch_Object.opGeneric(d.read(), o.d.read(), op);

    VRle result;
    result.d.write() = Scratch_Object;

    return result;
}

VRle VRle::operator-(const VRle &o) const
{
    if (empty()) return {};
    if (o.empty()) return *this;

    Scratch_Object.reset();
    Scratch_Object.opSubstract(d.read(), o.d.read());

    VRle result;
    result.d.write() = Scratch_Object;

    return result;
}

VRle VRle::operator&(const VRle &o) const
{
    if (empty() || o.empty()) return {};

    Scratch_Object.reset();
    Scratch_Object.opIntersect(d.read().view(), o.d.read().view());

    VRle result;
    result.d.write() = Scratch_Object;

    return result;
}

void VRle::operator&=(const VRle &o)
{
    if (empty()) return;
    if (o.empty()) {
        reset();
        return;
    }
    Scratch_Object.reset();
    Scratch_Object.opIntersect(d.read().view(), o.d.read().view());
    d.write() = Scratch_Object;
}

VRle operator-(const VRect &rect, const VRle &o)
{
    if (rect.empty()) return {};

    Scratch_Object.reset();
    Scratch_Object.addRect(rect);

    VRle result;
    result.d.write().opSubstract(Scratch_Object, o.d.read());

    return result;
}

VRle operator&(const VRect &rect, const VRle &o)
{
    if (rect.empty() || o.empty()) return {};

    Scratch_Object.reset();
    Scratch_Object.addRect(rect);

    VRle result;
    result.d.write().opIntersect(Scratch_Object.view(), o.d.read().view());

    return result;
}

void VRle::intersect(const VRle &clip, VRleSpanCb cb, void *userData) const
{
    if (empty() || clip.empty()) return;

    _opIntersect(d.read().view(), clip.d.read().view(), cb, userData);
}

V_END_NAMESPACE