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

#include "GrCCAtlas.h"

#include "GrClip.h"
#include "GrOnFlushResourceProvider.h"
#include "GrRectanizer_skyline.h"
#include "GrRenderTargetContext.h"
#include "GrTextureProxy.h"
#include "SkMakeUnique.h"
#include "SkMathPriv.h"
#include "ccpr/GrCCCoverageProcessor.h"
#include "ccpr/GrCCPathParser.h"
#include "ops/GrDrawOp.h"

class GrCCAtlas::Node {
public:
    Node(std::unique_ptr<Node> previous, int l, int t, int r, int b)
            : fPrevious(std::move(previous)), fX(l), fY(t), fRectanizer(r - l, b - t) {}

    Node* previous() const { return fPrevious.get(); }

    bool addRect(int w, int h, SkIPoint16* loc) {
        static constexpr int kPad = 1;

        if (!fRectanizer.addRect(w + kPad, h + kPad, loc)) {
            return false;
        }
        loc->fX += fX;
        loc->fY += fY;
        return true;
    }

private:
    const std::unique_ptr<Node> fPrevious;
    const int fX, fY;
    GrRectanizerSkyline fRectanizer;
};

class GrCCAtlas::DrawCoverageCountOp : public GrDrawOp {
public:
    DEFINE_OP_CLASS_ID

    DrawCoverageCountOp(sk_sp<const GrCCPathParser> parser, CoverageCountBatchID batchID,
                        const SkISize& drawBounds)
            : INHERITED(ClassID())
            , fParser(std::move(parser))
            , fBatchID(batchID)
            , fDrawBounds(drawBounds) {
        this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()),
                        GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo);
    }

    // GrDrawOp interface.
    const char* name() const override { return "GrCCAtlas::DrawCoverageCountOp"; }
    FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; }
    RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*,
                                GrPixelConfigIsClamped) override { return RequiresDstTexture::kNo; }
    bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; }
    void onPrepare(GrOpFlushState*) override {}
    void onExecute(GrOpFlushState* flushState) override {
        fParser->drawCoverageCount(flushState, fBatchID,
                                   SkIRect::MakeWH(fDrawBounds.width(), fDrawBounds.height()));
    }

private:
    const sk_sp<const GrCCPathParser> fParser;
    const CoverageCountBatchID fBatchID;
    const SkISize fDrawBounds;

    typedef GrDrawOp INHERITED;
};

GrCCAtlas::GrCCAtlas(const GrCaps& caps, int minWidth, int minHeight)
        : fMaxAtlasSize(caps.maxRenderTargetSize()), fDrawBounds{0, 0} {
    SkASSERT(fMaxAtlasSize <= caps.maxTextureSize());
    SkASSERT(SkTMax(minWidth, minHeight) <= fMaxAtlasSize);
    int initialSize = GrNextPow2(SkTMax(minWidth, minHeight));
    initialSize = SkTMax(int(kMinSize), initialSize);
    initialSize = SkTMin(initialSize, fMaxAtlasSize);
    fHeight = fWidth = initialSize;
    fTopNode = skstd::make_unique<Node>(nullptr, 0, 0, initialSize, initialSize);
}

GrCCAtlas::~GrCCAtlas() {}

bool GrCCAtlas::addRect(int w, int h, SkIPoint16* loc) {
    // This can't be called anymore once setCoverageCountBatchID() has been called.
    SkASSERT(!fCoverageCountBatchID);
    SkASSERT(!fTextureProxy);

    if (!this->internalPlaceRect(w, h, loc)) {
        return false;
    }

    fDrawBounds.fWidth = SkTMax(fDrawBounds.width(), loc->x() + w);
    fDrawBounds.fHeight = SkTMax(fDrawBounds.height(), loc->y() + h);
    return true;
}

bool GrCCAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) {
    SkASSERT(SkTMax(w, h) < fMaxAtlasSize);

    for (Node* node = fTopNode.get(); node; node = node->previous()) {
        if (node->addRect(w, h, loc)) {
            return true;
        }
    }

    // The rect didn't fit. Grow the atlas and try again.
    do {
        SkASSERT(SkTMax(fWidth, fHeight) <= fMaxAtlasSize);
        if (fWidth == fMaxAtlasSize && fHeight == fMaxAtlasSize) {
            return false;
        }
        if (fHeight <= fWidth) {
            int top = fHeight;
            fHeight = SkTMin(fHeight * 2, fMaxAtlasSize);
            fTopNode = skstd::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight);
        } else {
            int left = fWidth;
            fWidth = SkTMin(fWidth * 2, fMaxAtlasSize);
            fTopNode = skstd::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight);
        }
    } while (!fTopNode->addRect(w, h, loc));

    return true;
}

sk_sp<GrRenderTargetContext> GrCCAtlas::finalize(GrOnFlushResourceProvider* onFlushRP,
                                                 sk_sp<const GrCCPathParser> parser) {
    SkASSERT(fCoverageCountBatchID);
    SkASSERT(!fTextureProxy);

    GrSurfaceDesc desc;
    desc.fWidth = fWidth;
    desc.fHeight = fHeight;
    desc.fConfig = kAlpha_half_GrPixelConfig;
    sk_sp<GrRenderTargetContext> rtc = onFlushRP->makeRenderTargetContext(desc, nullptr, nullptr);
    if (!rtc) {
        SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some paths will not be drawn.\n",
                 fWidth, fHeight);
        return nullptr;
    }

    SkIRect clearRect = SkIRect::MakeSize(fDrawBounds);
    rtc->clear(&clearRect, 0, GrRenderTargetContext::CanClearFullscreen::kYes);

    auto op = skstd::make_unique<DrawCoverageCountOp>(std::move(parser), fCoverageCountBatchID,
                                                      fDrawBounds);
    rtc->addDrawOp(GrNoClip(), std::move(op));

    fTextureProxy = sk_ref_sp(rtc->asTextureProxy());
    return rtc;
}