/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef CacheStorage_h_
#define CacheStorage_h_

#include "nsICacheStorage.h"
#include "CacheEntry.h"
#include "LoadContextInfo.h"

#include "nsILoadContextInfo.h"
#include "nsTArray.h"
#include "nsTHashMap.h"

class nsIURI;

namespace mozilla {
namespace net {

// This dance is needed to make CacheEntryTable declarable-only in headers
// w/o exporting CacheEntry.h file to make nsNetModule.cpp compilable.
using TCacheEntryTable = nsRefPtrHashtable<nsCStringHashKey, CacheEntry>;
class CacheEntryTable : public TCacheEntryTable {
 public:
  enum EType { MEMORY_ONLY, ALL_ENTRIES };

  explicit CacheEntryTable(EType aType) : mType(aType) {}
  CacheEntryTable() = delete;

  EType Type() const { return mType; }

  // Secondary index for No-Vary-Search cache lookup.
  //
  // Without NVS, a cache lookup is a single exact-key hash lookup on this
  // table. With NVS, a response can declare that certain query parameters do
  // not affect the response (e.g. "No-Vary-Search: params=(\"utm_source\")"),
  // meaning /page?q=hello&utm_source=email should hit the same cache entry as
  // /page?q=hello. An exact-key lookup misses in this case, so we need a way
  // to find candidate entries that share the same base path.
  //
  // This index maps scheme://host:port/path (no query string) to the list of
  // full entry keys — the cache storage keys used to look up CacheEntry
  // objects in this table — for all entries that carry NVS metadata. On an
  // exact-key miss, AddStorageEntry() consults this index to find candidates
  // and checks each one for URL equivalence under its stored NVS header.
  //
  // A "full entry key" is the string passed to CacheEntry::HashingKey(), of
  // the form "https://example.com/page?q=hello", uniquely identifying the
  // cached response within this CacheEntryTable.
  //
  // Protected by CacheStorageService::sLock.
  nsTHashMap<nsCStringHashKey, nsTArray<nsCString>> mNoVarySearchIndex;

  void NoteNoVarySearchEntry(const nsACString& aBasePath,
                             const nsACString& aFullKey) {
    mNoVarySearchIndex.LookupOrInsert(aBasePath).AppendElement(aFullKey);
  }

  void RemoveNoVarySearchEntry(const nsACString& aBasePath,
                               const nsACString& aFullKey) {
    auto entry = mNoVarySearchIndex.Lookup(aBasePath);
    if (!entry) {
      return;
    }
    entry->RemoveElement(aFullKey);
    if (entry->IsEmpty()) {
      mNoVarySearchIndex.Remove(aBasePath);
    }
  }

  void RemoveNoVarySearchEntryByKey(const nsACString& aFullKey) {
    for (auto iter = mNoVarySearchIndex.Iter(); !iter.Done(); iter.Next()) {
      if (iter.Data().RemoveElement(aFullKey)) {
        if (iter.Data().IsEmpty()) {
          iter.Remove();
        }
        break;
      }
    }
  }

 private:
  EType const mType;
};

class CacheStorage : public nsICacheStorage {
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSICACHESTORAGE

 public:
  CacheStorage(nsILoadContextInfo* aInfo, bool aAllowDisk, bool aSkipSizeCheck,
               bool aPinning);

 protected:
  virtual ~CacheStorage() = default;

  RefPtr<LoadContextInfo> mLoadContextInfo;
  bool mWriteToDisk : 1;
  bool mSkipSizeCheck : 1;
  bool mPinning : 1;

 public:
  nsILoadContextInfo* LoadInfo() const { return mLoadContextInfo; }
  bool WriteToDisk() const {
    return mWriteToDisk &&
           (!mLoadContextInfo || !mLoadContextInfo->IsPrivate());
  }
  bool SkipSizeCheck() const { return mSkipSizeCheck; }
  bool Pinning() const { return mPinning; }
};

}  // namespace net
}  // namespace mozilla

#endif
