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

#include "gm.h"
#include "sk_tool_utils.h"

#include "SkSurface.h"

#if SK_SUPPORT_GPU

#include "GrContextPriv.h"
#include "GrProxyProvider.h"
#include "SkImage_Gpu.h"

static const int kNumMatrices = 6;
static const int kImageSize = 128;
static const int kLabelSize = 32;
static const int kNumLabels = 4;
static const int kInset = 16;

static const int kCellSize = kImageSize+2*kLabelSize;
static const int kGMWidth  = kNumMatrices*kCellSize;
static const int kGMHeight = 4*kCellSize;

static const SkPoint kPoints[kNumLabels] = {
    {          0, kImageSize },     // LL
    { kImageSize, kImageSize },     // LR
    {          0,          0 },     // UL
    { kImageSize,          0 },     // UR
};

static const SkMatrix kUVMatrices[kNumMatrices] = {
    SkMatrix::MakeAll( 0, -1, 1,
                      -1,  0, 1,
                       0,  0, 1),
    SkMatrix::MakeAll( 1,  0, 0,
                       0, -1, 1,
                       0,  0, 1),
    // flip x
    SkMatrix::MakeAll(-1,  0, 1,
                       0,  1, 0,
                       0,  0, 1),
    SkMatrix::MakeAll( 0,  1, 0,
                      -1,  0, 1,
                       0,  0, 1),
    // flip both x & y == rotate 180
    SkMatrix::MakeAll(-1,  0, 1,
                       0, -1, 1,
                       0,  0, 1),
    // identity
    SkMatrix::MakeAll(1,  0, 0,
                      0,  1, 0,
                      0,  0, 1)
};


// Create a fixed size text label like "LL" or "LR".
static sk_sp<SkImage> make_text_image(GrContext* context, const char* text, SkColor color) {
    SkPaint paint;
    sk_tool_utils::set_portable_typeface(&paint);
    paint.setAntiAlias(true);
    paint.setTextSize(32);
    paint.setColor(color);

    SkRect bounds;
    paint.measureText(text, strlen(text), &bounds);
    const SkMatrix mat = SkMatrix::MakeRectToRect(bounds, SkRect::MakeWH(kLabelSize, kLabelSize),
                                                  SkMatrix::kFill_ScaleToFit);

    const SkImageInfo ii = SkImageInfo::MakeN32Premul(kLabelSize, kLabelSize);
    sk_sp<SkSurface> surf = SkSurface::MakeRaster(ii);

    SkCanvas* canvas = surf->getCanvas();

    canvas->clear(SK_ColorWHITE);
    canvas->concat(mat);
    canvas->drawText(text, strlen(text), 0, 0, paint);

    sk_sp<SkImage> image = surf->makeImageSnapshot();

    return image->makeTextureImage(context, nullptr);
}

static SkColor swap_red_and_blue(SkColor c) {
    return SkColorSetRGB(SkColorGetB(c), SkColorGetG(c), SkColorGetR(c));
}

// Create an image with each corner marked w/ "LL", "LR", etc., with the origin either bottom-left
// or top-left.
static sk_sp<SkImage> make_reference_image(GrContext* context,
                                           const SkTArray<sk_sp<SkImage>>& labels,
                                           bool bottomLeftOrigin) {
    GrProxyProvider* proxyProvider = context->contextPriv().proxyProvider();
    SkASSERT(kNumLabels == labels.count());

    SkImageInfo ii = SkImageInfo::Make(kImageSize, kImageSize,
                                       kN32_SkColorType, kOpaque_SkAlphaType);
    SkBitmap bm;
    bm.allocPixels(ii);
    SkCanvas canvas(bm);

    canvas.clear(SK_ColorWHITE);
    for (int i = 0; i < kNumLabels; ++i) {
        canvas.drawImage(labels[i],
                         0.0 != kPoints[i].fX ? kPoints[i].fX-kLabelSize-kInset : kInset,
                         0.0 != kPoints[i].fY ? kPoints[i].fY-kLabelSize-kInset : kInset);
    }

    GrSurfaceDesc desc;
    desc.fOrigin = kTopLeft_GrSurfaceOrigin;
    desc.fWidth = kImageSize;
    desc.fHeight = kImageSize;
    desc.fConfig = kRGBA_8888_GrPixelConfig;

    if (bottomLeftOrigin) {
        // Note that Ganesh will flip the data when it is uploaded
        desc.fOrigin = kBottomLeft_GrSurfaceOrigin;
    }

    if (kN32_SkColorType == kBGRA_8888_SkColorType) {
        // We're playing a game here and uploading N32 data into an RGB dest. We might have
        // to swap red & blue to compensate.
        for (int y = 0; y < bm.height(); ++y) {
            uint32_t *sl = bm.getAddr32(0, y);
            for (int x = 0; x < bm.width(); ++x) {
                sl[x] = swap_red_and_blue(sl[x]);
            }
        }
    }

    sk_sp<GrTextureProxy> proxy = proxyProvider->createTextureProxy(desc, SkBudgeted::kYes,
                                                                    bm.getPixels(), bm.rowBytes());
    if (!proxy) {
        return nullptr;
    }

    return sk_make_sp<SkImage_Gpu>(context, kNeedNewImageUniqueID, kOpaque_SkAlphaType,
                                   std::move(proxy), nullptr, SkBudgeted::kYes);
}

