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

#include "GrAnalyticRectBatch.h"

#include "GrBatchFlushState.h"
#include "GrBatchTest.h"
#include "GrGeometryProcessor.h"
#include "GrInvariantOutput.h"
#include "GrProcessor.h"
#include "GrResourceProvider.h"
#include "SkRRect.h"
#include "SkStrokeRec.h"
#include "batches/GrVertexBatch.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLGeometryProcessor.h"
#include "glsl/GrGLSLGeometryProcessor.h"
#include "glsl/GrGLSLProgramDataManager.h"
#include "glsl/GrGLSLVarying.h"
#include "glsl/GrGLSLVertexShaderBuilder.h"
#include "glsl/GrGLSLUniformHandler.h"
#include "glsl/GrGLSLUtil.h"

namespace {

struct RectVertex {
    SkPoint  fPos;
    GrColor  fColor;
    SkPoint  fCenter;
    SkVector fDownDir;
    SkScalar fHalfWidth;
    SkScalar fHalfHeight;
};

}

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

/**
 * The output of this effect is the input color and coverage for an arbitrarily oriented rect. The
 * rect is specified as:
 *      Center of the rect
 *      Unit vector point down the height of the rect
 *      Half width + 0.5
 *      Half height + 0.5
 * The center and vector are stored in a vec4 varying ("RectEdge") with the
 * center in the xy components and the vector in the zw components.
 * The munged width and height are stored in a vec2 varying ("WidthHeight")
 * with the width in x and the height in y.
 */
