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

#include "tools/MSKPPlayer.h"

#include "include/core/SkCanvas.h"
#include "include/core/SkCanvasVirtualEnforcer.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPictureRecorder.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrDirectContext.h"
#include "include/private/SkTArray.h"
#include "include/utils/SkNoDrawCanvas.h"
#include "src/core/SkCanvasPriv.h"
#include "src/core/SkTLazy.h"
#include "src/utils/SkMultiPictureDocument.h"
#include "tools/SkSharingProc.h"

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

// Base Cmd struct.
struct MSKPPlayer::Cmd {
    virtual ~Cmd() = default;
    virtual bool isFullRedraw(SkCanvas*) const = 0;
    virtual void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const = 0;
    // If this command draws another layer, return it's ID. Otherwise, -1.
    virtual int layerID() const { return -1; }
};

// Draws a SkPicture.
struct MSKPPlayer::PicCmd : Cmd {
    sk_sp<SkPicture> fContent;
    SkIRect fClipRect = SkIRect::MakeEmpty(); // clip for picture (no clip if empty).

    bool isFullRedraw(SkCanvas* canvas) const override {
        if (fClipRect.isEmpty()) {
            return false;
        }
        return fClipRect.contains(SkIRect::MakeSize(canvas->getBaseLayerSize()));
    }

    void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override {
        if (!fClipRect.isEmpty()) {
            canvas->save();
            canvas->clipIRect(fClipRect);
        }
        canvas->drawPicture(fContent.get());
        if (!fClipRect.isEmpty()) {
            canvas->restore();
        }
    }
};

// Draws another layer. Stores the ID of the layer to draw and what command index on that
// layer should be current when the layer is drawn. The layer contents are updated to the
// stored command index before the layer is drawn.
struct MSKPPlayer::DrawLayerCmd : Cmd {
    int                         fLayerId;
    size_t                      fLayerCmdCnt;
    SkRect                      fSrcRect;
    SkRect                      fDstRect;
    SkSamplingOptions           fSampling;
    SkCanvas::SrcRectConstraint fConstraint;
    SkTLazy<SkPaint>            fPaint;

    bool isFullRedraw(SkCanvas* canvas) const override { return false; }
    void draw(SkCanvas* canvas, const LayerMap&, LayerStateMap*) const override;
    int layerID() const override { return fLayerId; }
};

void MSKPPlayer::DrawLayerCmd::draw(SkCanvas* canvas,
                                    const LayerMap& layerMap,
                                    LayerStateMap* layerStateMap) const {
    const LayerCmds& layer = layerMap.at(fLayerId);
    LayerState* layerState = &(*layerStateMap)[fLayerId];
    if (!layerState->fSurface) {
        layerState->fCurrCmd = 0;
        layerState->fSurface = MSKPPlayer::MakeSurfaceForLayer(layer, canvas);
        if (!layerState->fSurface) {
            SkDebugf("Couldn't create surface for layer");
            return;
        }
    }
    size_t cmd = layerState->fCurrCmd;
    if (cmd > fLayerCmdCnt) {
        // If the layer contains contents from later commands then replay from the beginning.
        cmd = 0;
    }
    SkCanvas* layerCanvas = layerState->fSurface->getCanvas();
    // Check if there is a full redraw between cmd and fLayerCmdCnt and if so jump to it and ensure
    // we clear the canvas if starting from a full redraw.
    for (size_t checkCmd = fLayerCmdCnt - 1; checkCmd > cmd; --checkCmd) {
        if (layer.fCmds[checkCmd]->isFullRedraw(layerCanvas)) {
            cmd = checkCmd;
            break;
        }
    }
    for (; cmd < fLayerCmdCnt; ++cmd) {
        if (cmd == 0 || layer.fCmds[cmd]->isFullRedraw(layerCanvas)) {
            layerState->fSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
        }
        layer.fCmds[cmd]->draw(layerCanvas, layerMap, layerStateMap);
    }
    layerState->fCurrCmd = fLayerCmdCnt;
    const SkPaint* paint = fPaint.isValid() ? fPaint.get() : nullptr;
    canvas->drawImageRect(layerState->fSurface->makeImageSnapshot(),
                          fSrcRect,
                          fDstRect,
                          fSampling,
                          paint,
                          fConstraint);
}

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

class MSKPPlayer::CmdRecordCanvas : public SkCanvasVirtualEnforcer<SkCanvas> {
public:
    CmdRecordCanvas(LayerCmds* dst, LayerMap* offscreenLayers, const SkIRect* clipRect = nullptr)
            : fDst(dst), fOffscreenLayers(offscreenLayers) {
        if (clipRect) {
            fClipRect = *clipRect;
        }
        fRecorder.beginRecording(SkRect::Make(dst->fDimensions));
    }
    ~CmdRecordCanvas() override { this->recordPicCmd(); }

protected:
    void onDrawPaint(const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawPaint(paint);
    }

