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

#include <stdint.h>

#include "mozilla/Atomics.h"
#include "mozilla/ServoUtils.h"
#include "mozilla/dom/DOMString.h"
#include "nsAttrValue.h"

namespace mozilla {
class ShadowParts;
}

struct MiscContainer final {
  using ValueType = nsAttrValue::ValueType;

  ValueType mType;
  // mStringBits points to either nsAtom* or mozilla::StringBuffer* and is used
  // when mType isn't eCSSDeclaration. Note eStringBase and eAtomBase is used
  // also to handle the type of mStringBits.
  //
  // Note that we use an atomic here so that we can use Compare-And-Swap
  // to cache the serialization during the parallel servo traversal. This case
  // (which happens when the main thread is blocked) is the only case where
  // mStringBits is mutated off-main-thread. The Atomic needs to be
  // ReleaseAcquire so that the pointer to the serialization does not become
  // observable to other threads before the initialization of the pointed-to
  // memory is also observable.
  mozilla::Atomic<uintptr_t, mozilla::ReleaseAcquire> mStringBits;
  union {
    struct {
      union {
        int32_t mInteger;
        nscolor mColor;
        uint32_t mEnumValue;
        mozilla::DeclarationBlock* mCSSDeclaration;
        nsIURI* mURL;
        const mozilla::AttrAtomArray* mAtomArray;
        const mozilla::ShadowParts* mShadowParts;
        const mozilla::SVGAnimatedIntegerPair* mSVGAnimatedIntegerPair;
        const mozilla::SVGAnimatedLength* mSVGLength;
        const mozilla::SVGAnimatedNumberPair* mSVGAnimatedNumberPair;
        const mozilla::SVGAnimatedOrient* mSVGAnimatedOrient;
        const mozilla::SVGAnimatedPreserveAspectRatio*
            mSVGAnimatedPreserveAspectRatio;
        const mozilla::SVGAnimatedViewBox* mSVGAnimatedViewBox;
        const mozilla::SVGLengthList* mSVGLengthList;
        const mozilla::SVGNumberList* mSVGNumberList;
        const mozilla::SVGPathData* mSVGPathData;
        const mozilla::SVGPointList* mSVGPointList;
        const mozilla::SVGStringList* mSVGStringList;
        const mozilla::SVGTransformList* mSVGTransformList;
      };
      uint32_t mRefCount : 31;
      uint32_t mCached : 1;
    } mValue;
    double mDoubleValue;
  };

  MiscContainer() : mType(nsAttrValue::eColor), mStringBits(0) {
    MOZ_COUNT_CTOR(MiscContainer);
    mValue.mColor = 0;
    mValue.mRefCount = 0;
    mValue.mCached = 0;
  }

 protected:
  // Only nsAttrValue should be able to delete us.
  friend class nsAttrValue;

  ~MiscContainer() {
    if (IsRefCounted()) {
      MOZ_ASSERT(mValue.mRefCount == 0);
      MOZ_ASSERT(!mValue.mCached);
    }
    MOZ_COUNT_DTOR(MiscContainer);
  }

 public:
  bool GetString(nsAString& aString) const;

  void* GetStringOrAtomPtr(bool& aIsString) const {
    uintptr_t bits = mStringBits;
    aIsString =
        nsAttrValue::ValueBaseType(mStringBits & NS_ATTRVALUE_BASETYPE_MASK) ==
        nsAttrValue::eStringBase;
    return reinterpret_cast<void*>(bits & NS_ATTRVALUE_POINTERVALUE_MASK);
  }

  nsAtom* GetStoredAtom() const {
    bool isString = false;
    void* ptr = GetStringOrAtomPtr(isString);
    return isString ? nullptr : static_cast<nsAtom*>(ptr);
  }

  mozilla::StringBuffer* GetStoredStringBuffer() const {
    bool isString = false;
    void* ptr = GetStringOrAtomPtr(isString);
    return isString ? static_cast<mozilla::StringBuffer*>(ptr) : nullptr;
  }

  void SetStringBitsMainThread(uintptr_t aBits) {
    // mStringBits is atomic, but the callers of this function are
    // single-threaded so they don't have to worry about it.
    MOZ_ASSERT(!mozilla::IsInServoTraversal());
    MOZ_ASSERT(NS_IsMainThread());
    mStringBits = aBits;
  }

  inline bool IsRefCounted() const {
    // Nothing stops us from refcounting (and sharing) other types of
    // MiscContainer (except eDoubleValue types) but there's no compelling
    // reason to.
    return mType == nsAttrValue::eAtomArray ||
           mType == nsAttrValue::eCSSDeclaration ||
           mType == nsAttrValue::eShadowParts;
  }

  inline int32_t AddRef() {
    MOZ_ASSERT(IsRefCounted());
    return ++mValue.mRefCount;
  }

  inline int32_t Release() {
    MOZ_ASSERT(IsRefCounted());
    return --mValue.mRefCount;
  }

  void Cache();
  void Evict();
};

/**
 * Implementation of inline methods
 */