class RectGeometryProcessor : public GrGeometryProcessor {
public:
    RectGeometryProcessor(const SkMatrix& localMatrix) : fLocalMatrix(localMatrix) {
        this->initClassID<RectGeometryProcessor>();
        fInPosition    = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
                                                kHigh_GrSLPrecision);
        fInColor       = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
        fInRectEdge    = &this->addVertexAttrib("inRectEdge", kVec4f_GrVertexAttribType);
        fInWidthHeight = &this->addVertexAttrib("inWidthHeight", kVec2f_GrVertexAttribType);
    }

    bool implementsDistanceVector() const override { return true; }

    const Attribute* inPosition()    const { return fInPosition; }
    const Attribute* inColor()       const { return fInColor; }
    const Attribute* inRectEdge()    const { return fInRectEdge;    }
    const Attribute* inWidthHeight() const { return fInWidthHeight; }

    const SkMatrix& localMatrix() const { return fLocalMatrix; }

    virtual ~RectGeometryProcessor() {}

    const char* name() const override { return "RectEdge"; }

    class GLSLProcessor : public GrGLSLGeometryProcessor {
    public:
        GLSLProcessor() {}

        void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
            const RectGeometryProcessor& rgp = args.fGP.cast<RectGeometryProcessor>();
            GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
            GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
            GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;

            // emit attributes
            varyingHandler->emitAttributes(rgp);

            // setup the varying for the position
            GrGLSLVertToFrag positionVary(kVec2f_GrSLType);
            varyingHandler->addVarying("Position", &positionVary);
            vertBuilder->codeAppendf("%s = %s;", positionVary.vsOut(), rgp.inPosition()->fName);

            // setup the varying for the center point and the unit vector that points down the
            // height of the rect
            GrGLSLVertToFrag rectEdgeVary(kVec4f_GrSLType);
            varyingHandler->addVarying("RectEdge", &rectEdgeVary);
            vertBuilder->codeAppendf("%s = %s;", rectEdgeVary.vsOut(), rgp.inRectEdge()->fName);

            // setup the varying for the width/2+.5 and height/2+.5
            GrGLSLVertToFrag widthHeightVary(kVec2f_GrSLType);
            varyingHandler->addVarying("WidthHeight", &widthHeightVary);
            vertBuilder->codeAppendf("%s = %s;",
                                     widthHeightVary.vsOut(), rgp.inWidthHeight()->fName);

            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;

            // setup pass through color
            varyingHandler->addPassThroughAttribute(rgp.inColor(), args.fOutputColor);

            // Setup position
            this->setupPosition(vertBuilder, gpArgs, rgp.inPosition()->fName);

            // emit transforms
            this->emitTransforms(vertBuilder,
                                 varyingHandler,
                                 uniformHandler,
                                 gpArgs->fPositionVar,
                                 rgp.inPosition()->fName,
                                 rgp.localMatrix(),
                                 args.fFPCoordTransformHandler);

            // TODO: compute all these offsets, spans, and scales in the VS
            fragBuilder->codeAppendf("float insetW = min(1.0, %s.x) - 0.5;",
                                     widthHeightVary.fsIn());
            fragBuilder->codeAppendf("float insetH = min(1.0, %s.y) - 0.5;",
                                     widthHeightVary.fsIn());
            fragBuilder->codeAppend("float outset = 0.5;");
            // For rects > 1 pixel wide and tall the span's are noops (i.e., 1.0). For rects
            // < 1 pixel wide or tall they serve to normalize the < 1 ramp to a 0 .. 1 range.
            fragBuilder->codeAppend("float spanW = insetW + outset;");
            fragBuilder->codeAppend("float spanH = insetH + outset;");
            // For rects < 1 pixel wide or tall, these scale factors are used to cap the maximum
            // value of coverage that is used. In other words it is the coverage that is
            // used in the interior of the rect after the ramp.
            fragBuilder->codeAppend("float scaleW = min(1.0, 2.0*insetW/spanW);");
            fragBuilder->codeAppend("float scaleH = min(1.0, 2.0*insetH/spanH);");
            // Compute the coverage for the rect's width
            fragBuilder->codeAppendf("vec2 offset = %s.xy - %s.xy;",
                                     positionVary.fsIn(), rectEdgeVary.fsIn());
            fragBuilder->codeAppendf("float perpDot = abs(offset.x * %s.w - offset.y * %s.z);",
                                     rectEdgeVary.fsIn(), rectEdgeVary.fsIn());

            if (args.fDistanceVectorName) {
                fragBuilder->codeAppendf("float widthDistance = %s.x - perpDot;",
                                         widthHeightVary.fsIn());
            }

            fragBuilder->codeAppendf(
                    "float coverage = scaleW*clamp((%s.x-perpDot)/spanW, 0.0, 1.0);",
                    widthHeightVary.fsIn());
            // Compute the coverage for the rect's height and merge with the width
            fragBuilder->codeAppendf("perpDot = abs(dot(offset, %s.zw));",
                                   rectEdgeVary.fsIn());

            if (args.fDistanceVectorName) {
                fragBuilder->codeAppendf("float heightDistance = %s.y - perpDot;",
                                         widthHeightVary.fsIn());
            }

            fragBuilder->codeAppendf(
                    "coverage = coverage*scaleH*clamp((%s.y-perpDot)/spanH, 0.0, 1.0);",
                    widthHeightVary.fsIn());

            fragBuilder->codeAppendf("%s = vec4(coverage);", args.fOutputCoverage);

            if (args.fDistanceVectorName) {
                fragBuilder->codeAppend( "// Calculating distance vector\n");
                fragBuilder->codeAppend( "vec2 dvAxis;");
                fragBuilder->codeAppend( "float dvLength;");

                fragBuilder->codeAppend( "if (heightDistance < widthDistance) {");
                fragBuilder->codeAppendf("    dvAxis = %s.zw;", rectEdgeVary.fsIn());
                fragBuilder->codeAppend( "    dvLength = heightDistance;");
                fragBuilder->codeAppend( "} else {");
                fragBuilder->codeAppendf("    dvAxis = vec2(-%s.w, %s.z);",
                                         rectEdgeVary.fsIn(), rectEdgeVary.fsIn());
                fragBuilder->codeAppend( "    dvLength = widthDistance;");
                fragBuilder->codeAppend( "}");

                fragBuilder->codeAppend( "float dvSign = sign(dot(offset, dvAxis));");
                fragBuilder->codeAppendf("%s = vec4(dvSign * dvAxis, dvLength, 0.0);",
                                         args.fDistanceVectorName);

            }
        }

        static void GenKey(const GrGeometryProcessor& gp,
                           const GrGLSLCaps&,
                           GrProcessorKeyBuilder* b) {
            b->add32(0x0);
        }

        void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc,
                     FPCoordTransformIter&& transformIter) override {
            const RectGeometryProcessor& rgp = primProc.cast<RectGeometryProcessor>();
            this->setTransformDataHelper(rgp.fLocalMatrix, pdman,&transformIter);
        }

    private:
        typedef GrGLSLGeometryProcessor INHERITED;
    };

    void getGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
        GLSLProcessor::GenKey(*this, caps, b);
    }

    GrGLSLPrimitiveProcessor* createGLSLInstance(const GrGLSLCaps&) const override {
        return new GLSLProcessor();
    }

