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

#include "include/effects/SkOpPathEffect.h"

#include "include/core/SkFlattenable.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathEffect.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkStrokeRec.h"
#include "include/core/SkTypes.h"
#include "include/pathops/SkPathOps.h"
#include "include/private/base/SkPathEnums.h"
#include "include/private/base/SkTo.h"
#include "src/core/SkPathEffectBase.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkWriteBuffer.h"
#include "src/effects/SkOpPE.h"

#include <utility>

sk_sp<SkPathEffect> SkMergePathEffect::Make(sk_sp<SkPathEffect> one, sk_sp<SkPathEffect> two,
                                            SkPathOp op) {
    return sk_sp<SkPathEffect>(new SkOpPE(std::move(one), std::move(two), op));
}

SkOpPE::SkOpPE(sk_sp<SkPathEffect> one, sk_sp<SkPathEffect> two, SkPathOp op)
    : fOne(std::move(one)), fTwo(std::move(two)), fOp(op) {}

bool SkOpPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
                          const SkRect* cull, const SkMatrix& ctm) const {
    SkPath one, two;
    if (fOne) {
        if (!fOne->filterPath(&one, src, rec, cull, ctm)) {
            return false;
        }
    } else {
        one = src;
    }
    if (fTwo) {
        if (!fTwo->filterPath(&two, src, rec, cull, ctm)) {
            return false;
        }
    } else {
        two = src;
    }
    return Op(one, two, fOp, dst);
}

bool SkOpPE::computeFastBounds(SkRect* bounds) const {
    if (!bounds) {
        return (!SkToBool(fOne) || as_PEB(fOne)->computeFastBounds(nullptr)) &&
               (!SkToBool(fTwo) || as_PEB(fTwo)->computeFastBounds(nullptr));
    }

    // bounds will hold the result of the fOne while b2 holds the result of fTwo's fast bounds
    SkRect b2 = *bounds;
    if (fOne && !as_PEB(fOne)->computeFastBounds(bounds)) {
        return false;
    }
    if (fTwo && !as_PEB(fTwo)->computeFastBounds(&b2)) {
        return false;
    }

    switch (fOp) {
        case SkPathOp::kIntersect_SkPathOp:
            if (!bounds->intersect(b2)) {
                bounds->setEmpty();
            }
            break;
        case SkPathOp::kDifference_SkPathOp:
            // (one - two) conservatively leaves one's bounds unmodified
            break;
        case SkPathOp::kReverseDifference_SkPathOp:
            // (two - one) conservatively leaves two's bounds unmodified
            *bounds = b2;
            break;
        case SkPathOp::kXOR_SkPathOp:
            // fall through to union since XOR computes a subset of regular OR
        case SkPathOp::kUnion_SkPathOp:
            bounds->join(b2);
            break;
    }

    return true;
}

void SkOpPE::flatten(SkWriteBuffer& buffer) const {
    buffer.writeFlattenable(fOne.get());
    buffer.writeFlattenable(fTwo.get());
    buffer.write32(fOp);
}

sk_sp<SkFlattenable> SkOpPE::CreateProc(SkReadBuffer& buffer) {
    auto one = buffer.readPathEffect();
    auto two = buffer.readPathEffect();
    SkPathOp op = buffer.read32LE(kReverseDifference_SkPathOp);
    return buffer.isValid() ? SkMergePathEffect::Make(std::move(one), std::move(two), op) : nullptr;
}

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

sk_sp<SkPathEffect> SkMatrixPathEffect::MakeTranslate(SkScalar dx, SkScalar dy) {
    if (!SkScalarsAreFinite(dx, dy)) {
        return nullptr;
    }
    return sk_sp<SkPathEffect>(new SkMatrixPE(SkMatrix::Translate(dx, dy)));
}

sk_sp<SkPathEffect> SkMatrixPathEffect::Make(const SkMatrix& matrix) {
    if (!matrix.isFinite()) {
        return nullptr;
    }
    return sk_sp<SkPathEffect>(new SkMatrixPE(matrix));
}

SkMatrixPE::SkMatrixPE(const SkMatrix& matrix) : fMatrix(matrix) {
    SkASSERT(matrix.isFinite());
}

bool SkMatrixPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*,
                              const SkMatrix&) const {
    src.transform(fMatrix, dst);
    return true;
}

