/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "VectorDrawableAtlas.h"

#include <GrRectanizer_pow2.h>
#include <SkCanvas.h>
#include <cmath>
#include "renderthread/RenderProxy.h"
#include "renderthread/RenderThread.h"
#include "utils/TraceUtils.h"

namespace android {
namespace uirenderer {
namespace skiapipeline {

VectorDrawableAtlas::VectorDrawableAtlas(size_t surfaceArea, StorageMode storageMode)
        : mWidth((int)std::sqrt(surfaceArea))
        , mHeight((int)std::sqrt(surfaceArea))
        , mStorageMode(storageMode) {}

void VectorDrawableAtlas::prepareForDraw(GrContext* context) {
    if (StorageMode::allowSharedSurface == mStorageMode) {
        if (!mSurface) {
            mSurface = createSurface(mWidth, mHeight, context);
            mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight);
            mPixelUsedByVDs = 0;
            mPixelAllocated = 0;
            mConsecutiveFailures = 0;
            mFreeRects.clear();
        } else {
            if (isFragmented()) {
                // Invoke repack outside renderFrame to avoid jank.
                renderthread::RenderProxy::repackVectorDrawableAtlas();
            }
        }
    }
}

#define MAX_CONSECUTIVE_FAILURES 5
#define MAX_UNUSED_RATIO 2.0f

bool VectorDrawableAtlas::isFragmented() {
    return mConsecutiveFailures > MAX_CONSECUTIVE_FAILURES &&
           mPixelUsedByVDs * MAX_UNUSED_RATIO < mPixelAllocated;
}

void VectorDrawableAtlas::repackIfNeeded(GrContext* context) {
    // We repackage when atlas failed to allocate space MAX_CONSECUTIVE_FAILURES consecutive
    // times and the atlas allocated pixels are at least MAX_UNUSED_RATIO times higher than pixels
    // used by atlas VDs.
    if (isFragmented() && mSurface) {
        repack(context);
    }
}

// compare to CacheEntry objects based on VD area.
bool VectorDrawableAtlas::compareCacheEntry(const CacheEntry& first, const CacheEntry& second) {
    return first.VDrect.width() * first.VDrect.height() <
           second.VDrect.width() * second.VDrect.height();
}

void VectorDrawableAtlas::repack(GrContext* context) {
    ATRACE_CALL();
    sk_sp<SkSurface> newSurface;
    SkCanvas* canvas = nullptr;
    if (StorageMode::allowSharedSurface == mStorageMode) {
        newSurface = createSurface(mWidth, mHeight, context);
        if (!newSurface) {
            return;
        }
        canvas = newSurface->getCanvas();
        canvas->clear(SK_ColorTRANSPARENT);
        mRectanizer = std::make_unique<GrRectanizerPow2>(mWidth, mHeight);
    } else {
        if (!mSurface) {
            return;  // nothing to repack
        }
        mRectanizer.reset();
    }
    mFreeRects.clear();
    SkImage* sourceImageAtlas = nullptr;
    if (mSurface) {
        sourceImageAtlas = mSurface->makeImageSnapshot().get();
    }

    // Sort the list by VD size, which allows for the smallest VDs to get first in the atlas.
    // Sorting is safe, because it does not affect iterator validity.
    if (mRects.size() <= 100) {
        mRects.sort(compareCacheEntry);
    }

    for (CacheEntry& entry : mRects) {
        SkRect currentVDRect = entry.VDrect;
        SkImage* sourceImage;  // copy either from the atlas or from a standalone surface
        if (entry.surface) {
            if (!fitInAtlas(currentVDRect.width(), currentVDRect.height())) {
                continue;  // don't even try to repack huge VD
            }
            sourceImage = entry.surface->makeImageSnapshot().get();
        } else {
            sourceImage = sourceImageAtlas;
        }
        size_t VDRectArea = currentVDRect.width() * currentVDRect.height();
        SkIPoint16 pos;
        if (canvas && mRectanizer->addRect(currentVDRect.width(), currentVDRect.height(), &pos)) {
            SkRect newRect =
                    SkRect::MakeXYWH(pos.fX, pos.fY, currentVDRect.width(), currentVDRect.height());
            canvas->drawImageRect(sourceImage, currentVDRect, newRect, nullptr);
            entry.VDrect = newRect;
            entry.rect = newRect;
            if (entry.surface) {
                // A rectangle moved from a standalone surface to the atlas.
                entry.surface = nullptr;
                mPixelUsedByVDs += VDRectArea;
            }
        } else {
            // Repack failed for this item. If it is not already, store it in a standalone
            // surface.
            if (!entry.surface) {
                // A rectangle moved from an atlas to a standalone surface.
                mPixelUsedByVDs -= VDRectArea;
                SkRect newRect = SkRect::MakeWH(currentVDRect.width(), currentVDRect.height());
                entry.surface = createSurface(newRect.width(), newRect.height(), context);
                auto tempCanvas = entry.surface->getCanvas();
                tempCanvas->clear(SK_ColorTRANSPARENT);
                tempCanvas->drawImageRect(sourceImageAtlas, currentVDRect, newRect, nullptr);
                entry.VDrect = newRect;
                entry.rect = newRect;
            }
        }
    }
    mPixelAllocated = mPixelUsedByVDs;
    context->flush();
    mSurface = newSurface;
    mConsecutiveFailures = 0;
}

AtlasEntry VectorDrawableAtlas::requestNewEntry(int width, int height, GrContext* context) {
    AtlasEntry result;
    if (width <= 0 || height <= 0) {
        return result;
    }

    if (mSurface) {
        const size_t area = width * height;

        // Use a rectanizer to allocate unused space from the atlas surface.
        bool notTooBig = fitInAtlas(width, height);
        SkIPoint16 pos;
        if (notTooBig && mRectanizer->addRect(width, height, &pos)) {
            mPixelUsedByVDs += area;
            mPixelAllocated += area;
            result.rect = SkRect::MakeXYWH(pos.fX, pos.fY, width, height);
            result.surface = mSurface;
            auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, nullptr);
            CacheEntry* entry = &(*eraseIt);
            entry->eraseIt = eraseIt;
            result.key = reinterpret_cast<AtlasKey>(entry);
            mConsecutiveFailures = 0;
            return result;
        }

        // Try to reuse atlas memory from rectangles freed by "releaseEntry".
        auto freeRectIt = mFreeRects.lower_bound(area);
        while (freeRectIt != mFreeRects.end()) {
            SkRect& freeRect = freeRectIt->second;
            if (freeRect.width() >= width && freeRect.height() >= height) {
                result.rect = SkRect::MakeXYWH(freeRect.fLeft, freeRect.fTop, width, height);
                result.surface = mSurface;
                auto eraseIt = mRects.emplace(mRects.end(), result.rect, freeRect, nullptr);
                CacheEntry* entry = &(*eraseIt);
                entry->eraseIt = eraseIt;
                result.key = reinterpret_cast<AtlasKey>(entry);
                mPixelUsedByVDs += area;
                mFreeRects.erase(freeRectIt);
                mConsecutiveFailures = 0;
                return result;
            }
            freeRectIt++;
        }

        if (notTooBig && mConsecutiveFailures <= MAX_CONSECUTIVE_FAILURES) {
            mConsecutiveFailures++;
        }
    }

