/*
 * Copyright (C) 2006-2023 Apple Inc. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#pragma once

#include <WebCore/Font.h>
#include <WebCore/FontCascadeDescription.h>
#include <WebCore/FontRanges.h>
#include <WebCore/FontSelector.h>
#include <WebCore/GlyphBuffer.h>
#include <WebCore/GlyphPage.h>
#include <WebCore/TextMeasurementCache.h>
#include <WebCore/TextRun.h>
#include <wtf/CurrentThread.h>
#include <wtf/EnumeratedArray.h>
#include <wtf/Forward.h>
#include <wtf/HashFunctions.h>
#include <wtf/HashMap.h>
#include <wtf/HashTraits.h>
#include <wtf/MainThread.h>
#include <wtf/Platform.h>
#include <wtf/TriState.h>
#include <wtf/unicode/CharacterNames.h>

#if PLATFORM(IOS_FAMILY)
#include <WebCore/WebCoreThread.h>
#endif

namespace WTF {
class TextStream;
}

namespace WebCore {

enum class ForTextEmphasis : bool;

class FontPlatformData;
class FontSelector;
class GraphicsContext;
class IntRect;
class MixedFontGlyphPage;

struct TextShapingResultAndDisplayList;

struct GlyphOverflow {
    // FIXME: May need clearer&safer storage and names. See webkit.org/b/307002
    LayoutUnit left;
    LayoutUnit right;
    LayoutUnit top;
    LayoutUnit bottom;
    bool computeBounds { false };
};

struct GlyphGeometryCacheEntry {
    Markable<float> width;
    Markable<GlyphOverflow> glyphOverflow;
    bool usedFallbackFonts { false };
};

namespace ShapedTextCacheDefaults {
// This is tuned for Canvas text operations (fillText, strokeText)
static constexpr int initialInterval = -3; // Cache immediately, no countdown
static constexpr int minInterval = -3; // After a hit, cache the next 3 attempts
static constexpr int maxInterval = -3; // Never ramp up sampling, stay aggressive
static constexpr unsigned maxSize = 3000; // Shaped text entries are large due to GlyphBuffer
static constexpr unsigned maxTextLength = 128; // Larger than default to cache longer canvas text
}

} // namespace WebCore

namespace WTF {

template<>
struct MarkableTraits<WebCore::GlyphOverflow> {
    static constexpr bool isEmptyValue(const WebCore::GlyphOverflow& value)
    {
        return MarkableTraits<WebCore::LayoutUnit>::isEmptyValue(value.left);
    }

    static constexpr WebCore::GlyphOverflow emptyValue()
    {
        return { .left = MarkableTraits<WebCore::LayoutUnit>::emptyValue(), .right = { }, .top = { }, .bottom = { }, .computeBounds = { } };
    }
};

} // namespace WTF

namespace WebCore {

DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(FontCascadeFonts);
class FontCascadeFonts : public RefCounted<FontCascadeFonts> {
    WTF_MAKE_NONCOPYABLE(FontCascadeFonts);
    WTF_DEPRECATED_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(FontCascadeFonts, FontCascadeFonts);
public:
    static Ref<FontCascadeFonts> create() { return adoptRef(*new FontCascadeFonts()); }
    static Ref<FontCascadeFonts> createForPlatformFont(const FontPlatformData& platformData) { return adoptRef(*new FontCascadeFonts(platformData)); }

    WEBCORE_EXPORT ~FontCascadeFonts();

    bool isForPlatformFont() const { return m_isForPlatformFont; }

    GlyphData glyphDataForCharacter(char32_t, const FontCascadeDescription&, FontSelector*, FontVariant, ResolvedEmojiPolicy);

    bool isFixedPitch(const FontCascadeDescription&, FontSelector*);

    bool canTakeFixedPitchFastContentMeasuring(const FontCascadeDescription&, FontSelector*);
    TriState NODELETE cachedCanTakeFixedPitchFastContentMeasuring() const
    {
        return m_canTakeFixedPitchFastContentMeasuring;
    }

    bool isLoadingCustomFonts() const;

    // FIXME: It should be possible to combine fontSelectorVersion and generation.
    unsigned generation() const { return m_generation; }

    using GlyphGeometryCache = TextMeasurementCache<GlyphGeometryCacheEntry>;
    GlyphGeometryCache& glyphGeometryCache() LIFETIME_BOUND { return m_glyphGeometryCache; }
    const GlyphGeometryCache& glyphGeometryCache() const LIFETIME_BOUND { return m_glyphGeometryCache; }

    using CachedTextShapingResultAndDisplayList = std::unique_ptr<TextShapingResultAndDisplayList>;
    using ShapedTextCache = TextMeasurementCache<
        CachedTextShapingResultAndDisplayList,
        ShapedTextCacheDefaults::initialInterval,
        ShapedTextCacheDefaults::minInterval,
        ShapedTextCacheDefaults::maxInterval,
        ShapedTextCacheDefaults::maxSize,
        ShapedTextCacheDefaults::maxTextLength
    >;
    ShapedTextCache& shapedTextCache() LIFETIME_BOUND { return m_shapedTextCache; }
    const ShapedTextCache& shapedTextCache() const LIFETIME_BOUND { return m_shapedTextCache; }

    TextShapingResultAndDisplayList* getOrCreateCachedShapedText(const TextRun&, const FontCascade&, unsigned from, std::optional<unsigned> to, ForTextEmphasis);

    const Font& primaryFont(const FontCascadeDescription&, FontSelector*);
    const Font* NODELETE cachedPrimaryFont() const { return m_cachedPrimaryFont.get(); }
    WEBCORE_EXPORT const FontRanges& realizeFallbackRangesAt(const FontCascadeDescription&, FontSelector*, unsigned fallbackIndex);

    void pruneSystemFallbacks();

    void addSystemFallbackFont(Ref<Font>&&);

private:
    FontCascadeFonts();
    FontCascadeFonts(const FontPlatformData&);

    GlyphData glyphDataForSystemFallback(char32_t, const FontCascadeDescription&, FontSelector*, FontVariant, ResolvedEmojiPolicy, bool systemFallbackShouldBeInvisible);
    GlyphData glyphDataForVariant(char32_t, const FontCascadeDescription&, FontSelector*, FontVariant, ResolvedEmojiPolicy, unsigned fallbackIndex = 0);

    WEBCORE_EXPORT void determinePitch(const FontCascadeDescription&, FontSelector*);
    WEBCORE_EXPORT void determineCanTakeFixedPitchFastContentMeasuring(const FontCascadeDescription&, FontSelector*);

    Vector<FontRanges, 1> m_realizedFallbackRanges;
    unsigned m_lastRealizedFallbackIndex { 0 };

    class GlyphPageCacheEntry {
    public:
        GlyphPageCacheEntry();
        GlyphPageCacheEntry(RefPtr<GlyphPage>&&);
        GlyphPageCacheEntry(GlyphPageCacheEntry&&) = default;
        GlyphPageCacheEntry& operator=(GlyphPageCacheEntry&&) = default;
        ~GlyphPageCacheEntry();

        GlyphData glyphDataForCharacter(char32_t);

        void NODELETE setSingleFontPage(RefPtr<GlyphPage>&&);
        void setGlyphDataForCharacter(char32_t, GlyphData);

        bool isNull() const { return !m_singleFont && !m_mixedFont; }
        bool isMixedFont() const { return !!m_mixedFont; }

    private:
        // Only one of these is non-null.
        RefPtr<GlyphPage> m_singleFont;
        std::unique_ptr<MixedFontGlyphPage> m_mixedFont;
    };

    EnumeratedArray<ResolvedEmojiPolicy, HashMap<unsigned, GlyphPageCacheEntry, IntHash<unsigned>, WTF::UnsignedWithZeroKeyHashTraits<unsigned>>, ResolvedEmojiPolicy::RequireEmoji> m_cachedPages;

    HashSet<Ref<Font>> m_systemFallbackFontSet;

    SingleThreadWeakPtr<const Font> m_cachedPrimaryFont;

    GlyphGeometryCache m_glyphGeometryCache;
    ShapedTextCache m_shapedTextCache;

    unsigned short m_generation { 0 };
    PitchType m_pitch { PitchType::Unknown };
    bool m_isForPlatformFont { false };
    TriState m_canTakeFixedPitchFastContentMeasuring : 2 { TriState::Indeterminate };
#if ASSERT_ENABLED
    std::optional<uint32_t> m_creationThreadID;
#endif
};

inline bool FontCascadeFonts::isFixedPitch(const FontCascadeDescription& description, FontSelector* fontSelector)
{
    if (m_pitch == PitchType::Unknown)
        determinePitch(description, fontSelector);
    return m_pitch == PitchType::Fixed;
}

inline bool FontCascadeFonts::canTakeFixedPitchFastContentMeasuring(const FontCascadeDescription& description, FontSelector* fontSelector)
{
    if (m_canTakeFixedPitchFastContentMeasuring == TriState::Indeterminate)
        determineCanTakeFixedPitchFastContentMeasuring(description, fontSelector);
    return m_canTakeFixedPitchFastContentMeasuring == TriState::True;
}

inline const Font& FontCascadeFonts::primaryFont(const FontCascadeDescription& description, FontSelector* fontSelector)
{
    ASSERT(m_creationThreadID ? *m_creationThreadID == currentThreadID() : isMainThread());
    if (!m_cachedPrimaryFont) {
        // CSS Fonts 4 §5.2: "The first available font [...] is defined to be the first font for which
        // the character U+0020 (space) is not excluded by a unicode-range [...]. Note: it does not
        // matter whether that font actually has a glyph for the space character."
        auto& primaryRanges = realizeFallbackRangesAt(description, fontSelector, 0);
        m_cachedPrimaryFont = primaryRanges.glyphDataForCharacter(space, ExternalResourceDownloadPolicy::Allow).font.get();
        if (!m_cachedPrimaryFont && primaryRanges.hasRangeContaining(space))
            m_cachedPrimaryFont = primaryRanges.rangeAt(0).font(ExternalResourceDownloadPolicy::Allow);
        if (!m_cachedPrimaryFont || m_cachedPrimaryFont->isInterstitial()) {
            for (unsigned index = 1; ; ++index) {
                auto& localRanges = realizeFallbackRangesAt(description, fontSelector, index);
                if (localRanges.isNull())
                    break;
                WeakPtr font = localRanges.glyphDataForCharacter(space, ExternalResourceDownloadPolicy::Forbid).font.get();
                if (font && !font->isInterstitial()) {
                    m_cachedPrimaryFont = WTF::move(font);
                    break;
                }
            }
        }
        // Last resort: all cascade fonts are loading or unavailable.
        if (!m_cachedPrimaryFont)
            m_cachedPrimaryFont = realizeFallbackRangesAt(description, fontSelector, 0).rangeAt(0).font(ExternalResourceDownloadPolicy::Forbid);
    }
    ASSERT(m_cachedPrimaryFont);
    return *m_cachedPrimaryFont;
}

WTF::TextStream& operator<<(WTF::TextStream&, const FontCascadeFonts&);

} // namespace WebCore
