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

#include "samplecode/Sample.h"

#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkFont.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"

#include "tools/ToolUtils.h"

static constexpr float kLineHeight = 16.f;
static constexpr float kLineInset = 8.f;

static float print_size(SkCanvas* canvas, const char* prefix, const SkIRect& rect,
                        float x, float y, const SkFont& font, const SkPaint& paint) {
    canvas->drawString(prefix, x, y, font, paint);
    y += kLineHeight;
    SkString sz;
    sz.appendf("%d x %d", rect.width(), rect.height());
    canvas->drawString(sz, x, y, font, paint);
    return y + kLineHeight;
}

static float print_info(SkCanvas* canvas, const SkIRect& origLayerBounds,
                        const SkIRect& localLayerBounds, const SkIRect& filterInputBounds,
                        const SkIRect& devLayerBounds) {
    SkFont font(nullptr, 12);
    SkPaint text;
    text.setAntiAlias(true);

    float y = kLineHeight;

    text.setColor(SK_ColorBLACK);
    y = print_size(canvas, "Orig layer", origLayerBounds, kLineInset, y, font, text);
    text.setColor(SK_ColorRED);
    y = print_size(canvas, "Filter layer", localLayerBounds, kLineInset, y, font, text);
    text.setColor(SK_ColorBLUE);
    y = print_size(canvas, "Filter input", filterInputBounds, kLineInset, y, font, text);
    text.setColor(SK_ColorMAGENTA);
    y = print_size(canvas, "Backdrop size", devLayerBounds, kLineInset, y, font, text);

    return y;
}

static SkPaint line_paint(SkScalar width, SkColor color) {
    SkPaint paint;
    paint.setColor(color);
    paint.setStrokeWidth(width);
    paint.setStyle(SkPaint::kStroke_Style);
    paint.setAntiAlias(true);
    return paint;
}

class BackdropBoundsSample : public Sample {
public:
    BackdropBoundsSample() {}

    void onDrawContent(SkCanvas* canvas) override {
        SkMatrix ctm = canvas->getTotalMatrix();

        // This decomposition is for the backdrop filtering, and does not represent the CTM that
        // the layer actually uses (unless it also has a filter during restore).
        SkMatrix toGlobal, layerMatrix;
        SkSize scale;
        if (ctm.isScaleTranslate()) {
            // No decomposition needed
            toGlobal = SkMatrix::I();
            layerMatrix = ctm;
        } else if (ctm.decomposeScale(&scale, &toGlobal)) {
            layerMatrix = SkMatrix::MakeScale(scale.fWidth, scale.fHeight);
        } else {
            toGlobal = ctm;
            layerMatrix = SkMatrix::I();
        }

        SkMatrix fromGlobal;
        if (!toGlobal.invert(&fromGlobal)) {
            SkDebugf("Unable to invert CTM\n");
            return;
        }

        // The local content, e.g. what would be submitted to drawRect
        const SkRect localContentRect = SkRect::MakeLTRB(45.5f, 23.123f, 150.f, 140.45f);
        canvas->drawRect(localContentRect, line_paint(0.f, SK_ColorBLACK));

        canvas->save();
        // The layer bounds of the content, this is the size of the actual layer and does not
        // reflect the backdrop specific decomposition.
        canvas->setMatrix(SkMatrix::I());
        SkIRect origLayerBounds = ctm.mapRect(localContentRect).roundOut();
        canvas->drawRect(SkRect::Make(origLayerBounds), line_paint(1.f, SK_ColorBLACK));

        // Have to undo the full CTM transform on the layer bounds to get the layer bounds
        // for the specific backdrop filter decomposition
        canvas->setMatrix(toGlobal);
        SkIRect layerBounds = fromGlobal.mapRect(SkRect::Make(origLayerBounds)).roundOut();
        canvas->drawRect(SkRect::Make(layerBounds), line_paint(0.5f, SK_ColorRED));

        // Input bounds for the backdrop filter to cover the actual layer bounds (emulate some
        // blur that must outset by 5px for reading on the edge).
        SkIRect filterInputBounds = layerBounds;
        filterInputBounds.outset(5, 5);
        canvas->drawRect(SkRect::Make(filterInputBounds), line_paint(1.f, SK_ColorBLUE));

        // The destination bounds that must be snapped in order to transform and fill the
        // filterInputBounds
        canvas->setMatrix(SkMatrix::I());
        SkIRect devLayerBounds = toGlobal.mapRect(SkRect::Make(filterInputBounds)).roundOut();
        canvas->drawRect(SkRect::Make(devLayerBounds), line_paint(2.f, SK_ColorMAGENTA));

        // The destination bounds mapped back into the layer space, which should cover 'layerBounds'
        SkPath backdropCoveringBounds;

        // Add axis lines, to show perspective distortion
        SkIRect local = fromGlobal.mapRect(SkRect::Make(devLayerBounds)).roundOut();
        static int kAxisSpace = 10;
        for (int y = local.fTop + kAxisSpace; y <= local.fBottom - kAxisSpace; y += kAxisSpace) {
            backdropCoveringBounds.moveTo(local.fLeft, y);
            backdropCoveringBounds.lineTo(local.fRight, y);
        }
        for (int x = local.fLeft + kAxisSpace; x <= local.fRight - kAxisSpace; x += kAxisSpace) {
            backdropCoveringBounds.moveTo(x, local.fTop);
            backdropCoveringBounds.lineTo(x, local.fBottom);
        }

        canvas->setMatrix(toGlobal);
        canvas->drawPath(backdropCoveringBounds, line_paint(0.f, SK_ColorGREEN));

        canvas->resetMatrix();
        print_info(canvas, origLayerBounds, layerBounds, filterInputBounds, devLayerBounds);

        canvas->restore();
    }

    SkString name() override { return SkString("BackdropBounds"); }

private:

    typedef Sample INHERITED;
};

DEF_SAMPLE(return new BackdropBoundsSample();)
