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

#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/private/base/SkDebug.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkArenaAlloc.h"
#include "src/pathops/SkPathOpsPoint.h"
#include "src/pathops/SkPathOpsRect.h"
#include "src/pathops/SkPathOpsTCurve.h"
#include "src/pathops/SkPathOpsTypes.h"

#include <cstdint>

class SkIntersections;
class SkTSect;
class SkTSpan;
struct SkDLine;

#ifdef SK_DEBUG
typedef uint8_t SkOpDebugBool;
#else
typedef bool SkOpDebugBool;
#endif

static inline bool SkDoubleIsNaN(double x) {
    return x != x;
}

class SkTCoincident {
public:
    SkTCoincident() {
        this->init();
    }

    void debugInit() {
#ifdef SK_DEBUG
        this->fPerpPt.fX = this->fPerpPt.fY = SK_ScalarNaN;
        this->fPerpT = SK_ScalarNaN;
        this->fMatch = 0xFF;
#endif
    }

    char dumpIsCoincidentStr() const;
    void dump() const;

    bool isMatch() const {
        SkASSERT(!!fMatch == fMatch);
        return SkToBool(fMatch);
    }

    void init() {
        fPerpT = -1;
        fMatch = false;
        fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN;
    }

    void markCoincident() {
        if (!fMatch) {
            fPerpT = -1;
        }
        fMatch = true;
    }

    const SkDPoint& perpPt() const {
        return fPerpPt;
    }

    double perpT() const {
        return fPerpT;
    }

    void setPerp(const SkTCurve& c1, double t, const SkDPoint& cPt, const SkTCurve& );

private:
    SkDPoint fPerpPt;
    double fPerpT;  // perpendicular intersection on opposite curve
    SkOpDebugBool fMatch;
};

struct SkTSpanBounded {
    SkTSpan* fBounded;
    SkTSpanBounded* fNext;
};

class SkTSpan {
public:
    SkTSpan(const SkTCurve& curve, SkArenaAlloc& heap) {
        fPart = curve.make(heap);
    }

    void addBounded(SkTSpan* , SkArenaAlloc* );
    double closestBoundedT(const SkDPoint& pt) const;
    bool contains(double t) const;

    void debugInit(const SkTCurve& curve, SkArenaAlloc& heap) {
#ifdef SK_DEBUG
        SkTCurve* fake = curve.make(heap);
        fake->debugInit();
        init(*fake);
        initBounds(*fake);
        fCoinStart.init();
        fCoinEnd.init();
#endif
    }

    const SkTSect* debugOpp() const;

#ifdef SK_DEBUG
    void debugSetGlobalState(SkOpGlobalState* state) {
        fDebugGlobalState = state;
    }

    const SkTSpan* debugSpan(int ) const;
    const SkTSpan* debugT(double t) const;
    bool debugIsBefore(const SkTSpan* span) const;
#endif
    void dump() const;
    void dumpAll() const;
    void dumpBounded(int id) const;
    void dumpBounds() const;
    void dumpCoin() const;

    double endT() const {
        return fEndT;
    }

    SkTSpan* findOppSpan(const SkTSpan* opp) const;

    SkTSpan* findOppT(double t) const {
        SkTSpan* result = oppT(t);
        SkOPASSERT(result);
        return result;
    }

    SkDEBUGCODE(SkOpGlobalState* globalState() const { return fDebugGlobalState; })

    bool hasOppT(double t) const {
        return SkToBool(oppT(t));
    }

    int hullsIntersect(SkTSpan* span, bool* start, bool* oppStart);
    void init(const SkTCurve& );
    bool initBounds(const SkTCurve& );

    bool isBounded() const {
        return fBounded != nullptr;
    }

    bool linearsIntersect(SkTSpan* span);
    double linearT(const SkDPoint& ) const;

    void markCoincident() {
        fCoinStart.markCoincident();
        fCoinEnd.markCoincident();
    }

    const SkTSpan* next() const {
        return fNext;
    }

    bool onlyEndPointsInCommon(const SkTSpan* opp, bool* start,
            bool* oppStart, bool* ptsInCommon);

    const SkTCurve& part() const {
        return *fPart;
    }

    int pointCount() const {
        return fPart->pointCount();
    }

    const SkDPoint& pointFirst() const {
        return (*fPart)[0];
    }

    const SkDPoint& pointLast() const {
        return (*fPart)[fPart->pointLast()];
    }

    bool removeAllBounded();
    bool removeBounded(const SkTSpan* opp);

    void reset() {
        fBounded = nullptr;
    }

    void resetBounds(const SkTCurve& curve) {
        fIsLinear = fIsLine = false;
        initBounds(curve);
    }

    bool split(SkTSpan* work, SkArenaAlloc* heap) {
        return splitAt(work, (work->fStartT + work->fEndT) * 0.5, heap);
    }

    bool splitAt(SkTSpan* work, double t, SkArenaAlloc* heap);

    double startT() const {
        return fStartT;
    }

private:

    // implementation is for testing only
    int debugID() const {
        return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1);
    }

    void dumpID() const;

    int hullCheck(const SkTSpan* opp, bool* start, bool* oppStart);
    int linearIntersects(const SkTCurve& ) const;
    SkTSpan* oppT(double t) const;

    void validate() const;
    void validateBounded() const;
    void validatePerpT(double oppT) const;
    void validatePerpPt(double t, const SkDPoint& ) const;

    SkTCurve* fPart;
    SkTCoincident fCoinStart;
    SkTCoincident fCoinEnd;
    SkTSpanBounded* fBounded;
    SkTSpan* fPrev;
    SkTSpan* fNext;
    SkDRect fBounds;
    double fStartT;
    double fEndT;
    double fBoundsMax;
    SkOpDebugBool fCollapsed;
    SkOpDebugBool fHasPerp;
    SkOpDebugBool fIsLinear;
    SkOpDebugBool fIsLine;
    SkOpDebugBool fDeleted;
    SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState);
    SkDEBUGCODE(SkTSect* fDebugSect);
    PATH_OPS_DEBUG_T_SECT_CODE(int fID);
    friend class SkTSect;
};

