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

#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/private/base/SkTDArray.h"
#include "src/base/SkTInternalLList.h"
#include "src/core/SkTHash.h"
#include "src/gpu/AtlasTypes.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/graphite/ClipStack.h"
#include "src/gpu/graphite/DrawAtlas.h"

#include <cstddef>
#include <cstdint>
#include <memory>
#include <string_view>

struct SkIPoint;

namespace skgpu::graphite {

class Caps;
class DrawContext;
class Recorder;
class TextureProxy;

//////////////////////////////////////////////////////////////////////////////////////////////////
/** The ClipAtlasManager manages the lifetime of and access to rasterized clip masks.
 */
class ClipAtlasManager {
public:
    ClipAtlasManager(Recorder* recorder);
    ~ClipAtlasManager() = default;

    sk_sp<TextureProxy> findOrCreateEntry(uint32_t stackRecordID,
                                          const ClipStack::ElementList*,
                                          SkIRect maskDeviceBounds,
                                          SkIPoint* outPos);

    bool recordUploads(DrawContext* dc);
    void compact();
    void freeGpuResources();

    void evictAtlases();

private:
    // Wrapper class to manage DrawAtlas and associated caching operations
    class DrawAtlasMgr : public AtlasGenerationCounter, public PlotEvictionCallback {
    public:
        DrawAtlasMgr(size_t width, size_t height,
                     size_t plotWidth, size_t plotHeight,
                     DrawAtlas::UseStorageTextures useStorageTextures,
                     std::string_view label, const Caps*);

        sk_sp<TextureProxy> findOrCreateEntry(Recorder* recorder,
                                              const skgpu::UniqueKey&,
                                              const ClipStack::ElementList*,
                                              SkIRect maskDeviceBounds,
                                              SkIRect keyBounds,
                                              SkIPoint* outPos);
        // Adds to DrawAtlas but not the cache
        sk_sp<TextureProxy> addToAtlas(Recorder* recorder,
                                       const ClipStack::ElementList*,
                                       SkIRect maskDeviceBounds,
                                       SkIPoint* outPos,
                                       AtlasLocator* locator);
        bool recordUploads(DrawContext*, Recorder*);
        void evict(PlotLocator) override;
        void compact(Recorder*);
        void freeGpuResources(Recorder*);

        void evictAll();

    private:
        std::unique_ptr<DrawAtlas> fDrawAtlas;

        // Tracks whether a combined clip mask is already in the DrawAtlas and its location
        struct MaskHashEntry {
            SkIRect fBounds;
            AtlasLocator fLocator;
            MaskHashEntry* fNext = nullptr;
        };
        struct UniqueKeyHash {
            uint32_t operator()(const skgpu::UniqueKey& key) const { return key.hash(); }
        };
        using MaskCache = skia_private::THashMap<skgpu::UniqueKey, MaskHashEntry, UniqueKeyHash>;
        MaskCache fMaskCache;
        int fHashEntryCount = 0;

        // List of stored keys per Plot, used to invalidate cache entries.
        // When a Plot is invalidated via evict(), we'll get its index and Page index from the
        // PlotLocator, index into the fKeyLists array to get the MaskKeyList for that Plot,
        // then iterate through the list and remove entries matching those keys from the MaskCache.
        struct MaskKeyEntry {
            skgpu::UniqueKey fKey;
            SkIRect fBounds;
            SK_DECLARE_INTERNAL_LLIST_INTERFACE(MaskKeyEntry);
        };
        using MaskKeyList = SkTInternalLList<MaskKeyEntry>;
        SkTDArray<MaskKeyList> fKeyLists;
        int fListEntryCount = 0;
    };

    Recorder* fRecorder;
    // We have two atlas managers, one for clips that can be keyed via the path keys,
    // and a smaller one for those that can only be keyed by the SaveRecord ID. We keep
    // them separate because the SaveRecord keyed clips will be far more transient, i.e.,
    // once the SaveRecord is popped they'll never be used again.
    DrawAtlasMgr fPathKeyAtlasMgr;
    DrawAtlasMgr fSaveRecordKeyAtlasMgr;
};

}  // namespace skgpu::graphite

#endif  // skgpu_graphite_ClipAtlasManager_DEFINED
