/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkAlphaRuns_DEFINED
#define SkAlphaRuns_DEFINED

#include "include/core/SkColor.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkCPUTypes.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkTo.h"

#include <cstdint>

/** Sparse array of run-length-encoded alpha (supersampling coverage) values.
    Sparseness allows us to independently compose several paths into the
    same SkAlphaRuns buffer.
*/

class SkAlphaRuns {
public:
    int16_t*    fRuns;
    uint8_t*     fAlpha;

    // Return 0-255 given 0-256
    static inline SkAlpha CatchOverflow(int alpha) {
        SkASSERT(alpha >= 0 && alpha <= 256);
        return alpha - (alpha >> 8);
    }

    /// Returns true if the scanline contains only a single run,
    /// of alpha value 0.
    bool empty() const {
        SkASSERT(fRuns[0] > 0);
        return fAlpha[0] == 0 && fRuns[fRuns[0]] == 0;
    }

    /// Reinitialize for a new scanline.
    void    reset(int width);

    /**
     *  Insert into the buffer a run starting at (x-offsetX):
     *      if startAlpha > 0
     *          one pixel with value += startAlpha,
     *              max 255
     *      if middleCount > 0
     *          middleCount pixels with value += maxValue
     *      if stopAlpha > 0
     *          one pixel with value += stopAlpha
     *  Returns the offsetX value that should be passed on the next call,
     *  assuming we're on the same scanline. If the caller is switching
     *  scanlines, then offsetX should be 0 when this is called.
     */
    SK_ALWAYS_INLINE int add(int x, U8CPU startAlpha, int middleCount, U8CPU stopAlpha,
                             U8CPU maxValue, int offsetX) {
        SkASSERT(middleCount >= 0);
        SkASSERT(x >= 0 && x + (startAlpha != 0) + middleCount + (stopAlpha != 0) <= fWidth);

        SkASSERT(fRuns[offsetX] >= 0);

        int16_t*    runs = fRuns + offsetX;
        uint8_t*    alpha = fAlpha + offsetX;
        uint8_t*    lastAlpha = alpha;
        x -= offsetX;

        if (startAlpha) {
            SkAlphaRuns::Break(runs, alpha, x, 1);
            /*  I should be able to just add alpha[x] + startAlpha.
                However, if the trailing edge of the previous span and the leading
                edge of the current span round to the same super-sampled x value,
                I might overflow to 256 with this add, hence the funny subtract (crud).
            */
            unsigned tmp = alpha[x] + startAlpha;
            SkASSERT(tmp <= 256);
            alpha[x] = SkToU8(tmp - (tmp >> 8));    // was (tmp >> 7), but that seems wrong if we're trying to catch 256

            runs += x + 1;
            alpha += x + 1;
            x = 0;
            SkDEBUGCODE(this->validate();)
        }

        if (middleCount) {
            SkAlphaRuns::Break(runs, alpha, x, middleCount);
            alpha += x;
            runs += x;
            x = 0;
            do {
                alpha[0] = SkToU8(CatchOverflow(alpha[0] + maxValue));
                int n = runs[0];
                SkASSERT(n <= middleCount);
                alpha += n;
                runs += n;
                middleCount -= n;
            } while (middleCount > 0);
            SkDEBUGCODE(this->validate();)
            lastAlpha = alpha;
        }

        if (stopAlpha) {
            SkAlphaRuns::Break(runs, alpha, x, 1);
            alpha += x;
            alpha[0] = SkToU8(alpha[0] + stopAlpha);
            SkDEBUGCODE(this->validate();)
            lastAlpha = alpha;
        }

        return SkToS32(lastAlpha - fAlpha);  // new offsetX
    }

    SkDEBUGCODE(void assertValid(int y, int maxStep) const;)
    SkDEBUGCODE(void dump() const;)

    /**
     * Break the runs in the buffer at offsets x and x+count, properly
     * updating the runs to the right and left.
     *   i.e. from the state AAAABBBB, run-length encoded as A4B4,
     *   Break(..., 2, 5) would produce AAAABBBB rle as A2A2B3B1.
     * Allows add() to sum another run to some of the new sub-runs.
     *   i.e. adding ..CCCCC. would produce AADDEEEB, rle as A2D2E3B1.
     */
    static void Break(int16_t runs[], uint8_t alpha[], int x, int count) {
        SkASSERT(count > 0 && x >= 0);

        //  SkAlphaRuns::BreakAt(runs, alpha, x);
        //  SkAlphaRuns::BreakAt(&runs[x], &alpha[x], count);

        int16_t* next_runs = runs + x;
        uint8_t*  next_alpha = alpha + x;

        while (x > 0) {
            int n = runs[0];
            SkASSERT(n > 0);

            if (x < n) {
                alpha[x] = alpha[0];
                runs[0] = SkToS16(x);
                runs[x] = SkToS16(n - x);
                break;
            }
            runs += n;
            alpha += n;
            x -= n;
        }

        runs = next_runs;
        alpha = next_alpha;
        x = count;

        for (;;) {
            int n = runs[0];
            SkASSERT(n > 0);

            if (x < n) {
                alpha[x] = alpha[0];
                runs[0] = SkToS16(x);
                runs[x] = SkToS16(n - x);
                break;
            }
            x -= n;
            if (x <= 0) {
                break;
            }
            runs += n;
            alpha += n;
        }
    }

    /**
     * Cut (at offset x in the buffer) a run into two shorter runs with
     * matching alpha values.
     * Used by the RectClipBlitter to trim a RLE encoding to match the
     * clipping rectangle.
     */
    static void BreakAt(int16_t runs[], uint8_t alpha[], int x) {
        while (x > 0) {
            int n = runs[0];
            SkASSERT(n > 0);

            if (x < n) {
                alpha[x] = alpha[0];
                runs[0] = SkToS16(x);
                runs[x] = SkToS16(n - x);
                break;
            }
            runs += n;
            alpha += n;
            x -= n;
        }
    }

private:
    SkDEBUGCODE(int fWidth;)
    SkDEBUGCODE(void validate() const;)
};

#endif