    void onDrawBehind(const SkPaint& paint) override {
        SkCanvasPriv::DrawBehind(fRecorder.getRecordingCanvas(), paint);
    }

    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawRect(rect, paint);
    }

    void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawRRect(rrect, paint);
    }

    void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawDRRect(outer, inner, paint);
    }

    void onDrawOval(const SkRect& rect, const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawOval(rect, paint);
    }

    void onDrawArc(const SkRect& rect,
                   SkScalar startAngle,
                   SkScalar sweepAngle,
                   bool useCenter,
                   const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawArc(rect, startAngle, sweepAngle, useCenter, paint);
    }

    void onDrawPath(const SkPath& path, const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawPath(path, paint);
    }

    void onDrawRegion(const SkRegion& region, const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawRegion(region, paint);
    }

    void onDrawTextBlob(const SkTextBlob* blob,
                        SkScalar x,
                        SkScalar y,
                        const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawTextBlob(blob, x, y, paint);
    }

    void onDrawPatch(const SkPoint cubics[12],
                     const SkColor colors[4],
                     const SkPoint texCoords[4],
                     SkBlendMode mode,
                     const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawPatch(cubics, colors, texCoords, mode, paint);
    }

    void onDrawPoints(SkCanvas::PointMode mode,
                      size_t count,
                      const SkPoint pts[],
                      const SkPaint& paint) override {
        fRecorder.getRecordingCanvas()->drawPoints(mode, count, pts, paint);
    }

    void onDrawImage2(const SkImage* image,
                      SkScalar dx,
                      SkScalar dy,
                      const SkSamplingOptions& sampling,
                      const SkPaint* paint) override {
        fRecorder.getRecordingCanvas()->drawImage(image, dx, dy, sampling, paint);
    }

    void onDrawImageRect2(const SkImage* image,
                          const SkRect& src,
                          const SkRect& dst,
                          const SkSamplingOptions& sampling,
                          const SkPaint* paint,
                          SrcRectConstraint constraint) override {
        if (fNextDrawImageFromLayerID != -1) {
            this->recordPicCmd();
            auto drawLayer = std::make_unique<DrawLayerCmd>();
            drawLayer->fLayerId = fNextDrawImageFromLayerID;
            drawLayer->fLayerCmdCnt = fOffscreenLayers->at(fNextDrawImageFromLayerID).fCmds.size();
            drawLayer->fSrcRect = src;
            drawLayer->fDstRect = dst;
            drawLayer->fSampling = sampling;
            drawLayer->fConstraint = constraint;
            if (paint) {
                drawLayer->fPaint.init(*paint);
            }
            fDst->fCmds.push_back(std::move(drawLayer));
            fNextDrawImageFromLayerID = -1;
            return;
        }
        fRecorder.getRecordingCanvas()->drawImageRect(image, src, dst, sampling, paint, constraint);
    }

    void onDrawImageLattice2(const SkImage* image,
                             const Lattice& lattice,
                             const SkRect& dst,
                             SkFilterMode mode,
                             const SkPaint* paint) override {
        fRecorder.getRecordingCanvas()->drawImageLattice(image, lattice, dst, mode, paint);
    }

    void onDrawAtlas2(const SkImage* image,
                      const SkRSXform rsxForms[],
                      const SkRect src[],
                      const SkColor colors[],
                      int count,
                      SkBlendMode mode,
                      const SkSamplingOptions& sampling,
                      const SkRect* cull,
                      const SkPaint* paint) override {
        fRecorder.getRecordingCanvas()->drawAtlas(image,
                                                  rsxForms,
                                                  src,
                                                  colors,
                                                  count,
                                                  mode,
                                                  sampling,
                                                  cull,
                                                  paint);
    }

    void onDrawEdgeAAImageSet2(const ImageSetEntry imageSet[],
                               int count,
                               const SkPoint dstClips[],
                               const SkMatrix preViewMatrices[],
                               const SkSamplingOptions& sampling,
                               const SkPaint* paint,
                               SrcRectConstraint constraint) override {
        fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAImageSet(imageSet,
                                                                        count,
                                                                        dstClips,
                                                                        preViewMatrices,
                                                                        sampling,
                                                                        paint,
                                                                        constraint);
    }

#ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
    void onDrawEdgeAAQuad(const SkRect& rect,
                          const SkPoint clip[4],
                          SkCanvas::QuadAAFlags aaFlags,
                          const SkColor4f& color,
                          SkBlendMode mode) override {}
