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

#include <type_traits>

#include "mozilla/Atomics.h"
#include "mozilla/Char16.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/TextUtils.h"
#include "nsISupports.h"
#include "nsString.h"

namespace mozilla {
struct AtomsSizes;
}  // namespace mozilla

class nsStaticAtom;
class nsDynamicAtom;

// This class encompasses both static and dynamic atoms.
//
// - In places where static and dynamic atoms can be used, use RefPtr<nsAtom>.
//   This is by far the most common case.
//
// - In places where only static atoms can appear, use nsStaticAtom* to avoid
//   unnecessary refcounting. This is a moderately common case.
//
// - In places where only dynamic atoms can appear, it doesn't matter much
//   whether you use RefPtr<nsAtom> or RefPtr<nsDynamicAtom>. This is an
//   extremely rare case.
//
class nsAtom {
 public:
  // Returns true if ToLowercaseASCII would return the string unchanged.
  //
  // This is deliberately a plain loop rather than std::all_of: gGkAtoms calls
  // this for every static atom within a single constant expression, and
  // std::all_of costs enough extra constexpr steps to exceed the default
  // -fconstexpr-steps budget with some standard library implementations.
  static constexpr bool ComputeIsAsciiLowercase(const char16_t* aString,
                                                const uint32_t aLength) {
    for (uint32_t i = 0; i < aLength; ++i) {
      if (mozilla::IsAsciiUppercaseAlpha(aString[i])) {
        return false;
      }
    }
    return true;
  }

  template <size_t N>
  static constexpr bool ComputeIsAsciiLowercase(const char16_t (&aString)[N]) {
    return ComputeIsAsciiLowercase(aString, N - 1);
  }

  void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                              mozilla::AtomsSizes& aSizes) const;

  bool Equals(char16ptr_t aString, uint32_t aLength) const {
    return mLength == aLength &&
           memcmp(GetUTF16String(), aString, mLength * sizeof(char16_t)) == 0;
  }

  bool Equals(const nsAString& aString) const {
    return Equals(aString.Data(), aString.Length());
  }

  bool IsStatic() const { return mIsStatic; }
  bool IsDynamic() const { return !IsStatic(); }

  inline const nsStaticAtom* AsStatic() const;
  inline const nsDynamicAtom* AsDynamic() const;
  inline nsDynamicAtom* AsDynamic();

  inline char16ptr_t GetUTF16String() const;

  uint32_t GetLength() const { return mLength; }

  operator mozilla::Span<const char16_t>() const {
    // Explicitly specify template argument here to avoid instantiating
    // Span<char16_t> first and then implicitly converting to Span<const
    // char16_t>
    return mozilla::Span<const char16_t>{GetUTF16String(), GetLength()};
  }

  void ToString(nsAString& aString) const;
  void ToUTF8String(nsACString& aString) const;

  // A hashcode that is better distributed than the actual atom pointer, for
  // use in situations that need a well-distributed hashcode. It's called hash()
  // rather than Hash() so we can use mozilla::BloomFilter<N, nsAtom>, because
  // BloomFilter requires elements to implement a function called hash().
  //
  uint32_t hash() const { return mHash; }

  // This function returns true if ToLowercaseASCII would return the string
  // unchanged.
  bool IsAsciiLowercase() const { return mIsAsciiLowercase; }

  // This function returns true if this is the empty atom. This is exactly
  // equivalent to `this == nsGkAtoms::_empty`, but it's a bit less foot-gunny,
  // since we also have `nsGkAtoms::empty`.
  //
  // Defined in nsGkAtoms.h
  inline bool IsEmpty() const;

  // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting
  // of this type is special.
  inline MozExternalRefCountType AddRef();
  inline MozExternalRefCountType Release();

  using HasThreadSafeRefCnt = std::true_type;

 protected:
  constexpr nsAtom(uint32_t aLength, bool aIsStatic, uint32_t aHash,
                   bool aIsAsciiLowercase)
      : mLength(aLength),
        mIsStatic(aIsStatic),
        mIsAsciiLowercase(aIsAsciiLowercase),
        mHash(aHash) {}

  ~nsAtom() = default;

  const uint32_t mLength : 30;
  const uint32_t mIsStatic : 1;
  const uint32_t mIsAsciiLowercase : 1;
  const uint32_t mHash;
};

