/* 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 DOM_QUOTA_ORIGININFO_H_
#define DOM_QUOTA_ORIGININFO_H_

#include "Assertions.h"
#include "ClientUsageArray.h"
#include "mozilla/ThreadSafeWeakPtr.h"
#include "mozilla/dom/quota/QuotaManager.h"

namespace mozilla::dom::quota {

class CanonicalQuotaObject;
class GroupInfo;

class OriginInfo final : public SupportsThreadSafeWeakPtr<OriginInfo> {
  friend class CanonicalQuotaObject;
  friend class GroupInfo;
  friend class PersistOp;
  friend class QuotaManager;
  friend class SupportsThreadSafeWeakPtr<OriginInfo>;

 public:
  MOZ_DECLARE_REFCOUNTED_TYPENAME(OriginInfo)

  OriginInfo(GroupInfo* aGroupInfo, const nsACString& aOrigin,
             const nsACString& aStorageOrigin, bool aIsPrivate,
             const ClientUsageArray& aClientUsages, uint64_t aUsage,
             int64_t aAccessTime, int32_t aMaintenanceDate, bool aPersisted,
             bool aDirectoryExists);

  GroupInfo* GetGroupInfo() const { return mGroupInfo; }

  const nsCString& Origin() const { return mOrigin; }

  int64_t LockedUsage() const {
    AssertCurrentThreadOwnsQuotaMutex();

#ifdef DEBUG
    QuotaManager* quotaManager = QuotaManager::Get();
    MOZ_ASSERT(quotaManager);

    uint64_t usage = 0;
    for (Client::Type type : quotaManager->AllClientTypes()) {
      AssertNoOverflow(usage, mClientUsages[type].valueOr(0));
      usage += mClientUsages[type].valueOr(0);
    }
    MOZ_ASSERT(mUsage == usage);
#endif

    return mUsage;
  }

  int64_t LockedAccessTime() const {
    AssertCurrentThreadOwnsQuotaMutex();

    return mAccessTime;
  }

  int32_t LockedMaintenanceDate() const {
    AssertCurrentThreadOwnsQuotaMutex();

    return mMaintenanceDate;
  }

  bool LockedAccessed() const {
    AssertCurrentThreadOwnsQuotaMutex();

    return mAccessed;
  }

  bool LockedPersisted() const {
    AssertCurrentThreadOwnsQuotaMutex();

    return mPersisted;
  }

  bool IsExtensionOrigin() const { return mIsExtension; }

  bool LockedDirectoryExists() const {
    AssertCurrentThreadOwnsQuotaMutex();

    return mDirectoryExists;
  }

  OriginMetadata FlattenToOriginMetadata() const;

  OriginStateMetadata LockedFlattenToOriginStateMetadata() const;

  FullOriginMetadata LockedFlattenToFullOriginMetadata() const;

  nsresult LockedBindToStatement(mozIStorageStatement* aStatement) const;

 private:
  // Private destructor, to discourage deletion outside of Release():
  ~OriginInfo() {
    MOZ_COUNT_DTOR(OriginInfo);

    MOZ_ASSERT(!mCanonicalQuotaObjects.Count());
  }

  void LockedDecreaseUsage(Client::Type aClientType, int64_t aSize);

  void LockedResetUsageForClient(Client::Type aClientType);

  UsageInfo LockedGetUsageForClient(Client::Type aClientType);

  void LockedUpdateAccessTime(int64_t aAccessTime) {
    AssertCurrentThreadOwnsQuotaMutex();

    mAccessTime = aAccessTime;
    if (!mAccessed) {
      mAccessed = true;
    }
  }

  void LockedUpdateMaintenanceDate(int32_t aMaintenanceDate) {
    AssertCurrentThreadOwnsQuotaMutex();

    mMaintenanceDate = aMaintenanceDate;
  }

  void LockedUpdateAccessed() {
    AssertCurrentThreadOwnsQuotaMutex();

    if (!mAccessed) {
      mAccessed = true;
    }
  }

  void LockedPersist();

  void LockedDirectoryCreated();

  void LockedTruncateUsages(Client::Type aClientType, uint64_t aDelta);

  Maybe<bool> LockedUpdateUsages(Client::Type aClientType, uint64_t aDelta);

  bool LockedUpdateUsagesForEviction(Client::Type aClientType, uint64_t aDelta);

  nsTHashMap<nsStringHashKey, NotNull<CanonicalQuotaObject*>>
      mCanonicalQuotaObjects;
  GroupInfo* mGroupInfo;
  const nsCString mOrigin;
  const nsCString mStorageOrigin;
  int64_t mAccessTime;
  int32_t mMaintenanceDate;
  bool mIsPrivate;
  bool mAccessed;
  bool mPersisted;
  const bool mIsExtension;
  /**
   * In some special cases like the LocalStorage client where it's possible to
   * create a Quota-using representation but not actually write any data, we
   * want to be able to track quota for an origin without creating its origin
   * directory or the per-client files until they are actually needed to store
   * data. In those cases, the OriginInfo will be created by
   * InitQuotaForOrigin and the resulting mDirectoryExists will be false until
   * the origin actually needs to be created. It is possible for mUsage to be
   * greater than zero while mDirectoryExists is false, representing a state
   * where a client like LocalStorage has reserved quota for disk writes, but
   * has not yet flushed the data to disk.
   */
  bool mDirectoryExists;

 private:
  ClientUsageArray mClientUsages;
  uint64_t mUsage;
};

class OriginInfoAccessTimeComparator {
 public:
  bool Equals(const NotNull<RefPtr<const OriginInfo>>& a,
              const NotNull<RefPtr<const OriginInfo>>& b) const {
    return a->LockedAccessTime() == b->LockedAccessTime();
  }

  bool LessThan(const NotNull<RefPtr<const OriginInfo>>& a,
                const NotNull<RefPtr<const OriginInfo>>& b) const {
    return a->LockedAccessTime() < b->LockedAccessTime();
  }
};

}  // namespace mozilla::dom::quota

#endif  // DOM_QUOTA_ORIGININFO_H_
