/*
 * Copyright 2006 The Android Open Source Project
 *
 * Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
 */

#ifndef SkStrike_DEFINED
#define SkStrike_DEFINED

#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontTypes.h"
#include "include/private/SkMutex.h"
#include "include/private/SkTHash.h"
#include "include/private/SkTemplates.h"
#include "src/core/SkArenaAlloc.h"
#include "src/core/SkDescriptor.h"
#include "src/core/SkGlyph.h"
#include "src/core/SkGlyphRunPainter.h"

#include <memory>

class SkScalerContext;
namespace sktext {
union IDOrPath;
union IDOrDrawable;
}  // namespace sktext

// This class represents a strike: a specific combination of typeface, size, matrix, etc., and
// holds the glyphs for that strike.
class SkScalerCache {
public:
    SkScalerCache(std::unique_ptr<SkScalerContext> scaler,
                  const SkFontMetrics* metrics = nullptr);

    // Lookup (or create if needed) the toGlyph using toID. If that glyph is not initialized with
    // an image, then use the information in from to initialize the width, height top, left,
    // format and image of the toGlyph. This is mainly used preserving the glyph if it was
    // created by a search of desperation.
    std::tuple<SkGlyph*, size_t> mergeGlyphAndImage(
            SkPackedGlyphID toID, const SkGlyph& from) SK_EXCLUDES(fMu);

    // If the path has never been set, then add a path to glyph.
    std::tuple<const SkPath*, size_t> mergePath(
            SkGlyph* glyph, const SkPath* path, bool hairline) SK_EXCLUDES(fMu);

    // If the drawable has never been set, then add a drawble to glyph.
    std::tuple<SkDrawable*, size_t> mergeDrawable(
            SkGlyph* glyph, sk_sp<SkDrawable> drawable) SK_EXCLUDES(fMu);

    /** Return the number of glyphs currently cached. */
    int countCachedGlyphs() const SK_EXCLUDES(fMu);

    /** If the advance axis intersects the glyph's path, append the positions scaled and offset
        to the array (if non-null), and set the count to the updated array length.
    */
    void findIntercepts(const SkScalar bounds[2], SkScalar scale, SkScalar xPos,
                        SkGlyph* , SkScalar* array, int* count) SK_EXCLUDES(fMu);

    const SkFontMetrics& getFontMetrics() const {
        return fFontMetrics;
    }

    std::tuple<SkSpan<const SkGlyph*>, size_t> metrics(
            SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) SK_EXCLUDES(fMu);

    std::tuple<SkSpan<const SkGlyph*>, size_t> preparePaths(
            SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) SK_EXCLUDES(fMu);

    std::tuple<SkSpan<const SkGlyph*>, size_t> prepareImages(
            SkSpan<const SkPackedGlyphID> glyphIDs, const SkGlyph* results[]) SK_EXCLUDES(fMu);

    std::tuple<SkSpan<const SkGlyph*>, size_t> prepareDrawables(
            SkSpan<const SkGlyphID> glyphIDs, const SkGlyph* results[]) SK_EXCLUDES(fMu);

    size_t prepareForDrawingMasksCPU(SkDrawableGlyphBuffer* accepted) SK_EXCLUDES(fMu);

    // SkStrikeForGPU APIs
    const SkGlyphPositionRoundingSpec& roundingSpec() const {
        return fRoundingSpec;
    }

    std::tuple<SkRect, size_t> prepareForMaskDrawing(
            SkDrawableGlyphBuffer* accepted,
            SkSourceGlyphBuffer* rejected) SK_EXCLUDES(fMu);

#if !defined(SK_DISABLE_SDF_TEXT)
    std::tuple<SkRect, size_t> prepareForSDFTDrawing(
            SkDrawableGlyphBuffer* accepted,
            SkSourceGlyphBuffer* rejected) SK_EXCLUDES(fMu);
#endif