#else
    void onDrawEdgeAAQuad(const SkRect& rect,
                          const SkPoint clip[4],
                          SkCanvas::QuadAAFlags aaFlags,
                          const SkColor4f& color,
                          SkBlendMode mode) override {
        fRecorder.getRecordingCanvas()->experimental_DrawEdgeAAQuad(rect,
                                                                    clip,
                                                                    aaFlags,
                                                                    color,
                                                                    mode);
    }
#endif

    void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value) override {
        static constexpr char kOffscreenLayerDraw[] = "OffscreenLayerDraw";
        static constexpr char kSurfaceID[] = "SurfaceID";
        SkTArray<SkString> tokens;
        SkStrSplit(key, "|", kStrict_SkStrSplitMode, &tokens);
        if (tokens.size() == 2) {
            if (tokens[0].equals(kOffscreenLayerDraw)) {
                // Indicates that the next drawPicture command contains the SkPicture to render
                // to the layer identified by the ID. 'rect' indicates the dirty area to update
                // (and indicates the layer size if this command is introducing a new layer id).
                fNextDrawPictureToLayerID = std::stoi(tokens[1].c_str());
                fNextDrawPictureToLayerClipRect = rect.roundOut();
                if (fOffscreenLayers->find(fNextDrawPictureToLayerID) == fOffscreenLayers->end()) {
                    SkASSERT(fNextDrawPictureToLayerClipRect.left() == 0 &&
                             fNextDrawPictureToLayerClipRect.top()  == 0);
                    (*fOffscreenLayers)[fNextDrawPictureToLayerID].fDimensions =
                            fNextDrawPictureToLayerClipRect.size();
                }
                // The next draw picture will notice that fNextDrawPictureToLayerID is set and
                // redirect the picture to the offscreen layer.
                return;
            } else if (tokens[0].equals(kSurfaceID)) {
                // Indicates that the following drawImageRect should draw an offscreen layer
                // to this layer.
                fNextDrawImageFromLayerID = std::stoi(tokens[1].c_str());
                return;
            }
        }
    }

    void onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) override {
        fRecorder.getRecordingCanvas()->private_draw_shadow_rec(path, rec);
    }

    void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
        fRecorder.getRecordingCanvas()->drawDrawable(drawable, matrix);
    }

    void onDrawPicture(const SkPicture* picture,
                       const SkMatrix* matrix,
                       const SkPaint* paint) override {
        if (fNextDrawPictureToLayerID != -1) {
            SkASSERT(!matrix);
            SkASSERT(!paint);
            LayerCmds* layer = &fOffscreenLayers->at(fNextDrawPictureToLayerID);
            CmdRecordCanvas sc(layer, fOffscreenLayers, &fNextDrawPictureToLayerClipRect);
            picture->playback(&sc);
            fNextDrawPictureToLayerID = -1;
            fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
            return;
        }
        if (paint) {
            this->saveLayer(nullptr, paint);
        }
        if (matrix) {
            this->save();
            this->concat(*matrix);
        }

        picture->playback(this);

        if (matrix) {
            this->restore();
        }
        if (paint) {
            this->restore();
        }
        fRecorder.getRecordingCanvas()->drawPicture(picture, matrix, paint);
    }

private:
    void recordPicCmd() {
        auto cmd = std::make_unique<PicCmd>();
        cmd->fContent = fRecorder.finishRecordingAsPicture();
        cmd->fClipRect = fClipRect;
        if (cmd->fContent) {
            fDst->fCmds.push_back(std::move(cmd));
        }
        // Set up to accumulate again.
        fRecorder.beginRecording(SkRect::Make(fDst->fDimensions));
    }

    SkPictureRecorder fRecorder; // accumulates draws until we draw an offscreen into this layer.
    LayerCmds*        fDst                            = nullptr;
    SkIRect           fClipRect                       = SkIRect::MakeEmpty();
    int               fNextDrawPictureToLayerID       = -1;
    SkIRect           fNextDrawPictureToLayerClipRect = SkIRect::MakeEmpty();
    int               fNextDrawImageFromLayerID       = -1;
    LayerMap*         fOffscreenLayers                = nullptr;
};

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

