/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#ifndef SkIntersections_DEFINE
#define SkIntersections_DEFINE

#include "include/core/SkPoint.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkMalloc.h"
#include "src/pathops/SkPathOpsConic.h"
#include "src/pathops/SkPathOpsCubic.h"
#include "src/pathops/SkPathOpsDebug.h"
#include "src/pathops/SkPathOpsLine.h"
#include "src/pathops/SkPathOpsPoint.h"
#include "src/pathops/SkPathOpsQuad.h"
#include "src/pathops/SkPathOpsTCurve.h"
#include "src/pathops/SkPathOpsTypes.h"

#include <array>
#include <cstdint>

struct SkDRect;

class SkIntersections {
public:
    SkIntersections(SkDEBUGCODE(SkOpGlobalState* globalState = nullptr))
        : fSwap(0)
#ifdef SK_DEBUG
        SkDEBUGPARAMS(fDebugGlobalState(globalState))
        , fDepth(0)
#endif
    {
        sk_bzero(fPt, sizeof(fPt));
        sk_bzero(fPt2, sizeof(fPt2));
        sk_bzero(fT, sizeof(fT));
        sk_bzero(fNearlySame, sizeof(fNearlySame));
#if DEBUG_T_SECT_LOOP_COUNT
        sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount));
#endif
        reset();
        fMax = 0;  // require that the caller set the max
    }

    class TArray {
    public:
        explicit TArray(const double ts[10]) : fTArray(ts) {}
        double operator[](int n) const {
            return fTArray[n];
        }
        const double* fTArray;
    };
    TArray operator[](int n) const { return TArray(fT[n]); }

    void allowNear(bool nearAllowed) {
        fAllowNear = nearAllowed;
    }

    void clearCoincidence(int index) {
        SkASSERT(index >= 0);
        int bit = 1 << index;
        fIsCoincident[0] &= ~bit;
        fIsCoincident[1] &= ~bit;
    }

    int conicHorizontal(const SkPoint a[3], SkScalar weight, SkScalar left, SkScalar right,
                SkScalar y, bool flipped) {
        SkDConic conic;
        conic.set(a, weight);
        fMax = 2;
        return horizontal(conic, left, right, y, flipped);
    }

    int conicVertical(const SkPoint a[3], SkScalar weight, SkScalar top, SkScalar bottom,
            SkScalar x, bool flipped) {
        SkDConic conic;
        conic.set(a, weight);
        fMax = 2;
        return vertical(conic, top, bottom, x, flipped);
    }

    int conicLine(const SkPoint a[3], SkScalar weight, const SkPoint b[2]) {
        SkDConic conic;
        conic.set(a, weight);
        SkDLine line;
        line.set(b);
        fMax = 3; // 2;  permit small coincident segment + non-coincident intersection
        return intersect(conic, line);
    }

    int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y,
                        bool flipped) {
        SkDCubic cubic;
        cubic.set(a);
        fMax = 3;
        return horizontal(cubic, left, right, y, flipped);
    }

    int cubicVertical(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) {
        SkDCubic cubic;
        cubic.set(a);
        fMax = 3;
        return vertical(cubic, top, bottom, x, flipped);
    }

    int cubicLine(const SkPoint a[4], const SkPoint b[2]) {
        SkDCubic cubic;
        cubic.set(a);
        SkDLine line;
        line.set(b);
        fMax = 3;
        return intersect(cubic, line);
    }

#ifdef SK_DEBUG
    SkOpGlobalState* globalState() const { return fDebugGlobalState; }
