/*
 * 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 "gm/gm.h"
#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.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/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradientShader.h"
#include "tools/ToolUtils.h"

static sk_sp<SkImage> make_image(SkCanvas* origCanvas, int w, int h) {
    SkImageInfo info = SkImageInfo::MakeN32Premul(w, h);
    auto        surface(ToolUtils::makeSurface(origCanvas, info));
    SkCanvas* canvas = surface->getCanvas();

    ToolUtils::draw_checkerboard(canvas, SK_ColorRED, SK_ColorGREEN, w / 10);
    return surface->makeImageSnapshot();
}

namespace skiagm {

class PerspShadersGM : public GM {
public:
    PerspShadersGM(bool doAA) : fDoAA(doAA) { }

protected:
    SkString onShortName() override {
        SkString name;
        name.printf("persp_shaders_%s",
                     fDoAA ? "aa" : "bw");
        return name;
    }

    SkISize onISize() override {
        return SkISize::Make(kCellSize*kNumCols, kCellSize*kNumRows);
    }

    void onOnceBeforeDraw() override {
        fBitmapImage = ToolUtils::create_checkerboard_image(
                kCellSize, kCellSize, SK_ColorBLUE, SK_ColorYELLOW, kCellSize / 10);

        SkPoint pts1[] = {
            { 0, 0 },
            { SkIntToScalar(kCellSize), SkIntToScalar(kCellSize) }
        };
        SkPoint pts2[] = {
            { 0, 0 },
            { 0, SkIntToScalar(kCellSize) }
        };
        constexpr SkColor colors[] = {
            SK_ColorRED, SK_ColorGREEN, SK_ColorRED, SK_ColorGREEN, SK_ColorRED
        };
        constexpr SkScalar pos[] = { 0, 0.25f, 0.5f, 0.75f, SK_Scalar1 };

        fLinearGrad1 = SkGradientShader::MakeLinear(pts1, colors, pos, std::size(colors),
                                                    SkTileMode::kClamp);
        fLinearGrad2 = SkGradientShader::MakeLinear(pts2, colors, pos, std::size(colors),
                                                    SkTileMode::kClamp);

        fPerspMatrix.reset();
        fPerspMatrix.setPerspY(SK_Scalar1 / 50);

        fPath.moveTo(0, 0);
        fPath.lineTo(0, SkIntToScalar(kCellSize));
        fPath.lineTo(kCellSize/2.0f, kCellSize/2.0f);
        fPath.lineTo(SkIntToScalar(kCellSize), SkIntToScalar(kCellSize));
        fPath.lineTo(SkIntToScalar(kCellSize), 0);
        fPath.close();
    }

    void drawRow(SkCanvas* canvas, const SkSamplingOptions& sampling) {
        SkPaint filterPaint;
        filterPaint.setAntiAlias(fDoAA);

        SkPaint pathPaint;
        pathPaint.setShader(fBitmapImage->makeShader(sampling));
        pathPaint.setAntiAlias(fDoAA);

        SkPaint gradPaint1;
        gradPaint1.setShader(fLinearGrad1);
        gradPaint1.setAntiAlias(fDoAA);
        SkPaint gradPaint2;
        gradPaint2.setShader(fLinearGrad2);
        gradPaint2.setAntiAlias(fDoAA);

        SkRect r = SkRect::MakeWH(SkIntToScalar(kCellSize), SkIntToScalar(kCellSize));

        canvas->save();

        canvas->save();
        canvas->concat(fPerspMatrix);
        canvas->drawImageRect(fBitmapImage, r, sampling, &filterPaint);
        canvas->restore();

        canvas->translate(SkIntToScalar(kCellSize), 0);
        canvas->save();
        canvas->concat(fPerspMatrix);
        canvas->drawImage(fImage.get(), 0, 0, sampling, &filterPaint);
        canvas->restore();

        canvas->translate(SkIntToScalar(kCellSize), 0);
        canvas->save();
        canvas->concat(fPerspMatrix);
        canvas->drawRect(r, pathPaint);
        canvas->restore();

        canvas->translate(SkIntToScalar(kCellSize), 0);
        canvas->save();
        canvas->concat(fPerspMatrix);
        canvas->drawPath(fPath, pathPaint);
        canvas->restore();

        canvas->translate(SkIntToScalar(kCellSize), 0);
        canvas->save();
        canvas->concat(fPerspMatrix);
        canvas->drawRect(r, gradPaint1);
        canvas->restore();

        canvas->translate(SkIntToScalar(kCellSize), 0);
        canvas->save();
        canvas->concat(fPerspMatrix);
        canvas->drawPath(fPath, gradPaint2);
        canvas->restore();

        canvas->restore();
    }

    void onDraw(SkCanvas* canvas) override {
        if (!fImage || !fImage->isValid(canvas->recordingContext())) {
            fImage = make_image(canvas, kCellSize, kCellSize);
        }

        this->drawRow(canvas, SkSamplingOptions(SkFilterMode::kNearest));
        canvas->translate(0, SkIntToScalar(kCellSize));
        this->drawRow(canvas, SkSamplingOptions(SkFilterMode::kLinear));
        canvas->translate(0, SkIntToScalar(kCellSize));
        this->drawRow(canvas, SkSamplingOptions(SkFilterMode::kLinear,
                                                SkMipmapMode::kNearest));
        canvas->translate(0, SkIntToScalar(kCellSize));
        this->drawRow(canvas, SkSamplingOptions(SkCubicResampler::Mitchell()));
        canvas->translate(0, SkIntToScalar(kCellSize));
        this->drawRow(canvas, SkSamplingOptions::Aniso(16));
        canvas->translate(0, SkIntToScalar(kCellSize));
    }
private:
    inline static constexpr int kCellSize = 50;
    inline static constexpr int kNumRows = 5;
    inline static constexpr int kNumCols = 6;

    bool            fDoAA;
    SkPath          fPath;
    sk_sp<SkShader> fLinearGrad1;
    sk_sp<SkShader> fLinearGrad2;
    SkMatrix        fPerspMatrix;
    sk_sp<SkImage>  fImage;
    sk_sp<SkImage>  fBitmapImage;

    using INHERITED = GM;
};
DEF_GM(return new PerspShadersGM(true);)
DEF_GM(return new PerspShadersGM(false);)
}  // namespace skiagm

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

#include "tools/Resources.h"

static SkPath make_path() {
    SkRandom rand;
    auto rand_pt = [&rand]() {
        auto x = rand.nextF();
        auto y = rand.nextF();
        return SkPoint{x * 400, y * 400};
    };

    SkPath path;
    for (int i = 0; i < 4; ++i) {
        SkPoint pts[6];
        for (auto& p : pts) {
            p = rand_pt();
        }
        path.moveTo(pts[0]).quadTo(pts[1], pts[2]).quadTo(pts[3], pts[4]).lineTo(pts[5]);
    }
    return path;
}

DEF_SIMPLE_GM(perspective_clip, canvas, 800, 800) {
    SkPath path = make_path();
    auto shader = GetResourceAsImage("images/mandrill_128.png")
                                    ->makeShader(SkSamplingOptions(), SkMatrix::Scale(3, 3));

    SkPaint paint;
    paint.setColor({0.75, 0.75, 0.75, 1});
    canvas->drawPath(path, paint);

    // This is a crazy perspective matrix, derived from halfplanes3, to draw a shape where
    // part of it is "behind" the viewer, hence showing the need for "half-plane" clipping
    // when in perspective.
    SkMatrix mx;
    const SkScalar array[] = {
        -1.7866f,  1.3357f, 273.0295f,
        -1.0820f,  1.3186f, 135.5196f,
        -0.0047f, -0.0015f,  2.1485f,
    };
    mx.set9(array);

    paint.setShader(shader);
    canvas->concat(mx);
    canvas->drawPath(path, paint);
}