    // Allocate a surface for a rectangle that is too big or if atlas is full.
    if (nullptr != context) {
        result.rect = SkRect::MakeWH(width, height);
        result.surface = createSurface(width, height, context);
        auto eraseIt = mRects.emplace(mRects.end(), result.rect, result.rect, result.surface);
        CacheEntry* entry = &(*eraseIt);
        entry->eraseIt = eraseIt;
        result.key = reinterpret_cast<AtlasKey>(entry);
    }

    return result;
}

AtlasEntry VectorDrawableAtlas::getEntry(AtlasKey atlasKey) {
    AtlasEntry result;
    if (INVALID_ATLAS_KEY != atlasKey) {
        CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey);
        result.rect = entry->VDrect;
        result.surface = entry->surface;
        if (!result.surface) {
            result.surface = mSurface;
        }
        result.key = atlasKey;
    }
    return result;
}

void VectorDrawableAtlas::releaseEntry(AtlasKey atlasKey) {
    if (INVALID_ATLAS_KEY != atlasKey) {
        if (!renderthread::RenderThread::isCurrent()) {
            {
                AutoMutex _lock(mReleaseKeyLock);
                mKeysForRelease.push_back(atlasKey);
            }
            // invoke releaseEntry on the renderthread
            renderthread::RenderProxy::releaseVDAtlasEntries();
            return;
        }
        CacheEntry* entry = reinterpret_cast<CacheEntry*>(atlasKey);
        if (!entry->surface) {
            // Store freed atlas rectangles in "mFreeRects" and try to reuse them later, when atlas
            // is full.
            SkRect& removedRect = entry->rect;
            size_t rectArea = removedRect.width() * removedRect.height();
            mFreeRects.emplace(rectArea, removedRect);
            SkRect& removedVDRect = entry->VDrect;
            size_t VDRectArea = removedVDRect.width() * removedVDRect.height();
            mPixelUsedByVDs -= VDRectArea;
            mConsecutiveFailures = 0;
        }
        auto eraseIt = entry->eraseIt;
        mRects.erase(eraseIt);
    }
}

void VectorDrawableAtlas::delayedReleaseEntries() {
    AutoMutex _lock(mReleaseKeyLock);
    for (auto key : mKeysForRelease) {
        releaseEntry(key);
    }
    mKeysForRelease.clear();
}

sk_sp<SkSurface> VectorDrawableAtlas::createSurface(int width, int height, GrContext* context) {
    SkImageInfo info = SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType);
    // This must have a top-left origin so that calls to surface->canvas->writePixels
    // performs a basic texture upload instead of a more complex drawing operation
    return SkSurface::MakeRenderTarget(context, SkBudgeted::kYes, info, 0, kTopLeft_GrSurfaceOrigin,
                                       nullptr);
}

void VectorDrawableAtlas::setStorageMode(StorageMode mode) {
    mStorageMode = mode;
    if (StorageMode::disallowSharedSurface == mStorageMode && mSurface) {
        mSurface.reset();
        mRectanizer.reset();
        mFreeRects.clear();
    }
}

} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