private:
    SkMatrix         fLocalMatrix;

    const Attribute* fInPosition;
    const Attribute* fInColor;
    const Attribute* fInRectEdge;
    const Attribute* fInWidthHeight;

    GR_DECLARE_GEOMETRY_PROCESSOR_TEST;

    typedef GrGeometryProcessor INHERITED;
};

GR_DEFINE_GEOMETRY_PROCESSOR_TEST(RectGeometryProcessor);

sk_sp<GrGeometryProcessor> RectGeometryProcessor::TestCreate(GrProcessorTestData* d) {
    return sk_sp<GrGeometryProcessor>(
        new RectGeometryProcessor(GrTest::TestMatrix(d->fRandom)));
}

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

class AnalyticRectBatch : public GrVertexBatch {
public:
    DEFINE_BATCH_CLASS_ID

    AnalyticRectBatch(GrColor color, const SkMatrix& viewMatrix, const SkRect& rect,
                      const SkRect& croppedRect, const SkRect& bounds)
        : INHERITED(ClassID())
        , fViewMatrixIfUsingLocalCoords(viewMatrix) {
        SkPoint center = SkPoint::Make(rect.centerX(), rect.centerY());
        viewMatrix.mapPoints(&center, 1);
        SkScalar halfWidth = viewMatrix.mapRadius(SkScalarHalf(rect.width()));
        SkScalar halfHeight = viewMatrix.mapRadius(SkScalarHalf(rect.height()));
        SkVector downDir = viewMatrix.mapVector(0.0f, 1.0f);
        downDir.normalize();

        SkRect deviceSpaceCroppedRect = croppedRect;
        viewMatrix.mapRect(&deviceSpaceCroppedRect);

        fGeoData.emplace_back(Geometry {color, center, downDir, halfWidth, halfHeight,
                                        deviceSpaceCroppedRect});

        this->setBounds(bounds, HasAABloat::kYes, IsZeroArea::kNo);
    }

    const char* name() const override { return "AnalyticRectBatch"; }

    SkString dumpInfo() const override {
        SkString string;
        for (int i = 0; i < fGeoData.count(); ++i) {
            string.appendf("Color: 0x%08x Rect [C:(%.2f, %.2f) D:<%.2f,%.3f> W/2:%.2f H/2:%.2f]\n",
                           fGeoData[i].fColor,
                           fGeoData[i].fCenter.x(), fGeoData[i].fCenter.y(),
                           fGeoData[i].fDownDir.x(), fGeoData[i].fDownDir.y(),
                           fGeoData[i].fHalfWidth,
                           fGeoData[i].fHalfHeight);
        }
        string.append(INHERITED::dumpInfo());
        return string;
    }

    void computePipelineOptimizations(GrInitInvariantOutput* color,
                                      GrInitInvariantOutput* coverage,
                                      GrBatchToXPOverrides* overrides) const override {
        // When this is called on a batch, there is only one geometry bundle
        color->setKnownFourComponents(fGeoData[0].fColor);
        coverage->setUnknownSingleComponent();
    }

private:
    void initBatchTracker(const GrXPOverridesForBatch& overrides) override {
        // Handle any overrides that affect our GP.
        overrides.getOverrideColorIfSet(&fGeoData[0].fColor);
        if (!overrides.readsLocalCoords()) {
            fViewMatrixIfUsingLocalCoords.reset();
        }
    }