class nsStaticAtom final : public nsAtom {
 public:
  // These are deleted so it's impossible to RefPtr<nsStaticAtom>. Raw
  // nsStaticAtom pointers should be used instead.
  MozExternalRefCountType AddRef() = delete;
  MozExternalRefCountType Release() = delete;

  // The static atom's precomputed hash value is an argument here, but it
  // must be the same as would be computed by mozilla::HashString(aStr),
  // which is what we use when atomizing strings. We compute this hash in
  // Atom.py and assert in nsAtomTable::RegisterStaticAtoms that the two
  // hashes match.
  constexpr nsStaticAtom(uint32_t aLength, uint32_t aHash,
                         uint32_t aStringOffset, bool aIsAsciiLowercase)
      : nsAtom(aLength, /* aIsStatic = */ true, aHash, aIsAsciiLowercase),
        mStringOffset(aStringOffset) {}

  const char16_t* String() const {
    return reinterpret_cast<const char16_t*>(uintptr_t(this) - mStringOffset);
  }

  already_AddRefed<nsAtom> ToAddRefed() {
    return already_AddRefed<nsAtom>(static_cast<nsAtom*>(this));
  }

 private:
  // This is an offset to the string chars, which must be at a lower address in
  // memory.
  uint32_t mStringOffset;
};

class nsDynamicAtom : public nsAtom {
 public:
  // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting
  // of this type is special.
  MozExternalRefCountType AddRef() {
    MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt");
    nsrefcnt count = ++mRefCnt;
    if (count == 1) {
      gUnusedAtomCount--;
    }
    return count;
  }

  MozExternalRefCountType Release() {
#ifdef DEBUG
    // We set a lower GC threshold for atoms in debug builds so that we exercise
    // the GC machinery more often.
    static const int32_t kAtomGCThreshold = 20;
#else
    static const int32_t kAtomGCThreshold = 10000;
#endif

    MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release");
    nsrefcnt count = --mRefCnt;
    if (count == 0) {
      if (++gUnusedAtomCount >= kAtomGCThreshold) {
        ScheduleAtomTableGC();
      }
    }

    return count;
  }

  mozilla::StringBuffer* StringBuffer() const { return mStringBuffer; }

  const char16_t* String() const {
    return reinterpret_cast<const char16_t*>(mStringBuffer->Data());
  }

 private:
  friend class nsAtomTable;
  friend class nsAtomSubTable;
  friend int32_t NS_GetUnusedAtomCount();

  static mozilla::Atomic<int32_t, mozilla::ReleaseAcquire> gUnusedAtomCount;
  static void ScheduleAtomTableGC();

  // These shouldn't be used directly, even by friend classes. The
  // Create()/Destroy() methods use them.
  nsDynamicAtom(already_AddRefed<mozilla::StringBuffer>, uint32_t aLength,
                uint32_t aHash, bool aIsAsciiLowercase);
  ~nsDynamicAtom() = default;

  static nsDynamicAtom* Create(const nsAString& aString, uint32_t aHash);
  static void Destroy(nsDynamicAtom* aAtom);

  mozilla::ThreadSafeAutoRefCnt mRefCnt;
  RefPtr<mozilla::StringBuffer> mStringBuffer;
};

const nsStaticAtom* nsAtom::AsStatic() const {
  MOZ_ASSERT(IsStatic());
  return static_cast<const nsStaticAtom*>(this);
}