// Here we're converting from a matrix that is intended for UVs to a matrix that is intended
// for rect geometry used for a drawImage call. They are, in some sense, inverses of each
// other but we also need a scale to map from the [0..1] uv range to the actual size of
// image.
static bool UVMatToGeomMatForImage(SkMatrix* geomMat, const SkMatrix& uvMat) {

    const SkMatrix yFlip = SkMatrix::MakeAll(1, 0, 0, 0, -1, 1, 0, 0, 1);

    SkMatrix tmp = uvMat;
    tmp.preConcat(yFlip);
    tmp.preScale(1.0f/kImageSize, 1.0f/kImageSize);

    tmp.postConcat(yFlip);
    tmp.postScale(kImageSize, kImageSize);

    return tmp.invert(geomMat);
}

// This GM exercises drawImage with a set of matrices that use an unusual amount of flips and
// rotates.
class FlippityGM : public skiagm::GM {
public:
    FlippityGM() {
        this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC));
    }

protected:

    SkString onShortName() override {
        return SkString("flippity");
    }

    SkISize onISize() override {
        return SkISize::Make(kGMWidth, kGMHeight);
    }

    // Draw the reference image and the four corner labels in the matrix's coordinate space
    void drawImageWithMatrixAndLabels(SkCanvas* canvas, SkImage* image, int matIndex,
                                      bool drawSubset, bool drawScaled) {
        static const SkRect kSubsets[kNumMatrices] = {
            SkRect::MakeXYWH(kInset, 0, kImageSize-kInset, kImageSize),
            SkRect::MakeXYWH(0, kInset, kImageSize, kImageSize-kInset),
            SkRect::MakeXYWH(0, 0, kImageSize-kInset, kImageSize),
            SkRect::MakeXYWH(0, 0, kImageSize, kImageSize-kInset),
            SkRect::MakeXYWH(kInset/2, kInset/2, kImageSize-kInset, kImageSize-kInset),
            SkRect::MakeXYWH(kInset, kInset, kImageSize-2*kInset, kImageSize-2*kInset),
        };

        SkMatrix imageGeomMat;
        SkAssertResult(UVMatToGeomMatForImage(&imageGeomMat, kUVMatrices[matIndex]));

        canvas->save();

            // draw the reference image
            canvas->concat(imageGeomMat);
            if (drawSubset) {
                canvas->drawImageRect(image, kSubsets[matIndex],
                                      drawScaled ? SkRect::MakeWH(kImageSize, kImageSize)
                                                 : kSubsets[matIndex],
                                      nullptr, SkCanvas::kFast_SrcRectConstraint);
            } else {
                canvas->drawImage(image, 0, 0);
            }

            // draw the labels
            for (int i = 0; i < kNumLabels; ++i) {
                canvas->drawImage(fLabels[i],
                                    0.0f == kPoints[i].fX ? -kLabelSize : kPoints[i].fX,
                                    0.0f == kPoints[i].fY ? -kLabelSize : kPoints[i].fY);
            }
        canvas->restore();
    }

    void drawRow(GrContext* context, SkCanvas* canvas,
                 bool bottomLeftImage, bool drawSubset, bool drawScaled) {

        sk_sp<SkImage> referenceImage = make_reference_image(context, fLabels, bottomLeftImage);

        canvas->save();
            canvas->translate(kLabelSize, kLabelSize);

            for (int i = 0; i < kNumMatrices; ++i) {
                this->drawImageWithMatrixAndLabels(canvas, referenceImage.get(), i,
                                                   drawSubset, drawScaled);
                canvas->translate(kCellSize, 0);
            }
        canvas->restore();
    }

    void makeLabels(GrContext* context) {
        static const char* kLabelText[kNumLabels] = { "LL", "LR", "UL", "UR" };

        static const SkColor kLabelColors[kNumLabels] = {
            SK_ColorRED,
            SK_ColorGREEN,
            SK_ColorBLUE,
            SK_ColorCYAN
        };

        SkASSERT(!fLabels.count());
        for (int i = 0; i < kNumLabels; ++i) {
            fLabels.push_back(make_text_image(context, kLabelText[i], kLabelColors[i]));
        }
        SkASSERT(kNumLabels == fLabels.count());
    }

    void onDraw(SkCanvas* canvas) override {
        GrContext* context = canvas->getGrContext();
        if (!context) {
            skiagm::GM::DrawGpuOnlyMessage(canvas);
            return;
        }

        this->makeLabels(context);

        canvas->save();

        // Top row gets TL image
        this->drawRow(context, canvas, false, false, false);

        canvas->translate(0, kCellSize);

        // Bottom row gets BL image
        this->drawRow(context, canvas, true, false, false);

        canvas->translate(0, kCellSize);

        // Third row gets subsets of BL images
        this->drawRow(context, canvas, true, true, false);

        canvas->translate(0, kCellSize);

        // Fourth row gets scaled subsets of BL images
        this->drawRow(context, canvas, true, true, true);

        canvas->restore();

        // separator grid
        for (int i = 0; i < 4; ++i) {
            canvas->drawLine(0, i * kCellSize, kGMWidth, i * kCellSize, SkPaint());
        }
        for (int i = 0; i < kNumMatrices; ++i) {
            canvas->drawLine(i * kCellSize, 0, i * kCellSize, kGMHeight, SkPaint());
        }
    }

private:
    SkTArray<sk_sp<SkImage>> fLabels;

    typedef GM INHERITED;
};

DEF_GM(return new FlippityGM;)

#endif