void SkMatrixPE::flatten(SkWriteBuffer& buffer) const {
    buffer.writeMatrix(fMatrix);
}

sk_sp<SkFlattenable> SkMatrixPE::CreateProc(SkReadBuffer& buffer) {
    SkMatrix mx;
    buffer.readMatrix(&mx);
    return buffer.isValid() ? SkMatrixPathEffect::Make(mx) : nullptr;
}

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

sk_sp<SkPathEffect> SkStrokePathEffect::Make(SkScalar width, SkPaint::Join join, SkPaint::Cap cap,
                                             SkScalar miter) {
    if (!SkScalarsAreFinite(width, miter) || width < 0 || miter < 0) {
        return nullptr;
    }
    return sk_sp<SkPathEffect>(new SkStrokePE(width, join, cap, miter));
}

SkStrokePE::SkStrokePE(SkScalar width, SkPaint::Join join, SkPaint::Cap cap, SkScalar miter)
    : fWidth(width), fMiter(miter), fJoin(join), fCap(cap) {}

bool SkStrokePE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec*, const SkRect*,
                              const SkMatrix&) const {
    SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
    rec.setStrokeStyle(fWidth);
    rec.setStrokeParams(fCap, fJoin, fMiter);
    return rec.applyToPath(dst, src);
}

bool SkStrokePE::computeFastBounds(SkRect* bounds) const {
    if (bounds) {
        SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
        rec.setStrokeStyle(fWidth);
        rec.setStrokeParams(fCap, fJoin, fMiter);
        bounds->outset(rec.getInflationRadius(), rec.getInflationRadius());
    }
    return true;
}

void SkStrokePE::flatten(SkWriteBuffer& buffer) const {
    buffer.writeScalar(fWidth);
    buffer.writeScalar(fMiter);
    buffer.write32(fJoin);
    buffer.write32(fCap);
}

sk_sp<SkFlattenable> SkStrokePE::CreateProc(SkReadBuffer& buffer) {
    SkScalar width = buffer.readScalar();
    SkScalar miter = buffer.readScalar();
    SkPaint::Join join = buffer.read32LE(SkPaint::kLast_Join);
    SkPaint::Cap cap = buffer.read32LE(SkPaint::kLast_Cap);
    return buffer.isValid() ? SkStrokePathEffect::Make(width, join, cap, miter) : nullptr;
}

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

#include "include/effects/SkStrokeAndFillPathEffect.h"
#include "src/core/SkPathPriv.h"

sk_sp<SkPathEffect> SkStrokeAndFillPathEffect::Make() {
    static SkPathEffect* strokeAndFill = new SkStrokeAndFillPE;
    return sk_ref_sp(strokeAndFill);
}

void SkStrokeAndFillPE::flatten(SkWriteBuffer&) const {}

static bool known_to_be_opposite_directions(const SkPath& a, const SkPath& b) {
    auto a_dir = SkPathPriv::ComputeFirstDirection(a),
         b_dir = SkPathPriv::ComputeFirstDirection(b);

    return (a_dir == SkPathFirstDirection::kCCW &&
            b_dir == SkPathFirstDirection::kCW)
            ||
           (a_dir == SkPathFirstDirection::kCW &&
            b_dir == SkPathFirstDirection::kCCW);
}

bool SkStrokeAndFillPE::onFilterPath(SkPath* dst, const SkPath& src, SkStrokeRec* rec,
                                     const SkRect*, const SkMatrix&) const {
    // This one is weird, since we exist to allow this paint-style to go away. If we see it,
    // just let the normal machine run its course.
    if (rec->getStyle() == SkStrokeRec::kStrokeAndFill_Style) {
        *dst = src;
        return true;
    }

    if (rec->getStyle() == SkStrokeRec::kStroke_Style) {
        if (!rec->applyToPath(dst, src)) {
            return false;
        }

        if (known_to_be_opposite_directions(src, *dst)) {
            dst->reverseAddPath(src);
        } else {
            dst->addPath(src);
        }
    } else {
        *dst = src;
    }
    rec->setFillStyle();
    return true;
}

sk_sp<SkFlattenable> SkStrokeAndFillPE::CreateProc(SkReadBuffer& buffer) {
    return SkStrokeAndFillPathEffect::Make();
}