const nsDynamicAtom* nsAtom::AsDynamic() const {
  MOZ_ASSERT(IsDynamic());
  return static_cast<const nsDynamicAtom*>(this);
}

nsDynamicAtom* nsAtom::AsDynamic() {
  MOZ_ASSERT(IsDynamic());
  return static_cast<nsDynamicAtom*>(this);
}

MozExternalRefCountType nsAtom::AddRef() {
  return IsStatic() ? 2 : AsDynamic()->AddRef();
}

MozExternalRefCountType nsAtom::Release() {
  return IsStatic() ? 1 : AsDynamic()->Release();
}

char16ptr_t nsAtom::GetUTF16String() const {
  return IsStatic() ? AsStatic()->String() : AsDynamic()->String();
}

// The four forms of NS_Atomize (for use with |RefPtr<nsAtom>|) return the
// atom for the string given. At any given time there will always be one atom
// representing a given string. Atoms are intended to make string comparison
// cheaper by simplifying it to pointer equality. A pointer to the atom that
// does not own a reference is not guaranteed to be valid.

// Find an atom that matches the given UTF-8 string. The string is assumed to
// be zero terminated. Never returns null.
already_AddRefed<nsAtom> NS_Atomize(const char* aUTF8String);

// Find an atom that matches the given UTF-8 string. Never returns null.
already_AddRefed<nsAtom> NS_Atomize(const nsACString& aUTF8String);

// Find an atom that matches the given UTF-16 string. The string is assumed to
// be zero terminated. Never returns null.
already_AddRefed<nsAtom> NS_Atomize(const char16_t* aUTF16String);

// Find an atom that matches the given UTF-16 string. Never returns null.
already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String);

// Find an atom that matches the given UTF-16 string, with a known
// already-computed hash via mozilla::HashString. Never returns null.
already_AddRefed<nsAtom> NS_Atomize(const nsAString& aUTF16String,
                                    uint32_t aKnownHash);

// An optimized version of the method above for the main thread.
already_AddRefed<nsAtom> NS_AtomizeMainThread(const nsAString& aUTF16String);

// Return a count of the total number of atoms currently alive in the system.
//
// Note that the result is imprecise and racy if other threads are currently
// operating on atoms. It's also slow, since it triggers a GC before counting.
// Currently this function is only used in tests, which should probably remain
// the case.
nsrefcnt NS_GetNumberOfAtoms();

// Return a pointer for a static atom for the string or null if there's no
// static atom for this string.
nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String);

class nsAtomString : public nsString {
 public:
  explicit nsAtomString(const nsAtom* aAtom) { aAtom->ToString(*this); }
};

class nsAtomCString : public nsCString {
 public:
  explicit nsAtomCString(const nsAtom* aAtom) { aAtom->ToUTF8String(*this); }
};

class nsAutoAtomCString : public nsAutoCString {
 public:
  explicit nsAutoAtomCString(const nsAtom* aAtom) {
    aAtom->ToUTF8String(*this);
  }
};

class nsDependentAtomString : public nsDependentString {
 public:
  explicit nsDependentAtomString(const nsAtom* aAtom)
      : nsDependentString(
            aAtom->GetUTF16String(), aAtom->GetLength(),
            aAtom->IsStatic() ? DataFlags::LITERAL : DataFlags::STRINGBUFFER) {}
};

// Checks if the ascii chars in a given atom are already lowercase.
// If they are, no-op. Otherwise, converts all the ascii uppercase
// chars to lowercase and atomizes, storing the result in the inout
// param.
void ToLowerCaseASCII(RefPtr<nsAtom>& aAtom);

// MOZ_DBG support
inline std::ostream& operator<<(std::ostream& aStream, const nsAtom& aAtom) {
  return aStream << (aAtom.IsDynamic() ? "nsDynamicAtom" : "nsStaticAtom")
                 << " { " << nsAtomCString(&aAtom) << " }";
}

#endif  // nsAtom_h