class SkTSect {
public:
    SkTSect(const SkTCurve& c
                             SkDEBUGPARAMS(SkOpGlobalState* ) PATH_OPS_DEBUG_T_SECT_PARAMS(int id));
    static void BinarySearch(SkTSect* sect1, SkTSect* sect2,
            SkIntersections* intersections);

    SkDEBUGCODE(SkOpGlobalState* globalState() { return fDebugGlobalState; })
    bool hasBounded(const SkTSpan* ) const;

    const SkTSect* debugOpp() const {
        return SkDEBUGRELEASE(fOppSect, nullptr);
    }

#ifdef SK_DEBUG
    const SkTSpan* debugSpan(int id) const;
    const SkTSpan* debugT(double t) const;
#endif
    void dump() const;
    void dumpBoth(SkTSect* ) const;
    void dumpBounded(int id) const;
    void dumpBounds() const;
    void dumpCoin() const;
    void dumpCoinCurves() const;
    void dumpCurves() const;

private:
    enum {
        kZeroS1Set = 1,
        kOneS1Set = 2,
        kZeroS2Set = 4,
        kOneS2Set = 8
    };

    SkTSpan* addFollowing(SkTSpan* prior);
    void addForPerp(SkTSpan* span, double t);
    SkTSpan* addOne();

    SkTSpan* addSplitAt(SkTSpan* span, double t) {
        SkTSpan* result = this->addOne();
        SkDEBUGCODE(result->debugSetGlobalState(this->globalState()));
        result->splitAt(span, t, &fHeap);
        result->initBounds(fCurve);
        span->initBounds(fCurve);
        return result;
    }

    bool binarySearchCoin(SkTSect* , double tStart, double tStep, double* t,
                          double* oppT, SkTSpan** oppFirst);
    SkTSpan* boundsMax();
    bool coincidentCheck(SkTSect* sect2);
    void coincidentForce(SkTSect* sect2, double start1s, double start1e);
    bool coincidentHasT(double t);
    int collapsed() const;
    void computePerpendiculars(SkTSect* sect2, SkTSpan* first,
                               SkTSpan* last);
    int countConsecutiveSpans(SkTSpan* first,
                              SkTSpan** last) const;

    int debugID() const {
        return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1);
    }

    bool deleteEmptySpans();
    void dumpCommon(const SkTSpan* ) const;
    void dumpCommonCurves(const SkTSpan* ) const;
    static int EndsEqual(const SkTSect* sect1, const SkTSect* sect2,
                         SkIntersections* );
    bool extractCoincident(SkTSect* sect2, SkTSpan* first,
                           SkTSpan* last, SkTSpan** result);
    SkTSpan* findCoincidentRun(SkTSpan* first, SkTSpan** lastPtr);
    int intersects(SkTSpan* span, SkTSect* opp,
                   SkTSpan* oppSpan, int* oppResult);
    bool isParallel(const SkDLine& thisLine, const SkTSect* opp) const;
    int linesIntersect(SkTSpan* span, SkTSect* opp,
                       SkTSpan* oppSpan, SkIntersections* );
    bool markSpanGone(SkTSpan* span);
    bool matchedDirection(double t, const SkTSect* sect2, double t2) const;
    void matchedDirCheck(double t, const SkTSect* sect2, double t2,
                         bool* calcMatched, bool* oppMatched) const;
    void mergeCoincidence(SkTSect* sect2);

    const SkDPoint& pointLast() const {
        return fCurve[fCurve.pointLast()];
    }

    SkTSpan* prev(SkTSpan* ) const;
    bool removeByPerpendicular(SkTSect* opp);
    void recoverCollapsed();
    bool removeCoincident(SkTSpan* span, bool isBetween);
    void removeAllBut(const SkTSpan* keep, SkTSpan* span,
                      SkTSect* opp);
    bool removeSpan(SkTSpan* span);
    void removeSpanRange(SkTSpan* first, SkTSpan* last);
    bool removeSpans(SkTSpan* span, SkTSect* opp);
    void removedEndCheck(SkTSpan* span);

    void resetRemovedEnds() {
        fRemovedStartT = fRemovedEndT = false;
    }

    SkTSpan* spanAtT(double t, SkTSpan** priorSpan);
    SkTSpan* tail();
    bool trim(SkTSpan* span, SkTSect* opp);
    bool unlinkSpan(SkTSpan* span);
    bool updateBounded(SkTSpan* first, SkTSpan* last,
                       SkTSpan* oppFirst);
    void validate() const;
    void validateBounded() const;

    const SkTCurve& fCurve;
    SkSTArenaAlloc<1024> fHeap;
    SkTSpan* fHead;
    SkTSpan* fCoincident;
    SkTSpan* fDeleted;
    int fActiveCount;
    bool fRemovedStartT;
    bool fRemovedEndT;
    bool fHung;
    SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState);
    SkDEBUGCODE(SkTSect* fOppSect);
    PATH_OPS_DEBUG_T_SECT_CODE(int fID);
    PATH_OPS_DEBUG_T_SECT_CODE(int fDebugCount);
#if DEBUG_T_SECT
    int fDebugAllocatedCount;
#endif
    friend class SkTSpan;
};

#endif
