/*
 * 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.
 */

#pragma once

#include <SkSurface.h>
#include <utils/FatVector.h>
#include <utils/RefBase.h>
#include <utils/Thread.h>
#include <list>
#include <map>

class GrRectanizer;

namespace android {
namespace uirenderer {
namespace skiapipeline {

typedef uintptr_t AtlasKey;

#define INVALID_ATLAS_KEY 0

struct AtlasEntry {
    sk_sp<SkSurface> surface;
    SkRect rect;
    AtlasKey key = INVALID_ATLAS_KEY;
};

/**
 * VectorDrawableAtlas provides offscreen buffers used to draw VD and AnimatedVD.
 * VectorDrawableAtlas can allocate a standalone surface or provide a subrect from a shared surface.
 * VectorDrawableAtlas is owned by the CacheManager and weak pointers are kept by each
 * VectorDrawable that is using it. VectorDrawableAtlas and its surface can be deleted at any time,
 * except during a renderFrame call. VectorDrawable does not contain a pointer to atlas SkSurface
 * nor any coordinates into the atlas, but instead holds a rectangle "id", which is resolved only
 * when drawing. This design makes VectorDrawableAtlas free to move the data internally.
 * At draw time a VectorDrawable may find, that its atlas has been deleted, which will make it
 * draw in a standalone cache surface not part of an atlas. In this case VD won't use
 * VectorDrawableAtlas until the next frame.
 * VectorDrawableAtlas tries to fit VDs in the atlas SkSurface. If there is not enough space in
 * the atlas, VectorDrawableAtlas creates a standalone surface for each VD.
 * When a VectorDrawable is deleted, it invokes VectorDrawableAtlas::releaseEntry, which is keeping
 * track of free spaces and allow to reuse the surface for another VD.
 */
// TODO: Check if not using atlas for AnimatedVD is more efficient.
// TODO: For low memory situations, when there are no paint effects in VD, we may render without an
// TODO: offscreen surface.
class VectorDrawableAtlas : public virtual RefBase {
public:
    enum class StorageMode { allowSharedSurface, disallowSharedSurface };

    VectorDrawableAtlas(size_t surfaceArea,
                        StorageMode storageMode = StorageMode::allowSharedSurface);

    /**
     * "prepareForDraw" may allocate a new surface if needed. It may schedule to repack the
     * atlas at a later time.
     */
    void prepareForDraw(GrContext* context);

    /**
     * Repack the atlas if needed, by moving used rectangles into a new atlas surface.
     * The goal of repacking is to fix a fragmented atlas.
     */
    void repackIfNeeded(GrContext* context);

    /**
     * Returns true if atlas is fragmented and repack is needed.
     */
    bool isFragmented();

    /**
     * "requestNewEntry" is called by VectorDrawable to allocate a new rectangle area from the atlas
     * or create a standalone surface if atlas is full.
     * On success it returns a non-negative unique id, which can be used later with "getEntry" and
     * "releaseEntry".
     */
    AtlasEntry requestNewEntry(int width, int height, GrContext* context);

    /**
     * "getEntry" extracts coordinates and surface of a previously created rectangle.
     * "atlasKey" is an unique id created by "requestNewEntry". Passing a non-existing "atlasKey" is
     * causing an undefined behaviour.
     * On success it returns a rectangle Id -> may be same or different from "atlasKey" if
     * implementation decides to move the record internally.
     */
    AtlasEntry getEntry(AtlasKey atlasKey);

    /**
     * "releaseEntry" is invoked when a VectorDrawable is deleted. Passing a non-existing "atlasKey"
     * is causing an undefined behaviour. This is the only function in the class that can be
     * invoked from any thread. It will marshal internally to render thread if needed.
     */
    void releaseEntry(AtlasKey atlasKey);

    void setStorageMode(StorageMode mode);

    /**
     * "delayedReleaseEntries" is indirectly invoked by "releaseEntry", when "releaseEntry" is
     * invoked from a non render thread.
     */
    void delayedReleaseEntries();

private:
    struct CacheEntry {
        CacheEntry(const SkRect& newVDrect, const SkRect& newRect,
                   const sk_sp<SkSurface>& newSurface)
                : VDrect(newVDrect), rect(newRect), surface(newSurface) {}

        /**
         * size and position of VectorDrawable into the atlas or in "this.surface"
         */
        SkRect VDrect;

        /**
         * rect allocated in atlas surface or "this.surface". It may be bigger than "VDrect"
         */
        SkRect rect;

        /**
         * this surface is used if atlas is full or VD is too big
         */
        sk_sp<SkSurface> surface;

        /**
         * iterator is used to delete self with a constant complexity (without traversing the list)
         */
        std::list<CacheEntry>::iterator eraseIt;
    };

    /**
     * atlas surface shared by all VDs
     */
    sk_sp<SkSurface> mSurface;

    std::unique_ptr<GrRectanizer> mRectanizer;
    const int mWidth;
    const int mHeight;

    /**
     * "mRects" keeps records only for rectangles used by VDs. List has nice properties: constant
     * complexity to insert and erase and references are not invalidated by insert/erase.
     */
    std::list<CacheEntry> mRects;

    /**
     * Rectangles freed by "releaseEntry" are removed from "mRects" and added to "mFreeRects".
     * "mFreeRects" is using for an index the rectangle area. There could be more than one free
     * rectangle with the same area, which is the reason to use "multimap" instead of "map".
     */
    std::multimap<size_t, SkRect> mFreeRects;

    /**
     * area in atlas used by VectorDrawables (area in standalone surface not counted)
     */
    int mPixelUsedByVDs = 0;

    /**
     * area allocated in mRectanizer
     */
    int mPixelAllocated = 0;

    /**
     * Consecutive times we had to allocate standalone surfaces, because atlas was full.
     */
    int mConsecutiveFailures = 0;

    /**
     * mStorageMode allows using a shared surface to store small vector drawables.
     * Using a shared surface can boost the performance by allowing GL ops to be batched, but may
     * consume more memory.
     */
    StorageMode mStorageMode;

    /**
     * mKeysForRelease is used by releaseEntry implementation to pass atlas keys from an arbitrary
     * calling thread to the render thread.
     */
    std::vector<AtlasKey> mKeysForRelease;

    /**
     * A lock used to protect access to mKeysForRelease.
     */
    Mutex mReleaseKeyLock;

    sk_sp<SkSurface> createSurface(int width, int height, GrContext* context);

    inline bool fitInAtlas(int width, int height) {
        return 2 * width < mWidth && 2 * height < mHeight;
    }

    void repack(GrContext* context);

    static bool compareCacheEntry(const CacheEntry& first, const CacheEntry& second);
};

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