#endif

    bool hasT(double t) const {
        SkASSERT(t == 0 || t == 1);
        return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1);
    }

    bool hasOppT(double t) const {
        SkASSERT(t == 0 || t == 1);
        return fUsed > 0 && (fT[1][0] == t || fT[1][fUsed - 1] == t);
    }

    int insertSwap(double one, double two, const SkDPoint& pt) {
        if (fSwap) {
            return insert(two, one, pt);
        } else {
            return insert(one, two, pt);
        }
    }

    bool isCoincident(int index) {
        return (fIsCoincident[0] & 1 << index) != 0;
    }

    int lineHorizontal(const SkPoint a[2], SkScalar left, SkScalar right, SkScalar y,
                       bool flipped) {
        SkDLine line;
        line.set(a);
        fMax = 2;
        return horizontal(line, left, right, y, flipped);
    }

    int lineVertical(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) {
        SkDLine line;
        line.set(a);
        fMax = 2;
        return vertical(line, top, bottom, x, flipped);
    }

    int lineLine(const SkPoint a[2], const SkPoint b[2]) {
        SkDLine aLine, bLine;
        aLine.set(a);
        bLine.set(b);
        fMax = 2;
        return intersect(aLine, bLine);
    }

    bool nearlySame(int index) const {
        SkASSERT(index == 0 || index == 1);
        return fNearlySame[index];
    }

    const SkDPoint& pt(int index) const {
        return fPt[index];
    }

    const SkDPoint& pt2(int index) const {
        return fPt2[index];
    }

    int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y,
                       bool flipped) {
        SkDQuad quad;
        quad.set(a);
        fMax = 2;
        return horizontal(quad, left, right, y, flipped);
    }

    int quadVertical(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) {
        SkDQuad quad;
        quad.set(a);
        fMax = 2;
        return vertical(quad, top, bottom, x, flipped);
    }

    int quadLine(const SkPoint a[3], const SkPoint b[2]) {
        SkDQuad quad;
        quad.set(a);
        SkDLine line;
        line.set(b);
        return intersect(quad, line);
    }

    // leaves swap, max alone
    void reset() {
        fAllowNear = true;
        fUsed = 0;
        sk_bzero(fIsCoincident, sizeof(fIsCoincident));
    }

    void set(bool swap, int tIndex, double t) {
        fT[(int) swap][tIndex] = t;
    }

    void setMax(int max) {
        SkASSERT(max <= (int) std::size(fPt));
        fMax = max;
    }

    void swap() {
        fSwap ^= true;
    }

    bool swapped() const {
        return fSwap;
    }

    int used() const {
        return fUsed;
    }

    void downDepth() {
        SkASSERT(--fDepth >= 0);
    }

    bool unBumpT(int index) {
        SkASSERT(fUsed == 1);
        fT[0][index] = fT[0][index] * (1 + BUMP_EPSILON * 2) - BUMP_EPSILON;
        if (!between(0, fT[0][index], 1)) {
            fUsed = 0;
            return false;
        }
        return true;
    }

    void upDepth() {
        SkASSERT(++fDepth < 16);
    }

    void alignQuadPts(const SkPoint a[3], const SkPoint b[3]);
    int cleanUpCoincidence();
    int closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* dist) const;
    void cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& c1,
                     const SkDCubic& c2);
    void flip();
    int horizontal(const SkDLine&, double left, double right, double y, bool flipped);
    int horizontal(const SkDQuad&, double left, double right, double y, bool flipped);
    int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]);
    int horizontal(const SkDCubic&, double y, double tRange[3]);
    int horizontal(const SkDConic&, double left, double right, double y, bool flipped);
    int horizontal(const SkDCubic&, double left, double right, double y, bool flipped);
    int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]);
    static double HorizontalIntercept(const SkDLine& line, double y);
    static int HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots);
    static int HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots);
    // FIXME : does not respect swap
    int insert(double one, double two, const SkDPoint& pt);
    void insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2);
    // start if index == 0 : end if index == 1
    int insertCoincident(double one, double two, const SkDPoint& pt);
    int intersect(const SkDLine&, const SkDLine&);
    int intersect(const SkDQuad&, const SkDLine&);
    int intersect(const SkDQuad&, const SkDQuad&);
    int intersect(const SkDConic&, const SkDLine&);
    int intersect(const SkDConic&, const SkDQuad&);
    int intersect(const SkDConic&, const SkDConic&);
    int intersect(const SkDCubic&, const SkDLine&);
    int intersect(const SkDCubic&, const SkDQuad&);
    int intersect(const SkDCubic&, const SkDConic&);
    int intersect(const SkDCubic&, const SkDCubic&);
    int intersectRay(const SkDLine&, const SkDLine&);
    int intersectRay(const SkDQuad&, const SkDLine&);
    int intersectRay(const SkDConic&, const SkDLine&);
    int intersectRay(const SkDCubic&, const SkDLine&);
    int intersectRay(const SkTCurve& tCurve, const SkDLine& line) {
        return tCurve.intersectRay(this, line);
    }

    void merge(const SkIntersections& , int , const SkIntersections& , int );
    int mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const;
    void removeOne(int index);
    void setCoincident(int index);
    int vertical(const SkDLine&, double top, double bottom, double x, bool flipped);
    int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped);
    int vertical(const SkDConic&, double top, double bottom, double x, bool flipped);
    int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped);
    static double VerticalIntercept(const SkDLine& line, double x);
    static int VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots);
    static int VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots);

    int depth() const {
#ifdef SK_DEBUG
        return fDepth;
#else
        return 0;
#endif
    }

    enum DebugLoop {
        kIterations_DebugLoop,
        kCoinCheck_DebugLoop,
        kComputePerp_DebugLoop,
    };

    void debugBumpLoopCount(DebugLoop );
    int debugCoincidentUsed() const;
    int debugLoopCount(DebugLoop ) const;
    void debugResetLoopCount();
    void dump() const;  // implemented for testing only

private:
    bool cubicCheckCoincidence(const SkDCubic& c1, const SkDCubic& c2);
    bool cubicExactEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2);
    void cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& );
    void cleanUpParallelLines(bool parallel);
    void computePoints(const SkDLine& line, int used);

    SkDPoint fPt[13];  // FIXME: since scans store points as SkPoint, this should also
    SkDPoint fPt2[2];  // used by nearly same to store alternate intersection point
    double fT[2][13];
    uint16_t fIsCoincident[2];  // bit set for each curve's coincident T
    bool fNearlySame[2];  // true if end points nearly match
    unsigned char fUsed;
    unsigned char fMax;
    bool fAllowNear;
    bool fSwap;
#ifdef SK_DEBUG
    SkOpGlobalState* fDebugGlobalState;
    int fDepth;
#endif
#if DEBUG_T_SECT_LOOP_COUNT
    int fDebugLoopCount[3];
#endif
};

#endif
