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

#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradient.h"
#include "include/private/base/SkFloatingPoint.h"

constexpr SkColor4f gColors[] = {
    SkColors::kRed, SkColors::kYellow
};

static SkGradient make_grad(SkTileMode tm) {
    return {{gColors, {}, tm}, {}};
}

// These annoying defines are necessary, because the only other alternative
// is to use SkIntToScalar(...) everywhere.
constexpr SkScalar sZero = 0;
constexpr SkScalar sHalf = SK_ScalarHalf;
constexpr SkScalar sOne = SK_Scalar1;

// These arrays define the gradient stop points
// as x1, y1, x2, y2 per gradient to draw.
constexpr SkPoint linearPts[][2] = {
    {{sZero, sZero}, {sOne,  sZero}},
    {{sZero, sZero}, {sZero, sOne}},
    {{sOne,  sZero}, {sZero, sZero}},
    {{sZero, sOne},  {sZero, sZero}},

    {{sZero, sZero}, {sOne,  sOne}},
    {{sOne,  sOne},  {sZero, sZero}},
    {{sOne,  sZero}, {sZero, sOne}},
    {{sZero, sOne},  {sOne,  sZero}}
};

constexpr SkPoint radialPts[][2] = {
    {{sZero, sHalf}, {sOne,  sHalf}},
    {{sHalf, sZero}, {sHalf, sOne}},
    {{sOne,  sHalf}, {sZero, sHalf}},
    {{sHalf, sOne},  {sHalf, sZero}},

    {{sZero, sZero}, {sOne,  sOne}},
    {{sOne,  sOne},  {sZero, sZero}},
    {{sOne,  sZero}, {sZero, sOne}},
    {{sZero, sOne},  {sOne,  sZero}}
};

// These define the pixels allocated to each gradient image.
constexpr SkScalar TESTGRID_X = SkIntToScalar(200);
constexpr SkScalar TESTGRID_Y = SkIntToScalar(200);

constexpr int IMAGES_X = 4;             // number of images per row

static sk_sp<SkShader> make_linear_gradient(const SkPoint pts[2], const SkMatrix& localMatrix) {
    return SkShaders::LinearGradient(pts, make_grad(SkTileMode::kClamp), &localMatrix);
}

static sk_sp<SkShader> make_radial_gradient(const SkPoint pts[2], const SkMatrix& localMatrix) {
    SkPoint center;
    center.set(sk_float_midpoint(pts[0].fX, pts[1].fX),
               sk_float_midpoint(pts[0].fY, pts[1].fY));
    float radius = (center - pts[0]).length();
    return SkShaders::RadialGradient(center, radius, make_grad(SkTileMode::kClamp), &localMatrix);
}

static void draw_gradients(SkCanvas* canvas,
                           sk_sp<SkShader> (*makeShader)(const SkPoint[2], const SkMatrix&),
                           const SkPoint ptsArray[][2], int numImages) {
    // Use some nice prime numbers for the rectangle and matrix with
    // different scaling along the x and y axes (which is the bug this
    // test addresses, where incorrect order of operations mixed up the axes)
    SkRect rectGrad = {
        SkIntToScalar(43),  SkIntToScalar(61),
        SkIntToScalar(181), SkIntToScalar(167) };
    SkMatrix shaderMat;
    shaderMat.setScale(rectGrad.width(), rectGrad.height());
    shaderMat.postTranslate(rectGrad.left(), rectGrad.top());

    canvas->save();
    for (int i = 0; i < numImages; i++) {
        // Advance line downwards if necessary.
        if (i % IMAGES_X == 0 && i != 0) {
            canvas->restore();
            canvas->translate(0, TESTGRID_Y);
            canvas->save();
        }

        SkPaint paint;
        paint.setShader(makeShader(*ptsArray, shaderMat));
        canvas->drawRect(rectGrad, paint);

        // Advance to next position.
        canvas->translate(TESTGRID_X, 0);
        ptsArray++;
    }
    canvas->restore();
}

DEF_SIMPLE_GM_BG(gradient_matrix, canvas, 800, 800, 0xFFDDDDDD) {
        draw_gradients(canvas, &make_linear_gradient,
                      linearPts, std::size(linearPts));

        canvas->translate(0, TESTGRID_Y);

        draw_gradients(canvas, &make_radial_gradient,
                      radialPts, std::size(radialPts));
}
