/*
    Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
    Copyright (C) 2001 Dirk Mueller <mueller@kde.org>
    Copyright (C) 2004, 2005, 2006, 2007, 2008 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.

    This class provides all functionality needed for loading images, style sheets and html
    pages from the web. It has a memory cache for these objects.
*/

#pragma once

#include "CachedResource.h"
#include "PlatformString.h"
#include "SessionID.h"
#include "Timer.h"
#include <wtf/HashMap.h>
#include <wtf/HashSet.h>
#include <wtf/NeverDestroyed.h>
#include <wtf/Noncopyable.h>
#include <wtf/Optional.h>
#include <wtf/Vector.h>
#include <wtf/WeakListHashSet.h>
#include <wtf/text/StringHash.h>

namespace WebCore  {

class CachedCSSStyleSheet;
class CachedResource;
class CachedResourceLoader;
class Image;
class KURL;
class ScriptExecutionContext;
class SecurityOrigin;
struct SecurityOriginHash;

// This cache holds subresources used by Web pages: images, scripts, stylesheets, etc.

// The cache keeps a flexible but bounded window of dead resources that grows/shrinks 
// depending on the live resource load. Here's an example of cache growth over time,
// with a min dead resource capacity of 25% and a max dead resource capacity of 50%:

//        |-----|                              Dead: -
//        |----------|                         Live: +
//      --|----------|                         Cache boundary: | (objects outside this mark have been evicted)
//      --|----------++++++++++|
// -------|-----+++++++++++++++|
// -------|-----+++++++++++++++|+++++

class MemoryCache {
    WTF_MAKE_NONCOPYABLE(MemoryCache); WTF_MAKE_FAST_ALLOCATED;
    friend class NeverDestroyed<MemoryCache>;
public:
    typedef HashMap<String, CachedResource*> CachedResourceMap;

    struct TypeStatistic {
        int count { 0 };
        int size { 0 };
        int liveSize { 0 };
        int decodedSize { 0 };
        void addResource(CachedResource*);
    };
    
    struct Statistics {
        TypeStatistic images;
        TypeStatistic cssStyleSheets;
        TypeStatistic scripts;
        TypeStatistic xslStyleSheets;
        TypeStatistic fonts;
        TypeStatistic rawResources;
        TypeStatistic others;
    };

    WEBCORE_EXPORT static MemoryCache& singleton();
    
    CachedResource* resourceForURL(const KURL&);
    CachedResource* resourceForRequest(const CachedResourceRequest&);
    
    bool add(CachedResource& resource);
    bool remove(CachedResource& resource) { return evict(resource); }

    static bool shouldRemoveFragmentIdentifier(const KURL&);
    static KURL removeFragmentIdentifierIfNeeded(const KURL&);
    
    void revalidationSucceeded(CachedResource& revalidatingResource, const ResourceResponse&);
    void revalidationFailed(CachedResource& revalidatingResource);
    
    // Sets the cache's memory capacities, in bytes. These will hold only approximately, 
    // since the decoded cost of resources like scripts and stylesheets is not known.
    //  - minDeadBytes: The maximum number of bytes that dead resources should consume when the cache is under pressure.
    //  - maxDeadBytes: The maximum number of bytes that dead resources should consume when the cache is not under pressure.
    //  - totalBytes: The maximum number of bytes that the cache should consume overall.
    void setCapacities(unsigned minDeadBytes, unsigned maxDeadBytes, unsigned totalBytes);

    // Turn the cache on and off.  Disabling the cache will remove all resources from the cache.  They may
    // still live on if they are referenced by some Web page though.
    void setDisabled(bool);
    ALWAYS_INLINE bool disabled() const { return m_disabled; }

    void dumpStats(int (*logFunction)(const char * format, ... ));
    void dumpLeakedResources(int (*logFunction)(const char * format, ... ));
    
    ALWAYS_INLINE bool hasSpace(int requiredSize) const
    {
    	return ((m_liveSize + m_deadSize + requiredSize) <= m_capacity);
    }

    /* return the total bytes removed from cache */
    bool inHandleOutOfMemory() const { return m_inHandleOutOfMemory; }
    int handleOutOfMemory(int requiredSize, bool& noMemoryToFree);
    
    /* return the total bytes removed from cache */
    int prune(int requiredSize, const Image* forImage, bool canReuse);
    void purge();
    
    int evict(const String& urlPrefix);
    
    void pruneDeadResources(bool all = false); // Flush decoded and encoded data from resources not referenced by Web pages.
    void pruneLiveResources(); // Flush decoded data from resources still referenced by Web pages.

    void evictResources();
    
    void setPruneEnabled(bool enabled) { m_pruneEnabled = enabled; }
    void prune();
    void pruneSoon();
    void pruneToPercentage(float targetPercentLive);
    void pruneDeadResourcesSinceLastAccess(double interval);

    void setDecodedDataDeletionInterval(double interval) { m_decodedDataDeletionInterval = interval; }
    double decodedDataDeletionInterval() const { return m_decodedDataDeletionInterval; }

    const Optional<double>& decodedScriptDataDeletionInterval() const { return m_decodedScriptDataDeletionInterval; }
    void setDecodedScriptDataDeletionInterval(Optional<double> interval) { m_decodedScriptDataDeletionInterval = interval; }

    void setDeadDecodedDataDeletionInterval(double interval) { m_deadDecodedDataDeletionInterval = interval; }
    double deadDecodedDataDeletionInterval() const { return m_deadDecodedDataDeletionInterval; }

