/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "SkRegion.h"
#include "SkChunkAlloc.h"
#include "SkTDArray.h"
#include "SkTemplates.h"

#if 0

struct VEdge {
    VEdge*  fPrev;
    VEdge*  fNext;
    
    SkRegion::RunType   fX;
    SkRegion::RunType   fTop;
    SkRegion::RunType   fBottom;
    int                 fWinding;
    
    void removeFromList() {
        fPrev->fNext = fNext;
        fNext->fPrev = fPrev;
    }

    void backwardsInsert() {
        while (fPrev->fX > fX) {
            VEdge* prev = fPrev;
            VEdge* next = this;

            // remove prev from the list
            prev->fPrev->fNext = next;
            next->fPrev = prev->fPrev;
            
            // insert prev after next
            prev->fNext = next->fNext;
            next->fNext->fPrev = prev;
            next->fNext = prev;
            prev->fPrev = next;
        }
    }

    static void SetFromRect(VEdge edges[], const SkIRect& r) {
        edges[0].fX = r.fLeft;
        edges[0].fTop = r.fTop;
        edges[0].fBottom = r.fBottom;
        edges[0].fWinding = -1;
        
        edges[1].fX = r.fRight;
        edges[1].fTop = r.fTop;
        edges[1].fBottom = r.fBottom;
        edges[1].fWinding = 1;
    }
};

class Accumulator {
public:
    Accumulator(SkRegion::RunType top, int numRects);
    ~Accumulator() {}
    
    SkRegion::RunType append(SkRegion::RunType top, const VEdge* edge);
    
    int count() const { return fTotalCount; }
    void copyTo(SkRegion::RunType dst[]);
    
private:
    struct Row {
        SkRegion::RunType*  fPtr;
        SkRegion::RunType   fBottom;
        int                 fCount; // just [L R] count
    };
    SkChunkAlloc    fAlloc;
    SkTDArray<Row>  fRows;
    SkRegion::RunType fTop;
    int             fTotalCount;
    int             fRectCount;
};

Accumulator::Accumulator(SkRegion::RunType top, int numRects)
        : fAlloc((1 + numRects * 2 + 1) * sizeof(int32_t)) {
    fRectCount = numRects;
    fTop = top;
    fTotalCount = 2; // Top + final sentinel
}

//#define TRACE_ROW(code) code
#define TRACE_ROW(code)

SkRegion::RunType Accumulator::append(SkRegion::RunType currY, const VEdge* edge) {
    // worst-case size
    size_t size = fRectCount * 2 * sizeof(SkRegion::RunType);
    SkRegion::RunType* row = (SkRegion::RunType*)fAlloc.allocThrow(size);
    SkRegion::RunType* rowHead = row;
    
    SkRegion::RunType nextY = SkRegion::kRunTypeSentinel;
    int winding = edge->fWinding;

    // record the L R values for this row

    if (edge->fTop > currY) {
        nextY = SkMin32(nextY, edge->fTop);
        TRACE_ROW(SkDebugf("Y %d\n", currY);)
    } else {
        SkRegion::RunType currR;
        *row++ = edge->fX;
        TRACE_ROW(SkDebugf("Y %d [%d", currY, edge->fX);)
        edge = edge->fNext;
        for (;;) {
            if (edge->fTop > currY) {
                nextY = SkMin32(nextY, edge->fTop);
                break;
            }

            int prevWinding = winding;
            winding += edge->fWinding;
            if (0 == winding) { // we finished an interval
                currR = edge->fX;
            } else if (0 == prevWinding && edge->fX > currR) {
                *row++ = currR;
                *row++ = edge->fX;
                TRACE_ROW(SkDebugf(" %d] [%d", currR, edge->fX);)
            }
            
            nextY = SkMin32(nextY, edge->fBottom);
            edge = edge->fNext;
        }
        SkASSERT(0 == winding);
        *row++ = currR;
        TRACE_ROW(SkDebugf(" %d]\n", currR);)
    }
    int rowCount = row - rowHead;
    
    // now see if we have already seen this row, or if its unique

    Row* r = fRows.count() ? &fRows[fRows.count() - 1] : NULL;
    if (r && (r->fCount == rowCount) &&
        !memcmp(r->fPtr, rowHead,
                rowCount * sizeof(SkRegion::RunType))) {
            r->fBottom = nextY;    // update bottom
            fAlloc.unalloc(rowHead);
        } else {
            Row* r = fRows.append();
            r->fPtr = rowHead;
            r->fBottom = nextY;
            r->fCount = rowCount;
            fTotalCount += 1 + rowCount + 1;
        }
    
    return nextY;
}

