/*
 * 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/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/effects/SkPerlinNoiseShader.h"

#include <utility>

namespace {

enum class Type {
    kFractalNoise,
    kTurbulence,
};

class PerlinNoiseGM : public skiagm::GM {
    SkISize fSize = {80, 80};

    void onOnceBeforeDraw() override { this->setBGColor(0xFF000000); }

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

    SkISize onISize() override { return {200, 500}; }

    void drawRect(SkCanvas* canvas, int x, int y, const SkPaint& paint, const SkISize& size) {
        canvas->save();
        canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
        SkRect r = SkRect::MakeWH(SkIntToScalar(size.width()),
                                  SkIntToScalar(size.height()));
        canvas->drawRect(r, paint);
        canvas->restore();
    }

    void test(SkCanvas* canvas, int x, int y, Type type,
              float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed,
              bool stitchTiles) {
        SkISize tileSize = SkISize::Make(fSize.width() / 2, fSize.height() / 2);
        sk_sp<SkShader> shader;
        switch (type) {
            case Type::kFractalNoise:
                shader = SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX,
                                                               baseFrequencyY,
                                                               numOctaves,
                                                               seed,
                                                               stitchTiles ? &tileSize : nullptr);
                break;
            case Type::kTurbulence:
                shader = SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX,
                                                             baseFrequencyY,
                                                             numOctaves,
                                                             seed,
                                                             stitchTiles ? &tileSize : nullptr);
                break;
        }
        SkPaint paint;
        paint.setShader(std::move(shader));
        if (stitchTiles) {
            drawRect(canvas, x, y, paint, tileSize);
            x += tileSize.width();
            drawRect(canvas, x, y, paint, tileSize);
            y += tileSize.width();
            drawRect(canvas, x, y, paint, tileSize);
            x -= tileSize.width();
            drawRect(canvas, x, y, paint, tileSize);
        } else {
            drawRect(canvas, x, y, paint, fSize);
        }
    }

    void onDraw(SkCanvas* canvas) override {
        canvas->clear(SK_ColorBLACK);
        test(canvas,   0,   0, Type::kFractalNoise,
             0.1f, 0.1f, 0, 0, false);
        test(canvas, 100,   0, Type::kTurbulence,
             0.1f, 0.1f, 0, 0, false);

        test(canvas,   0, 100, Type::kFractalNoise,
             0.1f, 0.1f, 2, 0, false);
        test(canvas, 100, 100, Type::kFractalNoise,
             0.05f, 0.1f, 1, 0, true);

        test(canvas,   0, 200, Type::kTurbulence,
             0.1f, 0.1f, 1, 0, true);
        test(canvas, 100, 200, Type::kTurbulence,
             0.2f, 0.4f, 5, 0, false);

        test(canvas,   0, 300, Type::kFractalNoise,
             0.1f, 0.1f, 3, 1, false);
        test(canvas, 100, 300, Type::kFractalNoise,
             0.1f, 0.1f, 3, 4, false);

        canvas->scale(0.75f, 1.0f);

        test(canvas,   0, 400, Type::kFractalNoise,
             0.1f, 0.1f, 2, 0, false);
        test(canvas, 100, 400, Type::kFractalNoise,
             0.1f, 0.05f, 1, 0, true);
    }

private:
    using INHERITED = GM;
};

class PerlinNoiseGM2 : public skiagm::GM {
    SkISize fSize = {80, 80};

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

    SkISize onISize() override { return {640, 480}; }

    void install(SkPaint* paint, Type type,
              float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed,
              bool stitchTiles) {
        sk_sp<SkShader> shader = (type == Type::kFractalNoise) ?
            SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves,
                                                  seed, stitchTiles ? &fSize : nullptr) :
            SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves,
                                                seed, stitchTiles ? &fSize : nullptr);
        paint->setShader(std::move(shader));
    }

    void onDraw(SkCanvas* canvas) override {
        canvas->translate(10, 10);

        SkPaint paint;
        this->install(&paint, Type::kFractalNoise, 0.1f, 0.1f, 2, 0, false);

        const SkScalar w = SkIntToScalar(fSize.width());
        const SkScalar h = SkIntToScalar(fSize.height());

        SkRect r = SkRect::MakeWH(w, h);
        canvas->drawRect(r, paint);

        canvas->save();
        canvas->translate(w * 5/4, 0);
        canvas->drawRect(r, paint);
        canvas->restore();

        canvas->save();
        canvas->translate(0, h + 10);
        canvas->scale(2, 2);
        canvas->drawRect(r, paint);
        canvas->restore();

        canvas->save();
        canvas->translate(w + 100, h + 10);
        canvas->scale(2, 2);
        canvas->drawRect(r, paint);
        canvas->restore();

        // The next row should draw the same as the previous, even though we are using a local
        // matrix instead of the canvas.

        canvas->translate(0, h * 2 + 10);

        SkMatrix lm;
        lm.setScale(2, 2);
        paint.setShader(paint.getShader()->makeWithLocalMatrix(lm));
        r.fRight += r.width();
        r.fBottom += r.height();

        canvas->save();
        canvas->translate(0, h + 10);
        canvas->drawRect(r, paint);
        canvas->restore();

        canvas->save();
        canvas->translate(w + 100, h + 10);
        canvas->drawRect(r, paint);
        canvas->restore();
    }
};

} // namespace

DEF_GM( return new PerlinNoiseGM; )
DEF_GM( return new PerlinNoiseGM2; )