    size_t prepareForPathDrawing(
            SkDrawableGlyphBuffer* accepted, SkSourceGlyphBuffer* rejected) SK_EXCLUDES(fMu);

    size_t prepareForDrawableDrawing(
            SkDrawableGlyphBuffer* accepted, SkSourceGlyphBuffer* rejected) SK_EXCLUDES(fMu);

    // Convert all the IDs into SkPaths in the span.
    size_t glyphIDsToPaths(SkSpan<sktext::IDOrPath> idsOrPaths) SK_EXCLUDES(fMu);

    // Convert all the IDs into SkDrawables in the span.
    size_t glyphIDsToDrawables(SkSpan<sktext::IDOrDrawable> idsOrDrawables) SK_EXCLUDES(fMu);

    std::tuple<SkScalar, size_t>
            findMaximumGlyphDimension(SkSpan<const SkGlyphID> glyphs) SK_EXCLUDES(fMu);

    void dump() const SK_EXCLUDES(fMu);

    SkScalerContext* getScalerContext() const { return fScalerContext.get(); }

private:
    template <typename Fn>
    size_t commonFilterLoop(SkDrawableGlyphBuffer* accepted, Fn&& fn) SK_REQUIRES(fMu);

    // Return a glyph. Create it if it doesn't exist, and initialize the glyph with metrics and
    // advances using a scaler.
    std::tuple<SkGlyph*, size_t> glyph(SkPackedGlyphID) SK_REQUIRES(fMu);

    std::tuple<SkGlyphDigest, size_t> digest(SkPackedGlyphID) SK_REQUIRES(fMu);

    // Generate the glyph digest information and update structures to add the glyph.
    SkGlyphDigest addGlyph(SkGlyph* glyph) SK_REQUIRES(fMu);

    std::tuple<const void*, size_t> prepareImage(SkGlyph* glyph) SK_REQUIRES(fMu);

    // If the path has never been set, then use the scaler context to add the glyph.
    size_t preparePath(SkGlyph*) SK_REQUIRES(fMu);

    // If the drawable has never been set, then use the scaler context to add the glyph.
    size_t prepareDrawable(SkGlyph*) SK_REQUIRES(fMu);

    enum PathDetail {
        kMetricsOnly,
        kMetricsAndPath
    };

    // internalPrepare will only be called with a mutex already held.
    std::tuple<SkSpan<const SkGlyph*>, size_t> internalPrepare(
            SkSpan<const SkGlyphID> glyphIDs,
            PathDetail pathDetail,
            const SkGlyph** results) SK_REQUIRES(fMu);

    const std::unique_ptr<SkScalerContext> fScalerContext;
    const SkFontMetrics                    fFontMetrics;
    const SkGlyphPositionRoundingSpec      fRoundingSpec;

    mutable SkMutex fMu;

    // Map from a combined GlyphID and sub-pixel position to a SkGlyphDigest. The actual glyph is
    // stored in the fAlloc. The pointer to the glyph is stored fGlyphForIndex. The
    // SkGlyphDigest's fIndex field stores the index. This pointer provides an unchanging
    // reference to the SkGlyph as long as the strike is alive, and fGlyphForIndex
    // provides a dense index for glyphs.
    SkTHashMap<SkPackedGlyphID, SkGlyphDigest, SkPackedGlyphID::Hash>
            fDigestForPackedGlyphID SK_GUARDED_BY(fMu);
    std::vector<SkGlyph*> fGlyphForIndex SK_GUARDED_BY(fMu);

    // so we don't grow our arrays a lot
    inline static constexpr size_t kMinGlyphCount = 8;
    inline static constexpr size_t kMinGlyphImageSize = 16 /* height */ * 8 /* width */;
    inline static constexpr size_t kMinAllocAmount = kMinGlyphImageSize * kMinGlyphCount;

    SkArenaAlloc            fAlloc SK_GUARDED_BY(fMu) {kMinAllocAmount};
};

#endif  // SkStrike_DEFINED
