/*
 * Copyright 2014 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/SkColorFilter.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkTableColorFilter.h"
#include "include/gpu/GrContext.h"

constexpr int kTestRectSize = 50;
constexpr int kDetectorGreenValue = 50;

// Below are few functions to install "detector" color filters. The filter is there to assert that
// the color value it sees is the expected. It will trigger only with kDetectorGreenValue, and
// turn that value into full green. The idea is that if an optimization incorrectly changes
// kDetectorGreenValue and then the incorrect value is observable by some part of the drawing
// pipeline, that pixel will remain empty.

static sk_sp<SkColorFilter> make_detector_color_filter() {
    uint8_t tableA[256] = { 0, };
    uint8_t tableR[256] = { 0, };
    uint8_t tableG[256] = { 0, };
    uint8_t tableB[256] = { 0, };
    tableA[255] = 255;
    tableG[kDetectorGreenValue] = 255;
    return SkTableColorFilter::MakeARGB(tableA, tableR, tableG, tableB);
}

// This detector detects that color filter phase of the pixel pipeline receives the correct value.
static void install_detector_color_filter(SkPaint* drawPaint) {
    drawPaint->setColorFilter(make_detector_color_filter());
}

// This detector detects that image filter phase of the pixel pipeline receives the correct value.
static void install_detector_image_filter(SkPaint* drawPaint) {
    drawPaint->setImageFilter(SkImageFilters::ColorFilter(
            make_detector_color_filter(), drawPaint->refImageFilter()));
}

static void no_detector_install(SkPaint*) {
}

typedef void(*InstallDetectorFunc)(SkPaint*);


// Draws an pattern that can be optimized by alpha folding outer savelayer alpha value to
// inner draw. Since we know that folding will happen to the inner draw, install a detector
// to make sure that optimization does not change anything observable.
static void draw_save_layer_draw_rect_restore_sequence(SkCanvas* canvas, SkColor shapeColor,
                                                       InstallDetectorFunc installDetector) {
    SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize)));
    SkPaint layerPaint;
    layerPaint.setColor(SkColorSetARGB(128, 0, 0, 0));
    canvas->saveLayer(&targetRect, &layerPaint);
        SkPaint drawPaint;
        drawPaint.setColor(shapeColor);
        installDetector(&drawPaint);
        canvas->drawRect(targetRect, drawPaint);
    canvas->restore();
}

// Draws an pattern that can be optimized by alpha folding outer savelayer alpha value to
// inner draw. A variant where the draw is not uniform color.
static void draw_save_layer_draw_bitmap_restore_sequence(SkCanvas* canvas, SkColor shapeColor,
                                                         InstallDetectorFunc installDetector) {
    SkBitmap bitmap;
    bitmap.allocN32Pixels(kTestRectSize, kTestRectSize);
    bitmap.eraseColor(shapeColor);
    {
        // Make the bitmap non-uniform color, so that it can not be optimized as uniform drawRect.
        SkCanvas canvas(bitmap);
        SkPaint p;
        p.setColor(SK_ColorWHITE);
        SkASSERT(shapeColor != SK_ColorWHITE);
        canvas.drawRect(SkRect::MakeWH(SkIntToScalar(7), SkIntToScalar(7)), p);
    }

    SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize)));
    SkPaint layerPaint;
    layerPaint.setColor(SkColorSetARGB(129, 0, 0, 0));
    canvas->saveLayer(&targetRect, &layerPaint);
        SkPaint drawPaint;
        installDetector(&drawPaint);
        canvas->drawBitmap(bitmap, SkIntToScalar(0), SkIntToScalar(0), &drawPaint);
    canvas->restore();
}

// Draws an pattern that can be optimized by alpha folding outer savelayer alpha value to
// inner savelayer. We know that alpha folding happens to inner savelayer, so add detector there.
static void draw_svg_opacity_and_filter_layer_sequence(SkCanvas* canvas, SkColor shapeColor,
                                                       InstallDetectorFunc installDetector) {

    SkRect targetRect(SkRect::MakeWH(SkIntToScalar(kTestRectSize), SkIntToScalar(kTestRectSize)));
    sk_sp<SkPicture> shape;
    {
        SkPictureRecorder recorder;
        SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kTestRectSize + 2),
                                                   SkIntToScalar(kTestRectSize + 2));
        SkPaint shapePaint;
        shapePaint.setColor(shapeColor);
        canvas->drawRect(targetRect, shapePaint);
        shape = recorder.finishRecordingAsPicture();
    }

    SkPaint layerPaint;
    layerPaint.setColor(SkColorSetARGB(130, 0, 0, 0));
    canvas->saveLayer(&targetRect, &layerPaint);
        canvas->save();
            canvas->clipRect(targetRect);
            SkPaint drawPaint;
            drawPaint.setImageFilter(SkImageFilters::Picture(shape));
            installDetector(&drawPaint);
            canvas->saveLayer(&targetRect, &drawPaint);
            canvas->restore();
        canvas->restore();
    canvas->restore();
}

// Draws two columns of rectangles. The test is correct when:
//  - Left and right columns always identical
//  - First 3 rows are green, with a white dent in the middle row
//  - Next 6 rows are green, with a grey dent in the middle row
//    (the grey dent is from the color filter removing everything but the "good" green, see below)
//  - Last 6 rows are grey
DEF_SIMPLE_GM(recordopts, canvas, (kTestRectSize+1)*2, (kTestRectSize+1)*15) {
    GrContext* context = canvas->getGrContext();
    canvas->clear(SK_ColorTRANSPARENT);

    typedef void (*TestVariantSequence)(SkCanvas*, SkColor, InstallDetectorFunc);
    TestVariantSequence funcs[] = {
        draw_save_layer_draw_rect_restore_sequence,
        draw_save_layer_draw_bitmap_restore_sequence,
        draw_svg_opacity_and_filter_layer_sequence,
    };

    // Draw layer-related sequences that can be optimized by folding the opacity layer alpha to
    // the inner draw operation. This tries to trigger the optimization, and relies on gm diffs
    // to keep the color value correct over time.

    // Draws two green rects side by side: one is without the optimization, the other is with
    // the optimization applied.

    SkColor shapeColor = SkColorSetARGB(255, 0, 255, 0);
    for (size_t k = 0; k < SK_ARRAY_COUNT(funcs); ++k) {
        canvas->save();

        TestVariantSequence drawTestSequence = funcs[k];
        drawTestSequence(canvas, shapeColor, no_detector_install);
        if (context) {
            context->flush();
        }
        canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0));
        {
            SkPictureRecorder recorder;
            drawTestSequence(recorder.beginRecording(SkIntToScalar(kTestRectSize),
                                                     SkIntToScalar(kTestRectSize)),
                             shapeColor, no_detector_install);
            recorder.finishRecordingAsPicture()->playback(canvas);
            if (context) {
                context->flush();
            }
        }
        canvas->restore();
        canvas->translate(SkIntToScalar(0), SkIntToScalar(kTestRectSize) + SkIntToScalar(1));
    }

    // Draw the same layer related sequences, but manipulate the sequences so that the result is
    // incorrect if the alpha is folded or folded incorrectly. These test the observable state
    // throughout the pixel pipeline, and thus may turn off the optimizations (this is why we
    // trigger the optimizations above).

    // Draws two green rects side by side: one is without the optimization, the other is with
    // the possibility that optimization is applied.
    // At the end, draws the same patterns in translucent black. This tests that the detectors
    // work, eg. that if the value the detector sees is wrong, the resulting image shows this.
    SkColor shapeColors[] = {
        SkColorSetARGB(255, 0, kDetectorGreenValue, 0),
        SkColorSetARGB(255, 0, (kDetectorGreenValue + 1), 0) // This tests that detectors work.
    };

    InstallDetectorFunc detectorInstallFuncs[] = {
        install_detector_image_filter,
        install_detector_color_filter
    };

    for (size_t i = 0; i < SK_ARRAY_COUNT(shapeColors); ++i) {
        shapeColor = shapeColors[i];
        for (size_t j = 0; j < SK_ARRAY_COUNT(detectorInstallFuncs); ++j) {
            InstallDetectorFunc detectorInstallFunc = detectorInstallFuncs[j];
            for (size_t k = 0; k < SK_ARRAY_COUNT(funcs); ++k) {
                TestVariantSequence drawTestSequence = funcs[k];
                canvas->save();
                drawTestSequence(canvas, shapeColor, detectorInstallFunc);
                if (context) {
                    context->flush();
                }
                canvas->translate(SkIntToScalar(kTestRectSize) + SkIntToScalar(1), SkIntToScalar(0));
                {
                    SkPictureRecorder recorder;
                    drawTestSequence(recorder.beginRecording(SkIntToScalar(kTestRectSize),
                                                             SkIntToScalar(kTestRectSize)),
                                     shapeColor, detectorInstallFunc);
                    recorder.finishRecordingAsPicture()->playback(canvas);
                    if (context) {
                        context->flush();
                    }
                }

                canvas->restore();
                canvas->translate(SkIntToScalar(0), SkIntToScalar(kTestRectSize) + SkIntToScalar(1));
            }

        }
    }
}