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

#include "SkOpSegment.h"
#include "SkTDArray.h"
#include "SkTSort.h"

class SkChunkAlloc;
enum class SkOpRayDir;
struct SkOpRayHit;
class SkPathWriter;

class SkOpContour {
public:
    SkOpContour() {
        reset();
    }

    ~SkOpContour() {
        if (fNext) {
            fNext->~SkOpContour();
        }
    }

    bool operator<(const SkOpContour& rh) const {
        return fBounds.fTop == rh.fBounds.fTop
                ? fBounds.fLeft < rh.fBounds.fLeft
                : fBounds.fTop < rh.fBounds.fTop;
    }

    void addAlignIntersections(SkOpContourHead* contourList, SkChunkAlloc* allocator) {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->addAlignIntersections(contourList, allocator);
        } while ((segment = segment->next()));
    }

    void addConic(SkPoint pts[3], SkScalar weight, SkChunkAlloc* allocator) {
        appendSegment(allocator).addConic(pts, weight, this);
    }

    void addCubic(SkPoint pts[4], SkChunkAlloc* allocator) {
        appendSegment(allocator).addCubic(pts, this);
    }

    SkOpSegment* addCurve(SkPath::Verb verb, const SkPoint pts[4], SkChunkAlloc* allocator);

    void addLine(SkPoint pts[2], SkChunkAlloc* allocator) {
        appendSegment(allocator).addLine(pts, this);
    }

    void addQuad(SkPoint pts[3], SkChunkAlloc* allocator) {
        appendSegment(allocator).addQuad(pts, this);
    }

    void align() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->align();
        } while ((segment = segment->next()));
    }

    SkOpSegment& appendSegment(SkChunkAlloc* allocator) {
        SkOpSegment* result = fCount++
                ? SkOpTAllocator<SkOpSegment>::Allocate(allocator) : &fHead;
        result->setPrev(fTail);
        if (fTail) {
            fTail->setNext(result);
        }
        fTail = result;
        return *result;
    }

    SkOpContour* appendContour(SkChunkAlloc* allocator) {
        SkOpContour* contour = SkOpTAllocator<SkOpContour>::New(allocator);
        contour->setNext(nullptr);
        SkOpContour* prev = this;
        SkOpContour* next;
        while ((next = prev->next())) {
            prev = next;
        }
        prev->setNext(contour);
        return contour;
    }
    
    const SkPathOpsBounds& bounds() const {
        return fBounds;
    }

    void calcAngles(SkChunkAlloc* allocator) {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->calcAngles(allocator);
        } while ((segment = segment->next()));
    }

    void complete() {
        setBounds();
    }

    int count() const {
        return fCount;
    }

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

    int debugIndent() const {
        return SkDEBUGRELEASE(fDebugIndent, 0);
    }

#if DEBUG_ACTIVE_SPANS
    void debugShowActiveSpans() {
        SkOpSegment* segment = &fHead;
        do {
            segment->debugShowActiveSpans();
        } while ((segment = segment->next()));
    }
