/*
 * 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 SkEdge_DEFINED
#define SkEdge_DEFINED

#include "include/core/SkMath.h"
#include "include/core/SkRect.h"
#include "include/private/SkTo.h"
#include "src/core/SkFDot6.h"

#include <utility>

// This correctly favors the lower-pixel when y0 is on a 1/2 pixel boundary
#define SkEdge_Compute_DY(top, y0)  (SkLeftShift(top, 6) + 32 - (y0))

struct SkEdge {
    enum Type {
        kLine_Type,
        kQuad_Type,
        kCubic_Type
    };

    SkEdge* fNext;
    SkEdge* fPrev;

    SkFixed fX;
    SkFixed fDX;
    int32_t fFirstY;
    int32_t fLastY;
    int8_t fCurveCount;    // only used by kQuad(+) and kCubic(-)
    uint8_t fCurveShift;    // appled to all Dx/DDx/DDDx except for fCubicDShift exception
    uint8_t fCubicDShift;   // applied to fCDx and fCDy only in cubic
    int8_t  fWinding;       // 1 or -1

    int setLine(const SkPoint& p0, const SkPoint& p1, const SkIRect* clip, int shiftUp);
    // call this version if you know you don't have a clip
    inline int setLine(const SkPoint& p0, const SkPoint& p1, int shiftUp);
    inline int updateLine(SkFixed ax, SkFixed ay, SkFixed bx, SkFixed by);
    void chopLineWithClip(const SkIRect& clip);

    inline bool intersectsClip(const SkIRect& clip) const {
        SkASSERT(fFirstY < clip.fBottom);
        return fLastY >= clip.fTop;
    }

#ifdef SK_DEBUG
    void dump() const {
        SkDebugf("edge: firstY:%d lastY:%d x:%g dx:%g w:%d\n", fFirstY, fLastY, SkFixedToFloat(fX), SkFixedToFloat(fDX), fWinding);
    }

    void validate() const {
        SkASSERT(fPrev && fNext);
        SkASSERT(fPrev->fNext == this);
        SkASSERT(fNext->fPrev == this);

        SkASSERT(fFirstY <= fLastY);
        SkASSERT(SkAbs32(fWinding) == 1);
    }
#endif
};

struct SkQuadraticEdge : public SkEdge {
    SkFixed fQx, fQy;
    SkFixed fQDx, fQDy;
    SkFixed fQDDx, fQDDy;
    SkFixed fQLastX, fQLastY;

    bool setQuadraticWithoutUpdate(const SkPoint pts[3], int shiftUp);
    int setQuadratic(const SkPoint pts[3], int shiftUp);
    int updateQuadratic();
};

struct SkCubicEdge : public SkEdge {
    SkFixed fCx, fCy;
    SkFixed fCDx, fCDy;
    SkFixed fCDDx, fCDDy;
    SkFixed fCDDDx, fCDDDy;
    SkFixed fCLastX, fCLastY;

    bool setCubicWithoutUpdate(const SkPoint pts[4], int shiftUp, bool sortY = true);
    int setCubic(const SkPoint pts[4], int shiftUp);
    int updateCubic();
};

int SkEdge::setLine(const SkPoint& p0, const SkPoint& p1, int shift) {
    SkFDot6 x0, y0, x1, y1;

    {
#ifdef SK_RASTERIZE_EVEN_ROUNDING
        x0 = SkScalarRoundToFDot6(p0.fX, shift);
        y0 = SkScalarRoundToFDot6(p0.fY, shift);
        x1 = SkScalarRoundToFDot6(p1.fX, shift);
        y1 = SkScalarRoundToFDot6(p1.fY, shift);
#else
        float scale = float(1 << (shift + 6));
        x0 = int(p0.fX * scale);
        y0 = int(p0.fY * scale);
        x1 = int(p1.fX * scale);
        y1 = int(p1.fY * scale);
#endif
    }

    int winding = 1;

    if (y0 > y1) {
        using std::swap;
        swap(x0, x1);
        swap(y0, y1);
        winding = -1;
    }

    int top = SkFDot6Round(y0);
    int bot = SkFDot6Round(y1);

    // are we a zero-height line?
    if (top == bot) {
        return 0;
    }

    SkFixed slope = SkFDot6Div(x1 - x0, y1 - y0);
    const SkFDot6 dy  = SkEdge_Compute_DY(top, y0);

    fX          = SkFDot6ToFixed(x0 + SkFixedMul(slope, dy));   // + SK_Fixed1/2
    fDX         = slope;
    fFirstY     = top;
    fLastY      = bot - 1;
    fCurveCount = 0;
    fWinding    = SkToS8(winding);
    fCurveShift = 0;
    return 1;
}

#endif
