/*
 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
 *
 * Squid software is distributed under GPLv2+ license and includes
 * contributions from numerous individuals and organizations.
 * Please see the COPYING and CONTRIBUTORS files for details.
 */

#ifndef SQUID_STORE_CONTROLLER_H
#define SQUID_STORE_CONTROLLER_H

#include "store/Storage.h"

class MemObject;
class RequestFlags;
class HttpRequestMethod;

namespace Store {

/// Public Store interface. Coordinates the work of memory/disk/transient stores
/// and hides their individual existence/differences from the callers.
class Controller: public Storage
{
public:
    Controller();
    virtual ~Controller() override;

    /* Storage API */
    virtual void create() override;
    virtual void init() override;
    virtual uint64_t maxSize() const override;
    virtual uint64_t minSize() const override;
    virtual uint64_t currentSize() const override;
    virtual uint64_t currentCount() const override;
    virtual int64_t maxObjectSize() const override;
    virtual void getStats(StoreInfoStats &stats) const override;
    virtual void stat(StoreEntry &) const override;
    virtual void sync() override;
    virtual void maintain() override;
    virtual void evictCached(StoreEntry &) override;
    virtual void evictIfFound(const cache_key *) override;
    virtual int callback() override;

    /// \returns a locally indexed and SMP-tracked matching StoreEntry (or nil)
    /// Slower than peek() but does not restrict StoreEntry use and storage.
    /// Counts as an entry reference from the removal policy p.o.v.
    StoreEntry *find(const cache_key *);

    /// \returns a matching StoreEntry not suitable for long-term use (or nil)
    /// Faster than find() but the returned entry may not receive updates, may
    /// lack information from some of the Stores, and should not be updated
    /// except that purging peek()ed entries is supported.
    /// Does not count as an entry reference from the removal policy p.o.v.
    StoreEntry *peek(const cache_key *);

    /// \returns matching StoreEntry associated with local ICP/HTCP transaction
    /// Warning: The returned StoreEntry is not synced and may be marked for
    /// deletion. It can only be used for extracting transaction callback details.
    /// New code should be designed to avoid this deprecated API.
    StoreEntry *findCallbackXXX(const cache_key *);

    /// Whether a transient entry with the given public key exists and (but) was
    /// marked for removal some time ago; get(key) returns nil in such cases.
    bool markedForDeletion(const cache_key *key) const;

    /// markedForDeletion() with no readers
    /// this is one method because the two conditions must be checked in the right order
    bool markedForDeletionAndAbandoned(const StoreEntry &) const;

    /// whether there is a disk entry with e.key
    bool hasReadableDiskEntry(const StoreEntry &) const;

    /// Additional unknown-size entry bytes required by Store in order to
    /// reduce the risk of selecting the wrong disk cache for the growing entry.
    int64_t accumulateMore(StoreEntry &) const;

    /// slowly calculate (and cache) hi/lo watermarks and similar limits
    void updateLimits();

    /// called when the entry is no longer needed by any transaction
    void handleIdleEntry(StoreEntry &);

    /// Evict memory cache entries to free at least `spaceRequired` bytes.
    /// Should be called via storeGetMemSpace().
    /// Unreliable: Fails if enough victims cannot be found fast enough.
    void freeMemorySpace(const int spaceRequired);

    /// called to get rid of no longer needed entry data in RAM, if any
    void memoryOut(StoreEntry &, const bool preserveSwappable);

    /// using a 304 response, update the old entry (metadata and reply headers)
    void updateOnNotModified(StoreEntry *old, StoreEntry &e304);

    /// tries to make the entry available for collapsing future requests
    bool allowCollapsing(StoreEntry *, const RequestFlags &, const HttpRequestMethod &);

    /// register a being-read StoreEntry (to optimize concurrent cache reads
    /// and to receive remote DELETE events)
    void addReading(StoreEntry *, const cache_key *);

    /// register a being-written StoreEntry (to support concurrent cache reads
    /// and to receive remote DELETE events)
    void addWriting(StoreEntry *, const cache_key *);

    /// whether the entry is in "reading from Transients" I/O state
    bool transientsReader(const StoreEntry &) const;

    /// whether the entry is in "writing to Transients" I/O state
    bool transientsWriter(const StoreEntry &) const;

    /// marks the entry completed for collapsed requests
    void transientsCompleteWriting(StoreEntry &);

    /// Update local intransit entry after changes made by appending worker.
    void syncCollapsed(const sfileno);

    /// stop any current (and prevent any future) SMP sharing of the given entry
    void stopSharing(StoreEntry &);

    /// number of the transient entry readers some time ago
    int transientReaders(const StoreEntry &) const;

    /// disassociates the entry from the intransit table
    void transientsDisconnect(StoreEntry &);

    /// removes collapsing requirement (for future hits)
    void transientsClearCollapsingRequirement(StoreEntry &e);

    /// disassociates the entry from the memory cache, preserving cached data
    void memoryDisconnect(StoreEntry &);

    /// \returns an iterator for all Store entries
    StoreSearch *search();

    /// whether there are any SMP-aware storages
    static bool SmpAware();

    /// the number of cache_dirs being rebuilt; TODO: move to Disks::Rebuilding
    static int store_dirs_rebuilding;

private:
    bool memoryCacheHasSpaceFor(const int pagesRequired) const;

    void referenceBusy(StoreEntry &e);
    bool dereferenceIdle(StoreEntry &, bool wantsLocalMemory);

    void allowSharing(StoreEntry &, const cache_key *);
    StoreEntry *peekAtLocal(const cache_key *);

    void memoryEvictCached(StoreEntry &);
    void transientsUnlinkByKeyIfFound(const cache_key *);
    bool keepForLocalMemoryCache(StoreEntry &e) const;
    bool anchorToCache(StoreEntry &e, bool &inSync);
    void checkTransients(const StoreEntry &) const;
    void checkFoundCandidate(const StoreEntry &) const;

    Disks *swapDir; ///< summary view of all disk caches
    Memory *sharedMemStore; ///< memory cache that multiple workers can use
    bool localMemStore; ///< whether local (non-shared) memory cache is enabled

    /// A shared table of public store entries that do not know whether they
    /// will belong to a memory cache, a disk cache, or will be uncachable
    /// when the response header comes. Used for SMP collapsed forwarding.
    Transients *transients;

    /// Hack: Relays page shortage from freeMemorySpace() to handleIdleEntry().
    int memoryPagesDebt_ = 0;
};

/// safely access controller singleton
extern Controller &Root();

/// initialize the storage module; a custom root is used by unit tests only
extern void Init(Controller *root = nullptr);

/// undo Init()
extern void FreeMemory();

} // namespace Store

#endif /* SQUID_STORE_CONTROLLER_H */

