/*
 * 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 "SkTArray.h"

#if defined(SK_DEBUG) || !FORCE_RELEASE
#include "SkThread.h"
#endif

class SkIntersections;
class SkOpContour;
class SkPathWriter;

struct SkCoincidence {
    SkOpContour* fOther;
    int fSegments[2];
    double fTs[2][2];
    SkPoint fPts[2][2];
    int fNearly[2];
};

class SkOpContour {
public:
    SkOpContour() {
        reset();
#if defined(SK_DEBUG) || !FORCE_RELEASE
        fID = sk_atomic_inc(&SkPathOpsDebug::gContourID);
#endif
    }

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

    bool addCoincident(int index, SkOpContour* other, int otherIndex,
                       const SkIntersections& ts, bool swap);
    void addCoincidentPoints();

    void addCross(const SkOpContour* crosser) {
#ifdef DEBUG_CROSS
        for (int index = 0; index < fCrosses.count(); ++index) {
            SkASSERT(fCrosses[index] != crosser);
        }
#endif
        fCrosses.push_back(crosser);
    }

    void addCubic(const SkPoint pts[4]) {
        fSegments.push_back().addCubic(pts, fOperand, fXor);
        fContainsCurves = fContainsCubics = true;
    }

    int addLine(const SkPoint pts[2]) {
        fSegments.push_back().addLine(pts, fOperand, fXor);
        return fSegments.count();
    }

    void addOtherT(int segIndex, int tIndex, double otherT, int otherIndex) {
        fSegments[segIndex].addOtherT(tIndex, otherT, otherIndex);
    }

    bool addPartialCoincident(int index, SkOpContour* other, int otherIndex,
                       const SkIntersections& ts, int ptIndex, bool swap);

    int addQuad(const SkPoint pts[3]) {
        fSegments.push_back().addQuad(pts, fOperand, fXor);
        fContainsCurves = true;
        return fSegments.count();
    }

    int addT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) {
        setContainsIntercepts();
        return fSegments[segIndex].addT(&other->fSegments[otherIndex], pt, newT);
    }

    int addSelfT(int segIndex, const SkPoint& pt, double newT) {
        setContainsIntercepts();
        return fSegments[segIndex].addSelfT(pt, newT);
    }

    void align(const SkOpSegment::AlignedSpan& aligned, bool swap, SkCoincidence* coincidence);
    void alignCoincidence(const SkOpSegment::AlignedSpan& aligned,
            SkTArray<SkCoincidence, true>* coincidences);

    void alignCoincidence(const SkOpSegment::AlignedSpan& aligned) {
        alignCoincidence(aligned, &fCoincidences);
        alignCoincidence(aligned, &fPartialCoincidences);
    }

    void alignMultiples(SkTDArray<SkOpSegment::AlignedSpan>* aligned) {
        int segmentCount = fSegments.count();
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            SkOpSegment& segment = fSegments[sIndex];
            if (segment.hasMultiples()) {
                segment.alignMultiples(aligned);
            }
        }
    }

    void alignTPt(int segmentIndex, const SkOpContour* other, int otherIndex,
                  bool swap, int tIndex, SkIntersections* ts, SkPoint* point) const;

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

    bool calcAngles();
    void calcCoincidentWinding();
    void calcPartialCoincidentWinding();

    void checkDuplicates() {
        int segmentCount = fSegments.count();
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            SkOpSegment& segment = fSegments[sIndex];
            if (segment.count() > 2) {
                segment.checkDuplicates();
            }
        }
    }

    void checkEnds() {
        if (!fContainsCurves) {
            return;
        }
        int segmentCount = fSegments.count();
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            SkOpSegment* segment = &fSegments[sIndex];
            if (segment->verb() == SkPath::kLine_Verb) {
                continue;
            }
            if (segment->done()) {
                continue;   // likely coincident, nothing to do
            }
            segment->checkEnds();
        }
    }

    void checkMultiples() {
        int segmentCount = fSegments.count();
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            SkOpSegment& segment = fSegments[sIndex];
            if (segment.count() > 2) {
                segment.checkMultiples();
                fMultiples |= segment.hasMultiples();
            }
        }
    }

    void checkSmall() {
        int segmentCount = fSegments.count();
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            SkOpSegment& segment = fSegments[sIndex];
            // OPTIMIZATION : skip segments that are done?
            if (segment.hasSmall()) {
                segment.checkSmall();
            }
        }
    }

    // if same point has different T values, choose a common T
    void checkTiny() {
        int segmentCount = fSegments.count();
        if (segmentCount <= 2) {
            return;
        }
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            SkOpSegment& segment = fSegments[sIndex];
            if (segment.hasTiny()) {
                segment.checkTiny();
            }
        }
    }

    void complete() {
        setBounds();
        fContainsIntercepts = false;
    }

    bool containsCubics() const {
        return fContainsCubics;
    }

    bool crosses(const SkOpContour* crosser) const {
        for (int index = 0; index < fCrosses.count(); ++index) {
            if (fCrosses[index] == crosser) {
                return true;
            }
        }
        return false;
    }

    bool done() const {
        return fDone;
    }

    const SkPoint& end() const {
        const SkOpSegment& segment = fSegments.back();
        return segment.pts()[SkPathOpsVerbToPoints(segment.verb())];
    }

    void fixOtherTIndex() {
        int segmentCount = fSegments.count();
        for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
            fSegments[sIndex].fixOtherTIndex();
        }
    }

    bool hasMultiples() const {
        return fMultiples;
    }

    void joinCoincidence() {
        joinCoincidence(fCoincidences, false);
        joinCoincidence(fPartialCoincidences, true);
    }

    SkOpSegment* nonVerticalSegment(int* start, int* end);

    bool operand() const {
        return fOperand;
    }

    void reset() {
        fSegments.reset();
        fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
        fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = fMultiples = false;
    }

    void resolveNearCoincidence();

    SkTArray<SkOpSegment>& segments() {
        return fSegments;
    }

    void setContainsIntercepts() {
        fContainsIntercepts = true;
    }

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

    void setOppXor(bool isOppXor) {
        fOppXor = isOppXor;
        int segmentCount = fSegments.count();
        for (int test = 0; test < segmentCount; ++test) {
            fSegments[test].setOppXor(isOppXor);
        }
    }

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

    void sortAngles();
    void sortSegments();

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

    void toPath(SkPathWriter* path) const;

    void toPartialBackward(SkPathWriter* path) const {
        int segmentCount = fSegments.count();
        for (int test = segmentCount - 1; test >= 0; --test) {
            fSegments[test].addCurveTo(1, 0, path, true);
        }
    }

    void toPartialForward(SkPathWriter* path) const {
        int segmentCount = fSegments.count();
        for (int test = 0; test < segmentCount; ++test) {
            fSegments[test].addCurveTo(0, 1, path, true);
        }
    }

    void topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, SkOpSegment** topStart);
    SkOpSegment* undoneSegment(int* start, int* end);

    int updateSegment(int index, const SkPoint* pts) {
        SkOpSegment& segment = fSegments[index];
        segment.updatePts(pts);
        return SkPathOpsVerbToPoints(segment.verb()) + 1;
    }

#if DEBUG_TEST
    SkTArray<SkOpSegment>& debugSegments() {
        return fSegments;
    }
#endif

#if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY
    void debugShowActiveSpans() {
        for (int index = 0; index < fSegments.count(); ++index) {
            fSegments[index].debugShowActiveSpans();
        }
    }
#endif

#if DEBUG_SHOW_WINDING
    int debugShowWindingValues(int totalSegments, int ofInterest);
    static void debugShowWindingValues(const SkTArray<SkOpContour*, true>& contourList);
#endif

    // available to test routines only
    void dump() const;
    void dumpAngles() const;
    void dumpCoincidence(const SkCoincidence& ) const;
    void dumpCoincidences() const;
    void dumpPt(int ) const;
    void dumpPts() const;
    void dumpSpan(int ) const;
    void dumpSpans() const;

private:
    void alignPt(int index, SkPoint* point, int zeroPt) const;
    int alignT(bool swap, int tIndex, SkIntersections* ts) const;
    void calcCommonCoincidentWinding(const SkCoincidence& );
    void checkCoincidentPair(const SkCoincidence& oneCoin, int oneIdx,
                             const SkCoincidence& twoCoin, int twoIdx, bool partial);
    void joinCoincidence(const SkTArray<SkCoincidence, true>& , bool partial);
    void setBounds();

    SkTArray<SkOpSegment> fSegments;
    SkTArray<SkOpSegment*, true> fSortedSegments;
    int fFirstSorted;
    SkTArray<SkCoincidence, true> fCoincidences;
    SkTArray<SkCoincidence, true> fPartialCoincidences;
    SkTArray<const SkOpContour*, true> fCrosses;
    SkPathOpsBounds fBounds;
    bool fContainsIntercepts;  // FIXME: is this used by anybody?
    bool fContainsCubics;
    bool fContainsCurves;
    bool fDone;
    bool fMultiples;  // set if some segment has multiple identical intersections with other curves
    bool fOperand;  // true for the second argument to a binary operator
    bool fXor;
    bool fOppXor;
#if defined(SK_DEBUG) || !FORCE_RELEASE
    int debugID() const { return fID; }
    int fID;
#else
    int debugID() const { return -1; }
#endif
};

#endif