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

#include "include/core/SkCanvas.h"
#include "include/core/SkPicture.h"
#include "include/core/SkTextBlob.h"
#include "include/private/SkOnce.h"
#include "src/core/SkMiniRecorder.h"
#include "src/core/SkPictureCommon.h"
#include "src/core/SkRecordDraw.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkTLazy.h"
#include <new>

using namespace SkRecords;

class SkEmptyPicture final : public SkPicture {
public:
    void playback(SkCanvas*, AbortCallback*) const override { }

    size_t approximateBytesUsed()   const override { return sizeof(*this); }
    int    approximateOpCount(bool) const override { return 0; }
    SkRect cullRect()               const override { return SkRect::MakeEmpty(); }
};

// Calculate conservative bounds for each type of draw op that can be its own mini picture.
// These are fairly easy because we know they can't be affected by any matrix or saveLayers.
static SkRect adjust_for_paint(SkRect bounds, const SkPaint& paint) {
    return paint.canComputeFastBounds() ? paint.computeFastBounds(bounds, &bounds)
                                        : SkRectPriv::MakeLargest();
}
static SkRect bounds(const DrawRect& op) {
    return adjust_for_paint(op.rect, op.paint);
}
static SkRect bounds(const DrawPath& op) {
    return op.path.isInverseFillType() ? SkRectPriv::MakeLargest()
                                       : adjust_for_paint(op.path.getBounds(), op.paint);
}
static SkRect bounds(const DrawTextBlob& op) {
    return adjust_for_paint(op.blob->bounds().makeOffset(op.x, op.y), op.paint);
}

template <typename T>
class SkMiniPicture final : public SkPicture {
public:
    SkMiniPicture(const SkRect* cull, T&& op)
        : fCull(cull ? *cull : bounds(op))
        , fOp(std::move(op)) {}

    void playback(SkCanvas* c, AbortCallback*) const override {
        SkRecords::Draw(c, nullptr, nullptr, 0, nullptr)(fOp);
    }

    size_t approximateBytesUsed()   const override { return sizeof(*this); }
    int    approximateOpCount(bool) const override { return 1; }
    SkRect cullRect()               const override { return fCull; }

private:
    SkRect fCull;
    T      fOp;
};


SkMiniRecorder::SkMiniRecorder() : fState(State::kEmpty) {}
SkMiniRecorder::~SkMiniRecorder() {
    if (fState != State::kEmpty) {
        // We have internal state pending.
        // Detaching then deleting a picture is an easy way to clean up.
        (void)this->detachAsPicture(nullptr);
    }
    SkASSERT(fState == State::kEmpty);
}

#define TRY_TO_STORE(Type, ...)                    \
    if (fState != State::kEmpty) { return false; } \
    fState = State::k##Type;                       \
    new (fBuffer) Type{__VA_ARGS__};               \
    return true

bool SkMiniRecorder::drawRect(const SkRect& rect, const SkPaint& paint) {
    TRY_TO_STORE(DrawRect, paint, rect);
}

bool SkMiniRecorder::drawPath(const SkPath& path, const SkPaint& paint) {
    TRY_TO_STORE(DrawPath, paint, path);
}

bool SkMiniRecorder::drawTextBlob(const SkTextBlob* b, SkScalar x, SkScalar y, const SkPaint& p) {
    TRY_TO_STORE(DrawTextBlob, p, sk_ref_sp(b), x, y);
}
#undef TRY_TO_STORE


sk_sp<SkPicture> SkMiniRecorder::detachAsPicture(const SkRect* cull) {
#define CASE(T)                                                        \
    case State::k##T: {                                                \
        T* op = reinterpret_cast<T*>(fBuffer);                         \
        auto pic = sk_make_sp<SkMiniPicture<T>>(cull, std::move(*op)); \
        op->~T();                                                      \
        fState = State::kEmpty;                                        \
        return std::move(pic);                                         \
    }

    static SkOnce once;
    static SkPicture* empty;

    switch (fState) {
        case State::kEmpty:
            once([]{ empty = new SkEmptyPicture; });
            return sk_ref_sp(empty);
        CASE(DrawPath)
        CASE(DrawRect)
        CASE(DrawTextBlob)
    }
    SkASSERT(false);
    return nullptr;
#undef CASE
}

void SkMiniRecorder::flushAndReset(SkCanvas* canvas) {
#define CASE(Type)                                                  \
    case State::k##Type: {                                          \
        fState = State::kEmpty;                                     \
        Type* op = reinterpret_cast<Type*>(fBuffer);                \
        SkRecords::Draw(canvas, nullptr, nullptr, 0, nullptr)(*op); \
        op->~Type();                                                \
    } return

    switch (fState) {
        case State::kEmpty: return;
        CASE(DrawPath);
        CASE(DrawRect);
        CASE(DrawTextBlob);
    }
    SkASSERT(false);
#undef CASE
}