void Accumulator::copyTo(SkRegion::RunType dst[]) {
    SkDEBUGCODE(SkRegion::RunType* startDst = dst;)
    
    *dst++ = fTop;
    
    const Row* curr = fRows.begin();
    const Row* stop = fRows.end();
    while (curr < stop) {
        *dst++ = curr->fBottom;
        memcpy(dst, curr->fPtr, curr->fCount * sizeof(SkRegion::RunType));
        dst += curr->fCount;
        *dst++ = SkRegion::kRunTypeSentinel;
        curr += 1;
    }
    *dst++ = SkRegion::kRunTypeSentinel;
    SkASSERT(dst - startDst == fTotalCount);
}

///////////////////////////////////////////////////////////////////////////////

template <typename T> int SkTCmp2Int(const T& a, const T& b) {
    return (a < b) ? -1 : ((b < a) ? 1 : 0);
}

static inline int SkCmp32(int32_t a, int32_t b) {
    return (a < b) ? -1 : ((b < a) ? 1 : 0);
}

static int compare_edgeptr(const void* p0, const void* p1) {
    const VEdge* e0 = *static_cast<VEdge*const*>(p0);
    const VEdge* e1 = *static_cast<VEdge*const*>(p1);

    SkRegion::RunType v0 = e0->fTop;
    SkRegion::RunType v1 = e1->fTop;

    if (v0 == v1) {
        v0 = e0->fX;
        v1 = e1->fX;
    }
    return SkCmp32(v0, v1);
}

// fillout edge[] from rects[], sorted. Return the head, and set the tail
//
static VEdge* sort_edges(VEdge** edgePtr, VEdge edge[], const SkIRect rects[],
                         int rectCount, VEdge** edgeTail) {
    int i;
    VEdge** ptr = edgePtr;
    for (int i = 0; i < rectCount; i++) {
        if (!rects[i].isEmpty()) {
            VEdge::SetFromRect(edge, rects[i]);
            *ptr++ = edge++;
            *ptr++ = edge++;
        }
    }
    
    int edgeCount = ptr - edgePtr;
    if (0 == edgeCount) {
        // all the rects[] were empty
        return NULL;
    }

    qsort(edgePtr, edgeCount, sizeof(*edgePtr), compare_edgeptr);
    for (i = 1; i < edgeCount; i++) {
        edgePtr[i - 1]->fNext = edgePtr[i];
        edgePtr[i]->fPrev = edgePtr[i - 1];
    }
    *edgeTail = edgePtr[edgeCount - 1];
    return edgePtr[0];
}

bool SkRegion::setRects(const SkIRect rects[], int rectCount) {
    if (0 == rectCount) {
        return this->setEmpty();
    }
    if (1 == rectCount) {
        return this->setRect(rects[0]);
    }

    int edgeCount = rectCount * 2;
    SkAutoMalloc memory((sizeof(VEdge) + sizeof(VEdge*)) * edgeCount);
    VEdge** edgePtr = (VEdge**)memory.get();
    VEdge* tail, *head = (VEdge*)(edgePtr + edgeCount);
    head = sort_edges(edgePtr, head, rects, rectCount, &tail);
    // check if we have no edges
    if (NULL == head) {
        return this->setEmpty();
    }

    // at this stage, we don't really care about edgeCount, or if rectCount is
    // larger that it should be (since sort_edges might have skipped some
    // empty rects[]). rectCount now is just used for worst-case allocations

    VEdge headEdge, tailEdge;
    headEdge.fPrev = NULL;
    headEdge.fNext = head;
    headEdge.fTop = SK_MinS32;
    headEdge.fX = SK_MinS32;
    head->fPrev = &headEdge;
    
    tailEdge.fPrev = tail;
    tailEdge.fNext = NULL;
    tailEdge.fTop = SK_MaxS32;
    tail->fNext = &tailEdge;

    int32_t currY = head->fTop;
    Accumulator accum(currY, rectCount);
    
    while (head->fNext) {
        VEdge* edge = head;
        // accumulate the current
        SkRegion::RunType nextY = accum.append(currY, edge);
        // remove the old
        while (edge->fTop <= currY) {
            VEdge* next = edge->fNext;
            if (edge->fBottom <= nextY) {
                edge->removeFromList();
            }
            edge = next;
        }
        // insert (sorted) the new
        while (edge->fTop == nextY) {
            VEdge* next = edge->fNext;
            edge->backwardsInsert();
            edge = next;
        }
        currY = nextY;
        head = headEdge.fNext;
    }

    SkAutoTArray<RunType> runs(accum.count());
    accum.copyTo(runs.get());
    return this->setRuns(runs.get(), accum.count());
}

#endif