/*
 * 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"

enum class SkOpRayDir;
struct SkOpRayHit;
class SkPathWriter;

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

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

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

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

    SkOpSegment* addLine(SkPoint pts[2]) {
        SkASSERT(pts[0] != pts[1]);
        return appendSegment().addLine(pts, this);
    }

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

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

    const SkPathOpsBounds& bounds() const {
        return fBounds;
    }

    void calcAngles() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        do {
            segment->calcAngles();
        } 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);
    }


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

    const SkOpCoincidence* debugCoincidence() const {
        return this->globalState()->coincidence();
    }

#if DEBUG_COIN
    void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const;
#endif

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

#if DEBUG_COIN
    void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const;
    void debugMoveMultiples(SkPathOpsDebug::GlitchLog* ) const;
    void debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const;
#endif

    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);
    }

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

    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())];
    }

    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 joinSegments() {
        SkOpSegment* segment = &fHead;
        SkOpSegment* next;
        do {
            next = segment->next();
            segment->joinEnds(next ? next : &fHead);
        } while ((segment = next));
    }

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

    // Please keep this aligned with debugMissingCoincidence()
    bool missingCoincidence() {
        SkASSERT(fCount > 0);
        SkOpSegment* segment = &fHead;
        bool result = false;
        do {
            if (segment->missingCoincidence()) {
                result = true;
            }
            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;
    }

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

    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, SkArenaAlloc*);

    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 {
            if (!next->count()) {
                continue;
            }
            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;
    }

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

    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;
    SkOpSpan* undoneSpan();

protected:
    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 {
public:
    SkOpContour* appendContour() {
        SkOpContour* contour = SkOpTAllocator<SkOpContour>::New(this->globalState()->allocator());
        contour->setNext(nullptr);
        SkOpContour* prev = this;
        SkOpContour* next;
        while ((next = prev->next())) {
            prev = next;
        }
        prev->setNext(contour);
        return contour;
    }

    void joinAllSegments() {
        SkOpContour* next = this;
        do {
            if (!next->count()) {
                continue;
            }
            next->joinSegments();
        } while ((next = next->next()));
    }

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

};

class SkOpContourBuilder {
public:
    SkOpContourBuilder(SkOpContour* contour)
        : fContour(contour)
        , fLastIsLine(false) {
    }

    void addConic(SkPoint pts[3], SkScalar weight);
    void addCubic(SkPoint pts[4]);
    void addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight = 1);
    void addLine(const SkPoint pts[2]);
    void addQuad(SkPoint pts[3]);
    void flush();
    SkOpContour* contour() { return fContour; }
    void setContour(SkOpContour* contour) { flush(); fContour = contour; }
protected:
    SkOpContour* fContour;
    SkPoint fLastLine[2];
    bool fLastIsLine;
};

#endif
