/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/core/SkPath.h"
#include "include/core/SkRegion.h"
#include "include/core/SkStrokeRec.h"
#include "include/effects/Sk2DPathEffect.h"
#include "src/core/SkPathEffectBase.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkWriteBuffer.h"

class Sk2DPathEffect : public SkPathEffectBase {
public:
    Sk2DPathEffect(const SkMatrix& mat) : fMatrix(mat) {
        // Calling invert will set the type mask on both matrices, making them thread safe.
        fMatrixIsInvertible = fMatrix.invert(&fInverse);
    }

protected:
    /** New virtual, to be overridden by subclasses.
        This is called once from filterPath, and provides the
        uv parameter bounds for the path. Subsequent calls to
        next() will receive u and v values within these bounds,
        and then a call to end() will signal the end of processing.
    */
    virtual void begin(const SkIRect& uvBounds, SkPath* dst) const {}
    virtual void next(const SkPoint& loc, int u, int v, SkPath* dst) const {}
    virtual void end(SkPath* dst) const {}

    /** Low-level virtual called per span of locations in the u-direction.
        The default implementation calls next() repeatedly with each
        location.
    */
    virtual void nextSpan(int x, int y, int ucount, SkPath* path) const {
        if (!fMatrixIsInvertible) {
            return;
        }
    #if defined(SK_BUILD_FOR_FUZZER)
        if (count > 100) {
            return;
        }
    #endif

        const SkMatrix& mat = this->getMatrix();
        SkPoint src, dst;

        src.set(SkIntToScalar(x) + SK_ScalarHalf, SkIntToScalar(y) + SK_ScalarHalf);
        do {
            mat.mapPoints(&dst, &src, 1);
            this->next(dst, x++, y, path);
            src.fX += SK_Scalar1;
        } while (--ucount > 0);
    }

    const SkMatrix& getMatrix() const { return fMatrix; }

    void flatten(SkWriteBuffer& buffer) const override {
        this->INHERITED::flatten(buffer);
        buffer.writeMatrix(fMatrix);
    }

    bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
                      const SkRect* cullRect, const SkMatrix&) const override {
        if (!fMatrixIsInvertible) {
            return false;
        }

        SkPath  tmp;
        SkIRect ir;

        src.transform(fInverse, &tmp);
        tmp.getBounds().round(&ir);
        if (!ir.isEmpty()) {
            this->begin(ir, dst);

            SkRegion rgn;
            rgn.setPath(tmp, SkRegion(ir));
            SkRegion::Iterator iter(rgn);
            for (; !iter.done(); iter.next()) {
                const SkIRect& rect = iter.rect();
                for (int y = rect.fTop; y < rect.fBottom; ++y) {
                    this->nextSpan(rect.fLeft, y, rect.width(), dst);
                }
            }

            this->end(dst);
        }
        return true;
    }

private:
    SkMatrix    fMatrix, fInverse;
    bool        fMatrixIsInvertible;

    // For simplicity, assume fast bounds cannot be computed
    bool computeFastBounds(SkRect*) const override { return false; }

    friend class Sk2DPathEffectBlitter;
    using INHERITED = SkPathEffect;
};

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

class SkLine2DPathEffectImpl : public Sk2DPathEffect {
public:
    SkLine2DPathEffectImpl(SkScalar width, const SkMatrix& matrix)
        : Sk2DPathEffect(matrix)
        , fWidth(width)
    {
        SkASSERT(width >= 0);
    }

    bool onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
                      const SkRect* cullRect, const SkMatrix& ctm) const override {
        if (this->INHERITED::onFilterPath(dst, src, rec, cullRect, ctm)) {
            rec->setStrokeStyle(fWidth);
            return true;
        }
        return false;
    }

    void nextSpan(int u, int v, int ucount, SkPath* dst) const override {
        if (ucount > 1) {
            SkPoint    src[2], dstP[2];

            src[0].set(SkIntToScalar(u) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf);
            src[1].set(SkIntToScalar(u+ucount) + SK_ScalarHalf, SkIntToScalar(v) + SK_ScalarHalf);
            this->getMatrix().mapPoints(dstP, src, 2);

            dst->moveTo(dstP[0]);
            dst->lineTo(dstP[1]);
        }
    }

    static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer) {
        SkMatrix matrix;
        buffer.readMatrix(&matrix);
        SkScalar width = buffer.readScalar();
        return SkLine2DPathEffect::Make(width, matrix);
    }

    void flatten(SkWriteBuffer &buffer) const override {
        buffer.writeMatrix(this->getMatrix());
        buffer.writeScalar(fWidth);
    }

    Factory getFactory() const override { return CreateProc; }
    const char* getTypeName() const override { return "SkLine2DPathEffect"; }

private:
    SkScalar fWidth;

    using INHERITED = Sk2DPathEffect;
};

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

class SK_API SkPath2DPathEffectImpl : public Sk2DPathEffect {
public:
    SkPath2DPathEffectImpl(const SkMatrix& m, const SkPath& p) : INHERITED(m), fPath(p) {}

    void next(const SkPoint& loc, int u, int v, SkPath* dst) const override {
        dst->addPath(fPath, loc.fX, loc.fY);
    }

    static sk_sp<SkFlattenable> CreateProc(SkReadBuffer& buffer) {
        SkMatrix matrix;
        buffer.readMatrix(&matrix);
        SkPath path;
        buffer.readPath(&path);
        return SkPath2DPathEffect::Make(matrix, path);
    }

    void flatten(SkWriteBuffer& buffer) const override {
        buffer.writeMatrix(this->getMatrix());
        buffer.writePath(fPath);
    }

    Factory getFactory() const override { return CreateProc; }
    const char* getTypeName() const override { return "SkPath2DPathEffect"; }

private:
    SkPath  fPath;

    using INHERITED = Sk2DPathEffect;
};

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

sk_sp<SkPathEffect> SkLine2DPathEffect::Make(SkScalar width, const SkMatrix& matrix) {
    if (!(width >= 0)) {
        return nullptr;
    }
    return sk_sp<SkPathEffect>(new SkLine2DPathEffectImpl(width, matrix));
}

sk_sp<SkPathEffect> SkPath2DPathEffect::Make(const SkMatrix& matrix, const SkPath& path) {
    return sk_sp<SkPathEffect>(new SkPath2DPathEffectImpl(matrix, path));
}

void SkLine2DPathEffect::RegisterFlattenables() {
    SK_REGISTER_FLATTENABLE(SkLine2DPathEffectImpl);
}

void SkPath2DPathEffect::RegisterFlattenables() {
    SK_REGISTER_FLATTENABLE(SkPath2DPathEffectImpl);
}