std::unique_ptr<MSKPPlayer> MSKPPlayer::Make(SkStreamSeekable* stream) {
    auto deserialContext = std::make_unique<SkSharingDeserialContext>();
    SkDeserialProcs procs;
    procs.fImageProc = SkSharingDeserialContext::deserializeImage;
    procs.fImageCtx = deserialContext.get();

    int pageCount = SkMultiPictureDocumentReadPageCount(stream);
    if (!pageCount) {
        return nullptr;
    }
    std::vector<SkDocumentPage> pages(pageCount);
    if (!SkMultiPictureDocumentRead(stream, pages.data(), pageCount, &procs)) {
        return nullptr;
    }
    std::unique_ptr<MSKPPlayer> result(new MSKPPlayer);
    result->fRootLayers.reserve(pages.size());
    for (const auto& page : pages) {
        SkISize dims = {SkScalarCeilToInt(page.fSize.width()),
                        SkScalarCeilToInt(page.fSize.height())};
        result->fRootLayers.emplace_back();
        result->fRootLayers.back().fDimensions = dims;
        result->fMaxDimensions.fWidth  = std::max(dims.width() , result->fMaxDimensions.width() );
        result->fMaxDimensions.fHeight = std::max(dims.height(), result->fMaxDimensions.height());
        CmdRecordCanvas sc(&result->fRootLayers.back(), &result->fOffscreenLayers);
        page.fPicture->playback(&sc);
    }
    return result;
}

MSKPPlayer::~MSKPPlayer() = default;

SkISize MSKPPlayer::frameDimensions(int i) const {
    if (i < 0 || i >= this->numFrames()) {
        return {-1, -1};
    }
    return fRootLayers[i].fDimensions;
}

bool MSKPPlayer::playFrame(SkCanvas* canvas, int i) {
    if (i < 0 || i >= this->numFrames()) {
        return false;
    }

    // Find the first offscreen layer that has a valid surface. If it's recording context
    // differs from the passed canvas's then reset all the layers. Playback will
    // automatically allocate new surfaces for offscreen layers as they're encountered.
    for (const auto& ols : fOffscreenLayerStates) {
        const LayerState& state = ols.second;
        if (state.fSurface) {
            if (state.fSurface->recordingContext() != canvas->recordingContext()) {
                this->resetLayers();
            }
            break;
        }
    }

    // Replay all the commands for this frame to the caller's canvas.
    const LayerCmds& layer = fRootLayers[i];
    for (const auto& cmd : layer.fCmds) {
        cmd->draw(canvas, fOffscreenLayers, &fOffscreenLayerStates);
    }
    return true;
}

sk_sp<SkSurface> MSKPPlayer::MakeSurfaceForLayer(const LayerCmds& layer, SkCanvas* rootCanvas) {
    // Assume layer has same surface props and info as this (mskp doesn't currently record this
    // data).
    SkSurfaceProps props;
    rootCanvas->getProps(&props);
    return rootCanvas->makeSurface(rootCanvas->imageInfo().makeDimensions(layer.fDimensions),
                                   &props);
}

void MSKPPlayer::resetLayers() { fOffscreenLayerStates.clear(); }

void MSKPPlayer::rewindLayers() {
    for (auto& [id, state] : fOffscreenLayerStates) {
        state.fCurrCmd = -1;
    }
}

void MSKPPlayer::allocateLayers(SkCanvas* canvas) {
    // Iterate over layers not states as states are lazily created in playback but here we want to
    // create any that don't already exist.
    for (auto& [id, layer] : fOffscreenLayers) {
        LayerState& state = fOffscreenLayerStates[id];
        if (!state.fSurface || state.fSurface->recordingContext() != canvas->recordingContext()) {
            state.fCurrCmd = -1;
            state.fSurface = MakeSurfaceForLayer(fOffscreenLayers[id], canvas);
        }
    }
}

std::vector<int> MSKPPlayer::layerIDs(int frame) const {
    std::vector<int> result;
    if (frame < 0) {
        result.reserve(fOffscreenLayers.size());
        for (auto& [id, _] : fOffscreenLayers) {
            result.push_back(id);
        }
        return result;
    }
    if (frame < static_cast<int>(fRootLayers.size())) {
        this->collectReferencedLayers(fRootLayers[frame], &result);
    }
    return result;
}

sk_sp<SkImage> MSKPPlayer::layerSnapshot(int layerID) const {
    auto iter = fOffscreenLayerStates.find(layerID);
    if (iter == fOffscreenLayerStates.end() || !iter->second.fSurface) {
        return nullptr;
    }
    return iter->second.fSurface->makeImageSnapshot();
}

void MSKPPlayer::collectReferencedLayers(const LayerCmds& layer, std::vector<int>* out) const {
    for (const auto& cmd : layer.fCmds) {
        if (int id = cmd->layerID(); id >= 0) {
            // Linear, but we'd need to have a lot of layers to actually care.
            if (std::find(out->begin(), out->end(), id) == out->end()) {
                out->push_back(id);
                auto iter = fOffscreenLayers.find(id);
                SkASSERT(iter != fOffscreenLayers.end());
                this->collectReferencedLayers(iter->second, out);
            }
        }
    }
}