    void onPrepareDraws(Target* target) const override {
        SkMatrix localMatrix;
        if (!fViewMatrixIfUsingLocalCoords.invert(&localMatrix)) {
            return;
        }

        // Setup geometry processor
        SkAutoTUnref<GrGeometryProcessor> gp(new RectGeometryProcessor(localMatrix));

        int instanceCount = fGeoData.count();
        size_t vertexStride = gp->getVertexStride();
        SkASSERT(vertexStride == sizeof(RectVertex));
        QuadHelper helper;
        RectVertex* verts = reinterpret_cast<RectVertex*>(helper.init(target, vertexStride,
                                                                      instanceCount));
        if (!verts) {
            return;
        }

        for (int i = 0; i < instanceCount; i++) {
            const Geometry& geom = fGeoData[i];

            GrColor  color       = geom.fColor;
            SkPoint  center      = geom.fCenter;
            SkVector downDir     = geom.fDownDir;
            SkScalar halfWidth   = geom.fHalfWidth;
            SkScalar halfHeight  = geom.fHalfHeight;
            SkRect   croppedRect = geom.fCroppedRect;

            SkVector rightDir;
            downDir.rotateCCW(&rightDir);

            verts[0].fPos = {croppedRect.fLeft, croppedRect.fTop};
            verts[0].fColor = color;
            verts[0].fCenter = center;
            verts[0].fDownDir = downDir;
            verts[0].fHalfWidth = halfWidth;
            verts[0].fHalfHeight = halfHeight;

            verts[1].fPos = {croppedRect.fRight, croppedRect.fTop};
            verts[1].fColor = color;
            verts[1].fCenter = center;
            verts[1].fDownDir = downDir;
            verts[1].fHalfWidth = halfWidth;
            verts[1].fHalfHeight = halfHeight;

            verts[2].fPos = {croppedRect.fRight, croppedRect.fBottom};
            verts[2].fColor = color;
            verts[2].fCenter = center;
            verts[2].fDownDir = downDir;
            verts[2].fHalfWidth = halfWidth;
            verts[2].fHalfHeight = halfHeight;

            verts[3].fPos = {croppedRect.fLeft, croppedRect.fBottom};
            verts[3].fColor = color;
            verts[3].fCenter = center;
            verts[3].fDownDir = downDir;
            verts[3].fHalfWidth = halfWidth;
            verts[3].fHalfHeight = halfHeight;

            verts += kVerticesPerQuad;
        }
        helper.recordDraw(target, gp);
    }

    bool onCombineIfPossible(GrBatch* t, const GrCaps& caps) override {
        AnalyticRectBatch* that = t->cast<AnalyticRectBatch>();
        if (!GrPipeline::CanCombine(*this->pipeline(), this->bounds(), *that->pipeline(),
                                    that->bounds(), caps)) {
            return false;
        }

        if (!fViewMatrixIfUsingLocalCoords.cheapEqualTo(that->fViewMatrixIfUsingLocalCoords)) {
            return false;
        }

        fGeoData.push_back_n(that->fGeoData.count(), that->fGeoData.begin());
        this->joinBounds(*that);
        return true;
    }

    struct Geometry {
        GrColor  fColor;
        SkPoint  fCenter;
        SkVector fDownDir;
        SkScalar fHalfWidth;
        SkScalar fHalfHeight;
        SkRect   fCroppedRect;
    };

    SkMatrix                     fViewMatrixIfUsingLocalCoords;
    SkSTArray<1, Geometry, true> fGeoData;

    typedef GrVertexBatch INHERITED;
};

GrDrawBatch* GrAnalyticRectBatch::CreateAnalyticRectBatch(GrColor color,
                                                          const SkMatrix& viewMatrix,
                                                          const SkRect& rect,
                                                          const SkRect& croppedRect,
                                                          const SkRect& bounds) {
    return new AnalyticRectBatch(color, viewMatrix, rect, croppedRect, bounds);
}

#ifdef GR_TEST_UTILS

DRAW_BATCH_TEST_DEFINE(AnalyticRectBatch) {
    SkMatrix viewMatrix = GrTest::TestMatrix(random);
    GrColor color = GrRandomColor(random);
    SkRect rect = GrTest::TestSquare(random);
    SkRect croppedRect = GrTest::TestSquare(random);
    SkRect bounds = GrTest::TestSquare(random);
    return new AnalyticRectBatch(color, viewMatrix, rect, croppedRect, bounds);
}

#endif