inline int32_t nsAttrValue::GetIntegerValue() const {
  MOZ_ASSERT(Type() == eInteger, "wrong type");
  return (BaseType() == eIntegerBase) ? GetIntInternal()
                                      : GetMiscContainer()->mValue.mInteger;
}

inline int16_t nsAttrValue::GetEnumValue() const {
  MOZ_ASSERT(Type() == eEnum, "wrong type");
  // We don't need to worry about sign extension here since we're
  // returning an int16_t which will cut away the top bits.
  return static_cast<int16_t>(((BaseType() == eIntegerBase)
                                   ? static_cast<uint32_t>(GetIntInternal())
                                   : GetMiscContainer()->mValue.mEnumValue) >>
                              NS_ATTRVALUE_ENUMTABLEINDEX_BITS);
}

inline double nsAttrValue::GetPercentValue() const {
  MOZ_ASSERT(Type() == ePercent, "wrong type");
  if (BaseType() == eIntegerBase) {
    return GetIntInternal() / 100.0f;
  }
  return GetMiscContainer()->mDoubleValue / 100.0f;
}

inline const mozilla::AttrAtomArray* nsAttrValue::GetAtomArrayValue() const {
  MOZ_ASSERT(Type() == eAtomArray, "wrong type");
  return GetMiscContainer()->mValue.mAtomArray;
}

inline mozilla::DeclarationBlock* nsAttrValue::GetCSSDeclarationValue() const {
  MOZ_ASSERT(Type() == eCSSDeclaration, "wrong type");
  return GetMiscContainer()->mValue.mCSSDeclaration;
}

inline nsIURI* nsAttrValue::GetURLValue() const {
  MOZ_ASSERT(Type() == eURL, "wrong type");
  return GetMiscContainer()->mValue.mURL;
}

inline double nsAttrValue::GetDoubleValue() const {
  MOZ_ASSERT(Type() == eDoubleValue, "wrong type");
  return GetMiscContainer()->mDoubleValue;
}

inline bool nsAttrValue::IsSVGType(ValueType aType) const {
  return aType >= eSVGTypesBegin && aType <= eSVGTypesEnd;
}

inline bool nsAttrValue::StoresOwnData() const {
  // Only SVG attributes don't store their own data.
  // FIXME(emilio): This is a pretty terrible set-up.
  return BaseType() != eOtherBase || !IsSVGType(Type());
}

inline void nsAttrValue::SetPtrValueAndType(void* aValue, ValueBaseType aType) {
  NS_ASSERTION(!(NS_PTR_TO_INT32(aValue) & ~NS_ATTRVALUE_POINTERVALUE_MASK),
               "pointer not properly aligned, this will crash");
  mBits = reinterpret_cast<intptr_t>(aValue) | aType;
}

inline void nsAttrValue::ResetIfSet() {
  if (mBits) {
    Reset();
  }
}

inline MiscContainer* nsAttrValue::GetMiscContainer() const {
  NS_ASSERTION(BaseType() == eOtherBase, "wrong type");
  return static_cast<MiscContainer*>(GetPtr());
}

inline int32_t nsAttrValue::GetIntInternal() const {
  NS_ASSERTION(BaseType() == eIntegerBase, "getting integer from non-integer");
  // Make sure we get a signed value.
  // Lets hope the optimizer optimizes this into a shift. Unfortunatly signed
  // bitshift right is implementaion dependant.
  return static_cast<int32_t>(mBits & ~NS_ATTRVALUE_INTEGERTYPE_MASK) /
         NS_ATTRVALUE_INTEGERTYPE_MULTIPLIER;
}

inline nsAttrValue::ValueType nsAttrValue::Type() const {
  switch (BaseType()) {
    case eIntegerBase: {
      return static_cast<ValueType>(mBits & NS_ATTRVALUE_INTEGERTYPE_MASK);
    }
    case eOtherBase: {
      return GetMiscContainer()->mType;
    }
    default: {
      return static_cast<ValueType>(static_cast<uint16_t>(BaseType()));
    }
  }
}

inline nsAtom* nsAttrValue::GetAtomValue() const {
  MOZ_ASSERT(Type() == eAtom, "wrong type");
  return reinterpret_cast<nsAtom*>(GetPtr());
}

inline void nsAttrValue::ToString(mozilla::dom::DOMString& aResult) const {
  switch (Type()) {
    case eString: {
      if (auto* str = static_cast<mozilla::StringBuffer*>(GetPtr())) {
        aResult.SetKnownLiveStringBuffer(
            str, str->StorageSize() / sizeof(char16_t) - 1);
      }
      // else aResult is already empty
      return;
    }
    case eAtom: {
      nsAtom* atom = static_cast<nsAtom*>(GetPtr());
      aResult.SetKnownLiveAtom(atom, mozilla::dom::DOMString::eNullNotExpected);
      break;
    }
    default: {
      ToString(static_cast<nsAString&>(aResult));
    }
  }
}

inline const mozilla::ShadowParts& nsAttrValue::GetShadowPartsValue() const {
  MOZ_ASSERT(Type() == eShadowParts);
  return *GetMiscContainer()->mValue.mShadowParts;
}

#endif
