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

#include "src/core/SkMathPriv.h"
#include "src/core/SkPathPriv.h"
#include "tests/SubsetPath.h"

SubsetPath::SubsetPath(const SkPath& path)
        : fPath(path)
        , fSubset(1) {
}

int SubsetPath::range(int* end) const {
    int leadingZero = SkCLZ(fSubset);
    int parts = 1 << (31 - leadingZero);
    int partIndex = fSubset - parts;
    SkASSERT(partIndex >= 0);
    int count = fSelected.count();
    int start = count * partIndex / parts;
    *end = count * (partIndex + 1) / parts;
    return start;
}

bool SubsetPath::subset(bool testFailed, SkPath* sub) {
    int start, end;
    if (!testFailed) {
        start = range(&end);
        for (; start < end; ++start) {
            fSelected[start] = true;
        }
    }
    do {
        do {
            ++fSubset;
            start = range(&end);
 //           SkDebugf("%d s=%d e=%d t=%d\n", fSubset, start, end, fTries);
            if (end - start > 1) {
                fTries = fSelected.count();
            } else if (end - start == 1) {
                if (--fTries <= 0) {
                    return false;
                }
            }
        } while (start == end);
    } while (!fSelected[start]);
    for (; start < end; ++start) {
        fSelected[start] = false;
    }
#if 1
    SkDebugf("selected: ");
    for (int index = 0; index < fSelected.count(); ++index) {
        SkDebugf("%c", fSelected[index] ? 'x' : '-');
    }
#endif
    *sub = getSubsetPath();
    return true;
}

SubsetContours::SubsetContours(const SkPath& path)
        : SubsetPath(path) {
    bool foundCurve = false;
    int contourCount = 0;
    for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
        switch (verb) {
            case SkPathVerb::kMove:
                break;
            case SkPathVerb::kLine:
            case SkPathVerb::kQuad:
            case SkPathVerb::kConic:
            case SkPathVerb::kCubic:
                foundCurve = true;
                break;
            case SkPathVerb::kClose:
                ++contourCount;
                foundCurve = false;
                break;
            default:
                SkDEBUGFAIL("bad verb");
                return;
        }
    }
    contourCount += foundCurve;
    for (int index = 0; index < contourCount; ++index) {
        *fSelected.append() = true;
    }
    fTries = contourCount;
}

SkPath SubsetContours::getSubsetPath() const {
    SkPath result;
    result.setFillType(fPath.getFillType());
    if (!fSelected.count()) {
        return result;
    }
    int contourCount = 0;
    bool enabled = fSelected[0];
    bool addMoveTo = true;
    for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
        if (enabled && addMoveTo) {
            result.moveTo(pts[0]);
            addMoveTo = false;
        }
        switch (verb) {
            case SkPathVerb::kMove:
                break;
            case SkPathVerb::kLine:
                if (enabled) {
                    result.lineTo(pts[1]);
                }
                break;
            case SkPathVerb::kQuad:
                if (enabled) {
                    result.quadTo(pts[1], pts[2]);
                }
                break;
            case SkPathVerb::kConic:
                if (enabled) {
                    result.conicTo(pts[1], pts[2], *w);
                }
                break;
            case SkPathVerb::kCubic:
                 if (enabled) {
                    result.cubicTo(pts[1], pts[2], pts[3]);
                }
                break;
            case SkPathVerb::kClose:
                if (enabled) {
                    result.close();
                }
                if (++contourCount >= fSelected.count()) {
                    break;
                }
                enabled = fSelected[contourCount];
                addMoveTo = true;
                continue;
            default:
                SkDEBUGFAIL("bad verb");
                return result;
        }
    }
    return result;
}

SubsetVerbs::SubsetVerbs(const SkPath& path)
        : SubsetPath(path) {
    int verbCount = 0;
    for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
        switch (verb) {
            case SkPathVerb::kMove:
                break;
            case SkPathVerb::kLine:
            case SkPathVerb::kQuad:
            case SkPathVerb::kConic:
            case SkPathVerb::kCubic:
                ++verbCount;
                break;
            case SkPathVerb::kClose:
                break;
            default:
                SkDEBUGFAIL("bad verb");
                return;
        }
    }
    for (int index = 0; index < verbCount; ++index) {
        *fSelected.append() = true;
    }
    fTries = verbCount;
}

SkPath SubsetVerbs::getSubsetPath() const {
    SkPath result;
    result.setFillType(fPath.getFillType());
    if (!fSelected.count()) {
        return result;
    }
    int verbIndex = 0;
    bool addMoveTo = true;
    bool addLineTo = false;
    for (auto [verb, pts, w] : SkPathPriv::Iterate(fPath)) {
        bool enabled = SkPathVerb::kLine <= verb && verb <= SkPathVerb::kCubic
            ? fSelected[verbIndex++] : false;
        if (enabled) {
            if (addMoveTo) {
                result.moveTo(pts[0]);
                addMoveTo = false;
            } else if (addLineTo) {
                result.lineTo(pts[0]);
                addLineTo = false;
            }
        }
        switch (verb) {
            case SkPathVerb::kMove:
                break;
            case SkPathVerb::kLine:
                if (enabled) {
                    result.lineTo(pts[1]);
                }
                break;
            case SkPathVerb::kQuad:
                if (enabled) {
                    result.quadTo(pts[1], pts[2]);
                }
                break;
            case SkPathVerb::kConic:
                if (enabled) {
                    result.conicTo(pts[1], pts[2], *w);
                }
                break;
            case SkPathVerb::kCubic:
                 if (enabled) {
                    result.cubicTo(pts[1], pts[2], pts[3]);
                }
                break;
            case SkPathVerb::kClose:
                result.close();
                addMoveTo = true;
                addLineTo = false;
                continue;
            default:
                SkDEBUGFAIL("bad verb");
                return result;
        }
        addLineTo = !enabled;
    }
    return result;
}