#endif

    const SkOpAngle* debugAngle(int id) const {
        return SkDEBUGRELEASE(this->globalState()->debugAngle(id), nullptr);
    }

    void debugCheckHealth(const char* id, SkPathOpsDebug::GlitchLog* ) const;

    SkOpContour* debugContour(int id) {
        return SkDEBUGRELEASE(this->globalState()->debugContour(id), nullptr);
    }

    void debugMissingCoincidence(const char* id, SkPathOpsDebug::GlitchLog* log,
                                 const SkOpCoincidence* coincidence) const;

    const SkOpPtT* debugPtT(int id) const {
        return SkDEBUGRELEASE(this->globalState()->debugPtT(id), nullptr);
    }

    const SkOpSegment* debugSegment(int id) const {
        return SkDEBUGRELEASE(this->globalState()->debugSegment(id), nullptr);
    }

    const SkOpSpanBase* debugSpan(int id) const {
        return SkDEBUGRELEASE(this->globalState()->debugSpan(id), nullptr);
    }

    SkOpGlobalState* globalState() const {
        return fState; 
    }

    void debugValidate() const {
#if DEBUG_VALIDATE
        const SkOpSegment* segment = &fHead;
        const SkOpSegment* prior = nullptr;
        do {
            segment->debugValidate();
            SkASSERT(segment->prev() == prior);
            prior = segment;
        } while ((segment = segment->next()));
        SkASSERT(prior == fTail);
#endif
    }

    bool done() const {
        return fDone;
    }

    void dump() const;
    void dumpAll() const;
    void dumpAngles() const;
    void dumpContours() const;
    void dumpContoursAll() const;
    void dumpContoursAngles() const;
    void dumpContoursPts() const;
    void dumpContoursPt(int segmentID) const;
    void dumpContoursSegment(int segmentID) const;
    void dumpContoursSpan(int segmentID) const;
    void dumpContoursSpans() const;
    void dumpPt(int ) const;
    void dumpPts(const char* prefix = "seg") const;
    void dumpPtsX(const char* prefix) const;
    void dumpSegment(int ) const;
    void dumpSegments(const char* prefix = "seg", SkPathOp op = (SkPathOp) -1) const;
    void dumpSpan(int ) const;
    void dumpSpans() const;

    const SkPoint& end() const {
        return fTail->pts()[SkPathOpsVerbToPoints(fTail->verb())];
    }

    bool findCollapsed() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->findCollapsed();
        } while ((segment = segment->next()));
        return true;
    }

    SkOpSpan* findSortableTop(SkOpContour* );

    SkOpSegment* first() {
        SkASSERT(fCount > 0);
        return &fHead;
    }

    const SkOpSegment* first() const {
        SkASSERT(fCount > 0);
        return &fHead;
    }

    void indentDump() const {
        SkDEBUGCODE(fDebugIndent += 2);
    }

    void init(SkOpGlobalState* globalState, bool operand, bool isXor) {
        fState = globalState;
        fOperand = operand;
        fXor = isXor;
        SkDEBUGCODE(fID = globalState->nextContourID());
    }

    int isCcw() const {
        return fCcw;
    }

    bool isXor() const {
        return fXor;
    }

    void markDone() {
        SkOpSegment* segment = &fHead;
        do {
            segment->markAllDone();
        } while ((segment = segment->next()));
    }

    bool missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        bool result = false;
        do {
            if (fState->angleCoincidence()) {
#if DEBUG_ANGLE
                segment->debugCheckAngleCoin();
#endif
            } else if (segment->missingCoincidence(coincidences, allocator)) {
                result = true;
    // FIXME: trying again loops forever in issue3651_6
    // The continue below is speculative -- once there's an actual case that requires it,
    // add the plumbing necessary to look for another missing coincidence in the same segment
         //       continue; // try again in case another missing coincidence is further along
            }
            segment = segment->next();
        } while (segment);
        return result;
    }

    bool moveMultiples() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            if (!segment->moveMultiples()) {
                return false;
            }
        } while ((segment = segment->next()));
        return true;
    }

    void moveNearby() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->moveNearby();
        } while ((segment = segment->next()));
    }

    SkOpContour* next() {
        return fNext;
    }

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

    bool operand() const {
        return fOperand;
    }

    bool oppXor() const {
        return fOppXor;
    }

    void outdentDump() const {
        SkDEBUGCODE(fDebugIndent -= 2);
    }

    void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkChunkAlloc* );

    void remove(SkOpContour* contour) {
        if (contour == this) {
            SkASSERT(fCount == 0);
            return;
        }
        SkASSERT(contour->fNext == nullptr);
        SkOpContour* prev = this;
        SkOpContour* next;
        while ((next = prev->next()) != contour) {
            SkASSERT(next);
            prev = next;
        }
        SkASSERT(prev);
        prev->setNext(nullptr);
    }

    void reset() {
        fTail = nullptr;
        fNext = nullptr;
        fCount = 0;
        fDone = false;
        SkDEBUGCODE(fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin));
        SkDEBUGCODE(fFirstSorted = -1);
        SkDEBUGCODE(fDebugIndent = 0);
    }

    void resetReverse() {
        SkOpContour* next = this;
        do {
            next->fCcw = -1;
            next->fReverse = false;
        } while ((next = next->next()));
    }

    bool reversed() const {
        return fReverse;
    }

    void setBounds() {
        SkASSERT(fCount > 0);
        const SkOpSegment* segment = &fHead;
        fBounds = segment->bounds();
        while ((segment = segment->next())) {
            fBounds.add(segment->bounds());
        }
    }

    void setCcw(int ccw) {
        fCcw = ccw;
    }

    void setGlobalState(SkOpGlobalState* state) {
        fState = state;
    }

    void setNext(SkOpContour* contour) {
//        SkASSERT(!fNext == !!contour);
        fNext = contour;
    }

    void setOperand(bool isOp) {
        fOperand = isOp;
    }

    void setOppXor(bool isOppXor) {
        fOppXor = isOppXor;
    }

    void setReverse() {
        fReverse = true;
    }

    void setXor(bool isXor) {
        fXor = isXor;
    }

    SkPath::Verb simplifyCubic(SkPoint pts[4]);

    void sortAngles() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->sortAngles();
        } while ((segment = segment->next()));
    }

    const SkPoint& start() const {
        return fHead.pts()[0];
    }

    void toPartialBackward(SkPathWriter* path) const {
        const SkOpSegment* segment = fTail;
        do {
            SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path));
        } while ((segment = segment->prev()));
    }

    void toPartialForward(SkPathWriter* path) const {
        const SkOpSegment* segment = &fHead;
        do {
            SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path));
        } while ((segment = segment->next()));
    }

    void toReversePath(SkPathWriter* path) const;
    void toPath(SkPathWriter* path) const;
    SkOpSegment* undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr);

private:
    SkOpGlobalState* fState;
    SkOpSegment fHead;
    SkOpSegment* fTail;
    SkOpContour* fNext;
    SkPathOpsBounds fBounds;
    int fCcw;
    int fCount;
    int fFirstSorted;
    bool fDone;  // set by find top segment
    bool fOperand;  // true for the second argument to a binary operator
    bool fReverse;  // true if contour should be reverse written to path (used only by fix winding)
    bool fXor;  // set if original path had even-odd fill
    bool fOppXor;  // set if opposite path had even-odd fill
    SkDEBUGCODE(int fID);
    SkDEBUGCODE(mutable int fDebugIndent);
};

class SkOpContourHead : public SkOpContour {
};

#endif