/*
 * 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 "GrDrawPathOp.h"
#include "GrAppliedClip.h"
#include "GrRenderTargetContext.h"
#include "GrRenderTargetPriv.h"
#include "SkTemplates.h"

GrDrawPathOpBase::GrDrawPathOpBase(uint32_t classID, const SkMatrix& viewMatrix, GrPaint&& paint,
                                   GrPathRendering::FillType fill, GrAAType aaType)
        : INHERITED(classID)
        , fViewMatrix(viewMatrix)
        , fInputColor(paint.getColor())
        , fFillType(fill)
        , fAAType(aaType)
        , fPipelineSRGBFlags(GrPipeline::SRGBFlagsFromPaint(paint))
        , fProcessorSet(std::move(paint)) {}

SkString GrDrawPathOp::dumpInfo() const {
    SkString string;
    string.printf("PATH: 0x%p", fPath.get());
    string.append(INHERITED::dumpInfo());
    return string;
}

GrPipeline::InitArgs GrDrawPathOpBase::pipelineInitArgs(const GrOpFlushState& state) {
    static constexpr GrUserStencilSettings kCoverPass{
            GrUserStencilSettings::StaticInit<
                    0x0000,
                    GrUserStencilTest::kNotEqual,
                    0xffff,
                    GrUserStencilOp::kZero,
                    GrUserStencilOp::kKeep,
                    0xffff>()
    };
    GrPipeline::InitArgs args;
    args.fFlags = fPipelineSRGBFlags;
    if (GrAATypeIsHW(fAAType)) {
        args.fFlags |= GrPipeline::kHWAntialias_Flag;
    }
    args.fUserStencil = &kCoverPass;
    args.fProxy = state.drawOpArgs().fProxy;
    args.fCaps = &state.caps();
    args.fResourceProvider = state.resourceProvider();
    args.fDstProxy = state.drawOpArgs().fDstProxy;
    return args;
}

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

void init_stencil_pass_settings(const GrOpFlushState& flushState,
                                GrPathRendering::FillType fillType, GrStencilSettings* stencil) {
    const GrAppliedClip* appliedClip = flushState.drawOpArgs().fAppliedClip;
    bool stencilClip = appliedClip && appliedClip->hasStencilClip();
    stencil->reset(GrPathRendering::GetStencilPassSettings(fillType), stencilClip,
                   flushState.drawOpArgs().renderTarget()->renderTargetPriv().numStencilBits());
}

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

void GrDrawPathOp::onExecute(GrOpFlushState* state) {
    GrPipeline pipeline(this->pipelineInitArgs(*state), this->detachProcessors(),
                        state->detachAppliedClip());
    sk_sp<GrPathProcessor> pathProc(GrPathProcessor::Create(this->color(), this->viewMatrix()));

    GrStencilSettings stencil;
    init_stencil_pass_settings(*state, this->fillType(), &stencil);
    state->gpu()->pathRendering()->drawPath(pipeline, *pathProc, stencil, fPath.get());
}

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

SkString GrDrawPathRangeOp::dumpInfo() const {
    SkString string;
    string.printf("RANGE: 0x%p COUNTS: [", fPathRange.get());
    for (DrawList::Iter iter(fDraws); iter.get(); iter.next()) {
        string.appendf("%d, ", iter.get()->fInstanceData->count());
    }
    string.remove(string.size() - 2, 2);
    string.append("]");
    string.append(INHERITED::dumpInfo());
    return string;
}

GrDrawPathRangeOp::GrDrawPathRangeOp(const SkMatrix& viewMatrix, SkScalar scale, SkScalar x,
                                     SkScalar y, GrPaint&& paint, GrPathRendering::FillType fill,
                                     GrAAType aaType, GrPathRange* range,
                                     const InstanceData* instanceData, const SkRect& bounds)
        : INHERITED(ClassID(), viewMatrix, std::move(paint), fill, aaType)
        , fPathRange(range)
        , fTotalPathCount(instanceData->count())
        , fScale(scale) {
    fDraws.addToHead()->set(instanceData, x, y);
    this->setBounds(bounds, HasAABloat::kNo, IsZeroArea::kNo);
}

static void pre_translate_transform_values(const float* xforms,
                                           GrPathRendering::PathTransformType type, int count,
                                           SkScalar x, SkScalar y, float* dst);

bool GrDrawPathRangeOp::onCombineIfPossible(GrOp* t, const GrCaps& caps) {
    GrDrawPathRangeOp* that = t->cast<GrDrawPathRangeOp>();
    if (this->fPathRange.get() != that->fPathRange.get() ||
        this->transformType() != that->transformType() || this->fScale != that->fScale ||
        this->color() != that->color() || !this->viewMatrix().cheapEqualTo(that->viewMatrix())) {
        return false;
    }
    if (this->processors() != that->processors()) {
        return false;
    }
    if (this->pipelineSRGBFlags() != that->pipelineSRGBFlags()) {
        return false;
    }
    switch (fDraws.head()->fInstanceData->transformType()) {
        case GrPathRendering::kNone_PathTransformType:
            if (this->fDraws.head()->fX != that->fDraws.head()->fX ||
                this->fDraws.head()->fY != that->fDraws.head()->fY) {
                return false;
            }
            break;
        case GrPathRendering::kTranslateX_PathTransformType:
            if (this->fDraws.head()->fY != that->fDraws.head()->fY) {
                return false;
            }
            break;
        case GrPathRendering::kTranslateY_PathTransformType:
            if (this->fDraws.head()->fX != that->fDraws.head()->fX) {
                return false;
            }
            break;
        default:
            break;
    }
    // TODO: Check some other things here. (winding, opaque, pathProc color, vm, ...)
    // Try to combine this call with the previous DrawPaths. We do this by stenciling all the
    // paths together and then covering them in a single pass. This is not equivalent to two
    // separate draw calls, so we can only do it if there is no blending (no overlap would also
    // work). Note that it's also possible for overlapping paths to cancel each other's winding
    // numbers, and we only partially account for this by not allowing even/odd paths to be
    // combined. (Glyphs in the same font tend to wind the same direction so it works out OK.)

    if (GrPathRendering::kWinding_FillType != this->fillType() ||
        GrPathRendering::kWinding_FillType != that->fillType()) {
        return false;
    }
    if (!this->processorAnalysis().canCombineOverlappedStencilAndCover()) {
        return false;
    }
    fTotalPathCount += that->fTotalPathCount;
    while (Draw* head = that->fDraws.head()) {
        Draw* draw = fDraws.addToTail();
        draw->fInstanceData.reset(head->fInstanceData.release());
        draw->fX = head->fX;
        draw->fY = head->fY;
        that->fDraws.popHead();
    }
    this->joinBounds(*that);
    return true;
}

void GrDrawPathRangeOp::onExecute(GrOpFlushState* state) {
    const Draw& head = *fDraws.head();

    SkMatrix drawMatrix(this->viewMatrix());
    drawMatrix.preScale(fScale, fScale);
    drawMatrix.preTranslate(head.fX, head.fY);

    SkMatrix localMatrix;
    localMatrix.setScale(fScale, fScale);
    localMatrix.preTranslate(head.fX, head.fY);

    sk_sp<GrPathProcessor> pathProc(
            GrPathProcessor::Create(this->color(), drawMatrix, localMatrix));

    GrPipeline pipeline(this->pipelineInitArgs(*state), this->detachProcessors(),
                        state->detachAppliedClip());
    GrStencilSettings stencil;
    init_stencil_pass_settings(*state, this->fillType(), &stencil);
    if (fDraws.count() == 1) {
        const InstanceData& instances = *head.fInstanceData;
        state->gpu()->pathRendering()->drawPaths(pipeline,
                                                 *pathProc,
                                                 stencil,
                                                 fPathRange.get(),
                                                 instances.indices(),
                                                 GrPathRange::kU16_PathIndexType,
                                                 instances.transformValues(),
                                                 instances.transformType(),
                                                 instances.count());
    } else {
        int floatsPerTransform = GrPathRendering::PathTransformSize(this->transformType());
        SkAutoSTMalloc<4096, float> transformStorage(floatsPerTransform * fTotalPathCount);
        SkAutoSTMalloc<2048, uint16_t> indexStorage(fTotalPathCount);
        int idx = 0;
        for (DrawList::Iter iter(fDraws); iter.get(); iter.next()) {
            const Draw& draw = *iter.get();
            const InstanceData& instances = *draw.fInstanceData;
            memcpy(&indexStorage[idx], instances.indices(), instances.count() * sizeof(uint16_t));
            pre_translate_transform_values(instances.transformValues(), this->transformType(),
                                           instances.count(), draw.fX - head.fX, draw.fY - head.fY,
                                           &transformStorage[floatsPerTransform * idx]);
            idx += instances.count();

            // TODO: Support mismatched transform types if we start using more types other than 2D.
            SkASSERT(instances.transformType() == this->transformType());
        }
        SkASSERT(idx == fTotalPathCount);

        state->gpu()->pathRendering()->drawPaths(pipeline,
                                                 *pathProc,
                                                 stencil,
                                                 fPathRange.get(),
                                                 indexStorage,
                                                 GrPathRange::kU16_PathIndexType,
                                                 transformStorage,
                                                 this->transformType(),
                                                 fTotalPathCount);
    }
}

inline void pre_translate_transform_values(const float* xforms,
                                           GrPathRendering::PathTransformType type, int count,
                                           SkScalar x, SkScalar y, float* dst) {
    if (0 == x && 0 == y) {
        memcpy(dst, xforms, count * GrPathRendering::PathTransformSize(type) * sizeof(float));
        return;
    }
    switch (type) {
        case GrPathRendering::kNone_PathTransformType:
            SK_ABORT("Cannot pre-translate kNone_PathTransformType.");
            break;
        case GrPathRendering::kTranslateX_PathTransformType:
            SkASSERT(0 == y);
            for (int i = 0; i < count; i++) {
                dst[i] = xforms[i] + x;
            }
            break;
        case GrPathRendering::kTranslateY_PathTransformType:
            SkASSERT(0 == x);
            for (int i = 0; i < count; i++) {
                dst[i] = xforms[i] + y;
            }
            break;
        case GrPathRendering::kTranslate_PathTransformType:
            for (int i = 0; i < 2 * count; i += 2) {
                dst[i] = xforms[i] + x;
                dst[i + 1] = xforms[i + 1] + y;
            }
            break;
        case GrPathRendering::kAffine_PathTransformType:
            for (int i = 0; i < 6 * count; i += 6) {
                dst[i] = xforms[i];
                dst[i + 1] = xforms[i + 1];
                dst[i + 2] = xforms[i] * x + xforms[i + 1] * y + xforms[i + 2];
                dst[i + 3] = xforms[i + 3];
                dst[i + 4] = xforms[i + 4];
                dst[i + 5] = xforms[i + 3] * x + xforms[i + 4] * y + xforms[i + 5];
            }
            break;
        default:
            SK_ABORT("Unknown transform type.");
            break;
    }
}