    void setDeadFontPlatformDataDeletionInterval(double interval) { m_deadFontPlatformDataDeletionInterval = interval; }
    double deadFontPlatformDataDeletionInterval() const { return m_deadFontPlatformDataDeletionInterval; }

    void setAcceleratedSurfaceDeletionInterval(double interval) { m_acceleratedSurfaceDeletionInterval = interval; }
    double acceleratedSurfaceDeletionInterval() const { return m_acceleratedSurfaceDeletionInterval; }

    void setSimpleFontDataDeletionInterval(double interval) { m_simpleFontDataDeletionInterval = interval; }
    double simpleFontDataDeletionInterval() const { return m_simpleFontDataDeletionInterval; }

    void didAccessDecodedData(CachedResource&);
    void insertInAccessOrderedResourcesList(CachedResource&);
    void removeFromAccessOrderedResourcesList(CachedResource&);

    // Called to adjust the cache totals when a resource changes size.
    void adjustSize(bool live, int delta);

    void addToLiveResourcesSize(CachedResource*);
    void removeFromLiveResourcesSize(CachedResource*);

    static void removeUrlFromCache(ScriptExecutionContext*, const String& urlString);

    // Function to collect cache statistics for the caches window in the Safari Debug menu.
    Statistics getStatistics();
    
    void resourceAccessed(CachedResource&);

    typedef HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash> SecurityOriginSet;
    void removeResourcesWithOrigin(SecurityOrigin*);
    void getOriginsWithCache(SecurityOriginSet& origins);

    unsigned minDeadCapacity() const { return m_minDeadCapacity; }
    unsigned maxDeadCapacity() const { return m_maxDeadCapacity; }
    unsigned capacity() const { return m_capacity; }
    unsigned liveSize() const { return m_liveSize; }
    unsigned deadSize() const { return m_deadSize; }

    unsigned largeAnimationCutoff() const { return m_largeAnimationCutoff; }
    void setLargeAnimationCutoff(unsigned cutoff) { m_largeAnimationCutoff = cutoff; }

private:
    MemoryCache();
    ~MemoryCache(); // Not implemented to make sure nobody accidentally calls delete -- WebCore does not delete singletons.
       
#ifndef NDEBUG
    void dumpLRULists(bool includeLive) const;
#endif

    unsigned liveCapacity() const;
    unsigned deadCapacity() const;
    bool needsPruning() const;
    void pruneTimerFired();

    // pruneDead*() - Flush decoded and encoded data from resources not referenced by Web pages.
    // pruneLive*() - Flush decoded data from resources still referenced by Web pages.
    void pruneDeadResourcesToPercentage(float prunePercentage); // Prune to % current size
    void pruneLiveResourcesToPercentage(float prunePercentage);
    void pruneDeadResourcesToSize(unsigned targetSize);
    void pruneLiveResourcesToSize(unsigned targetSize);

    bool evict(CachedResource&);

    static void removeUrlFromCacheImpl(ScriptExecutionContext*, const String& urlString);

    bool m_disabled;  // Whether or not the cache is enabled.
    bool m_pruneEnabled;
    bool m_inPruneResources;
    bool m_inHandleOutOfMemory;

    unsigned m_capacity;
    unsigned m_minDeadCapacity;
    unsigned m_maxDeadCapacity;
    double m_decodedDataDeletionInterval;
    double m_deadDecodedDataDeletionInterval;
    double m_deadFontPlatformDataDeletionInterval;
    double m_acceleratedSurfaceDeletionInterval;
    double m_simpleFontDataDeletionInterval;

    Optional<double> m_decodedScriptDataDeletionInterval;

    unsigned m_liveSize; // The number of bytes currently consumed by "live" resources in the cache.
    unsigned m_deadSize; // The number of bytes currently consumed by "dead" resources in the cache.

    unsigned m_largeAnimationCutoff { 0 };

    using LRUList = WeakListHashSet<CachedResource>;
    LRUList m_accessOrderedResources;

    // A URL-based map of all resources that are in the cache (including the freshest version of objects that are currently being 
    // referenced by a Web page).
    HashMap<String, CachedResource*> m_resources;
    
    bool m_resourcesInUpdating;

    Timer<MemoryCache> m_pruneTimer;
};

ALWAYS_INLINE unsigned MemoryCache::deadCapacity() const
{
    // Dead resource capacity is whatever space is not occupied by live resources, bounded by an independent minimum and maximum.
    unsigned capacity = m_capacity - std::min(m_liveSize, m_capacity); // Start with available capacity.
    capacity = std::max(capacity, m_minDeadCapacity); // Make sure it's above the minimum.
    capacity = std::min(capacity, m_maxDeadCapacity); // Make sure it's below the maximum.
    return capacity;
}

ALWAYS_INLINE unsigned MemoryCache::liveCapacity() const
{
    // Live resource capacity is whatever is left over after calculating dead resource capacity.
    return m_capacity - deadCapacity();
}

ALWAYS_INLINE bool MemoryCache::needsPruning() const
{
    return m_liveSize + m_deadSize > m_capacity || m_deadSize > m_maxDeadCapacity;
}

ALWAYS_INLINE void MemoryCache::prune()
{
    if (!needsPruning())
        return;

    pruneDeadResources(); // Prune dead first, in case it was "borrowing" capacity from live.
    pruneLiveResources();
}

}
