/*
 * Copyright 2020 Google LLC
 *
 * 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/android/SkAnimatedImage.h"
#include "include/codec/SkAndroidCodec.h"
#include "include/codec/SkCodec.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkData.h"
#include "include/core/SkImage.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathTypes.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkRRect.h"
#include "tools/Resources.h"

static sk_sp<SkPicture> post_processor(const SkRect& bounds) {
    int radius = (bounds.width() + bounds.height()) / 6;
    SkPathBuilder pathBuilder;
    pathBuilder.setFillType(SkPathFillType::kInverseEvenOdd)
               .addRRect(SkRRect::MakeRectXY(bounds, radius, radius));

    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setColor(SK_ColorTRANSPARENT);
    paint.setBlendMode(SkBlendMode::kSrc);

    SkPictureRecorder recorder;
    auto* canvas = recorder.beginRecording(bounds);
    canvas->drawPath(pathBuilder.detach(), paint);
    return recorder.finishRecordingAsPicture();
}

class AnimatedImageGM : public skiagm::GM {
    const char*   fPath;
    const char*   fName;
    const int     fStep;
    const SkIRect fCropRect;
    SkISize       fSize;
    int           fTranslate;
    sk_sp<SkData> fData;

    static const int kMaxFrames = 2;

    void init() {
        if (!fData) {
            fData = GetResourceAsData(fPath);
            auto codec = SkCodec::MakeFromData(fData);
            auto dimensions = codec->dimensions();

            fTranslate = std::max(dimensions.width(), dimensions.height()) // may be rotated
                         * 1.25f    // will be scaled up
                         + 2;       // padding

            fSize = { fTranslate * kMaxFrames
                                 * 2    // crop and no-crop
                                 * 2,   // post-process and no post-process
                      fTranslate * 4    // 4 scales
                                 * 2 }; // makePictureSnapshot and getCurrentFrame
        }
    }
public:
    AnimatedImageGM(const char* path, const char* name, int step, SkIRect cropRect)
        : fPath(path)
        , fName(name)
        , fStep(step)
        , fCropRect(cropRect)
        , fSize{0, 0}
        , fTranslate(0)
    {}
    ~AnimatedImageGM() override = default;

    SkString getName() const override { return SkStringPrintf("%s_animated_image", fName); }

    SkISize getISize() override {
        this->init();
        return fSize;
    }

    void onDraw(SkCanvas* canvas) override {
        this->init();
        for (bool usePic : { true, false }) {
            auto drawProc = [canvas, usePic](const sk_sp<SkAnimatedImage>& animatedImage) {
                if (usePic) {
                    sk_sp<SkPicture> pic = animatedImage->makePictureSnapshot();
                    canvas->drawPicture(pic);
                } else {
                    auto image = animatedImage->getCurrentFrame();
                    canvas->drawImage(image, 0, 0);
                }
            };
            for (float scale : { 1.25f, 1.0f, .75f, .5f }) {
                canvas->save();
                for (bool doCrop : { false, true }) {
                    for (bool doPostProcess : { false, true }) {
                        auto codec = SkCodec::MakeFromData(fData);
                        const auto origin = codec->getOrigin();
                        auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec));
                        auto info = androidCodec->getInfo();
                        const auto unscaledSize = SkEncodedOriginSwapsWidthHeight(origin)
                                ? SkISize{ info.height(), info.width() } :  info.dimensions();

                        SkISize scaledSize = { SkScalarFloorToInt(unscaledSize.width()  * scale) ,
                                               SkScalarFloorToInt(unscaledSize.height() * scale) };
                        info = info.makeDimensions(scaledSize);

                        auto cropRect = SkIRect::MakeSize(scaledSize);
                        if (doCrop) {
                            auto matrix = SkMatrix::RectToRectOrIdentity(SkRect::Make(unscaledSize),
                                                                         SkRect::Make(scaledSize));
                            matrix.preConcat(SkEncodedOriginToMatrix(origin,
                                    unscaledSize.width(), unscaledSize.height()));
                            SkRect cropRectFloat = SkRect::Make(fCropRect);
                            matrix.mapRect(&cropRectFloat);
                            cropRectFloat.roundOut(&cropRect);
                        }

                        sk_sp<SkPicture> postProcessor = doPostProcess
                                ? post_processor(SkRect::Make(cropRect.size())) : nullptr;
                        auto animatedImage = SkAnimatedImage::Make(std::move(androidCodec),
                                info, cropRect, std::move(postProcessor));
                        animatedImage->setRepetitionCount(0);

                        for (int frame = 0; frame < kMaxFrames; frame++) {
                            {
                                SkAutoCanvasRestore acr(canvas, doCrop);
                                if (doCrop) {
                                    canvas->translate(cropRect.left(), cropRect.top());
                                }
                                drawProc(animatedImage);
                            }

                            canvas->translate(fTranslate, 0);
                            const auto duration = animatedImage->currentFrameDuration();
                            if (duration == SkAnimatedImage::kFinished) {
                                break;
                            }
                            for (int i = 0; i < fStep; i++) {
                                animatedImage->decodeNextFrame();
                            }
                        }
                    }
                }
                canvas->restore();
                canvas->translate(0, fTranslate);
            }
        }
    }
};

DEF_GM( return new AnimatedImageGM("images/stoplight_h.webp", "stoplight", 2,
                                   // Deliberately not centered in X or Y, and shows all three
                                   // lights, but otherwise arbitrary.
                                   SkIRect::MakeLTRB(5, 6, 11, 29)); )
DEF_GM( return new AnimatedImageGM("images/flightAnim.gif", "flight", 20,
                                   // Deliberately starts in the upper left corner to exercise
                                   // a special case, but otherwise arbitrary.
                                   SkIRect::MakeLTRB(0, 0, 300, 200)); )
