/*
 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#pragma once

#include <WebCore/Attribute.h>
#include <WebCore/SpaceSplitString.h>
#include <wtf/IndexedRange.h>
#include <wtf/StdLibExtras.h>
#include <wtf/TypeCasts.h>
#include <wtf/Vector.h>

namespace WebCore {

class Attr;
class ImmutableStyleProperties;
class ShareableElementData;
class StyleProperties;
class UniqueElementData;

DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(ElementData);
class ElementData : public RefCounted<ElementData> {
    WTF_DEPRECATED_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(ElementData, ElementData);
public:
    // Override RefCounted's deref() to ensure operator delete is called on
    // the appropriate subclass type.
    void deref();

    static const unsigned attributeNotFound = static_cast<unsigned>(-1);

    void setClassNames(SpaceSplitString&& classNames) const { m_classNames = WTF::move(classNames); }
    const SpaceSplitString& classNames() const LIFETIME_BOUND { return m_classNames; }
    static constexpr ptrdiff_t classNamesMemoryOffset() { return OBJECT_OFFSETOF(ElementData, m_classNames); }

    const AtomString& idForStyleResolution() const LIFETIME_BOUND { return m_idForStyleResolution; }
    static constexpr ptrdiff_t idForStyleResolutionMemoryOffset() { return OBJECT_OFFSETOF(ElementData, m_idForStyleResolution); }
    void setIdForStyleResolution(const AtomString& newId) const { m_idForStyleResolution = newId; }

    const StyleProperties* inlineStyle() const { return m_inlineStyle.get(); }
    const ImmutableStyleProperties* presentationalHintStyle() const;

    unsigned length() const;
    bool isEmpty() const { return !length(); }

    std::span<const Attribute> NODELETE attributes() const LIFETIME_BOUND;
    const Attribute& NODELETE attributeAt(unsigned index) const;
    const Attribute* NODELETE findAttributeByName(const QualifiedName&) const;
    unsigned NODELETE findAttributeIndexByName(const QualifiedName&) const;
    unsigned NODELETE findAttributeIndexByName(const AtomString& name, bool shouldIgnoreAttributeCase) const;

    bool hasID() const { return !m_idForStyleResolution.isNull(); }
    bool hasClass() const { return !m_classNames.isEmpty(); }
    bool hasName() const { return m_arraySizeAndFlags & s_flagHasNameAttribute; }

    bool NODELETE isEquivalent(const ElementData* other) const;

    bool isUnique() const { return m_arraySizeAndFlags & s_flagIsUnique; }
    static uint32_t isUniqueFlag() { return s_flagIsUnique; }

    static constexpr ptrdiff_t arraySizeAndFlagsMemoryOffset() { return OBJECT_OFFSETOF(ElementData, m_arraySizeAndFlags); }
    static inline uint32_t styleAttributeIsDirtyFlag() { return s_flagStyleAttributeIsDirty; }
    static uint32_t animatedSVGAttributesAreDirtyFlag() { return s_flagAnimatedSVGAttributesAreDirty; }

    static uint32_t arraySizeOffset() { return s_flagCount; }

private:
    mutable uint32_t m_arraySizeAndFlags;

    static const uint32_t s_arraySize = 27;
    static const uint32_t s_flagCount = 5;
    static const uint32_t s_flagIsUnique = 1;
    static const uint32_t s_flagHasNameAttribute = 1 << 1;
    static const uint32_t s_flagPresentationalHintStyleIsDirty = 1 << 2;
    static const uint32_t s_flagStyleAttributeIsDirty = 1 << 3;
    static const uint32_t s_flagAnimatedSVGAttributesAreDirty = 1 << 4;
    static const uint32_t s_flagsMask = (1 << s_flagCount) - 1;
    // FIXME: could the SVG specific flags go to some SVG class?

    inline void updateFlag(uint32_t flag, bool set) const
    {
        if (set)
            m_arraySizeAndFlags |= flag;
        else
            m_arraySizeAndFlags &= ~flag;
    }
    static inline uint32_t NODELETE arraySizeAndFlagsFromOther(const ElementData& other, bool isUnique);

protected:
    ElementData();
    explicit ElementData(unsigned arraySize);
    ElementData(const ElementData&, bool isUnique);

    unsigned arraySize() const { return m_arraySizeAndFlags >> s_flagCount; }

    void setHasNameAttribute(bool hasName) const { updateFlag(s_flagHasNameAttribute, hasName); }

    bool styleAttributeIsDirty() const { return m_arraySizeAndFlags & s_flagStyleAttributeIsDirty; }
    void setStyleAttributeIsDirty(bool isDirty) const { updateFlag(s_flagStyleAttributeIsDirty, isDirty); }

    bool presentationalHintStyleIsDirty() const { return m_arraySizeAndFlags & s_flagPresentationalHintStyleIsDirty; }
    void setPresentationalHintStyleIsDirty(bool isDirty) const { updateFlag(s_flagPresentationalHintStyleIsDirty, isDirty); }

    bool animatedSVGAttributesAreDirty() const { return m_arraySizeAndFlags & s_flagAnimatedSVGAttributesAreDirty; }
    void setAnimatedSVGAttributesAreDirty(bool dirty) const { updateFlag(s_flagAnimatedSVGAttributesAreDirty, dirty); }

    mutable RefPtr<StyleProperties> m_inlineStyle;
    mutable SpaceSplitString m_classNames;
    mutable AtomString m_idForStyleResolution;

private:
    friend class Element;
    friend class StyledElement;
    friend class ShareableElementData;
    friend class UniqueElementData;
    friend class SVGElement;
    friend class HTMLImageElement;

    void destroy();

    const Attribute* attributeBase() const;
    std::span<const Attribute> attributeSpan() const { return unsafeMakeSpan(attributeBase(), length()); }
    const Attribute* findAttributeByName(const AtomString& name, bool shouldIgnoreAttributeCase) const;

    Ref<UniqueElementData> makeUniqueCopy() const;
};

DECLARE_ALLOCATOR_WITH_HEAP_IDENTIFIER(ShareableElementData);
class ShareableElementData : public ElementData {
    WTF_DEPRECATED_MAKE_FAST_ALLOCATED_WITH_HEAP_IDENTIFIER(ShareableElementData, ShareableElementData);
public:
    static Ref<ShareableElementData> createWithAttributes(std::span<const Attribute>);

    explicit ShareableElementData(std::span<const Attribute>);
    explicit ShareableElementData(const UniqueElementData&);
    ~ShareableElementData();

    static constexpr ptrdiff_t attributeArrayMemoryOffset() { return OBJECT_OFFSETOF(ShareableElementData, m_attributeArray); }

    std::span<Attribute> NODELETE attributes() { return unsafeMakeSpan(m_attributeArray, arraySize()); }
    std::span<const Attribute> NODELETE attributes() const { return unsafeMakeSpan(m_attributeArray, arraySize()); }

    Attribute m_attributeArray[0];
};

class UniqueElementData : public ElementData {
public:
    static Ref<UniqueElementData> create();
    Ref<ShareableElementData> makeShareableCopy() const;

    // These functions do no error/duplicate checking.
    void addAttribute(const QualifiedName&, const AtomString&);
    void removeAttributeAt(unsigned index);

    Attribute& NODELETE attributeAt(unsigned index);
    Attribute* NODELETE findAttributeByName(const QualifiedName&);

    std::span<const Attribute> NODELETE attributes() const LIFETIME_BOUND { return m_attributeVector.span(); }

    UniqueElementData();
    explicit UniqueElementData(const ShareableElementData&);
    explicit UniqueElementData(const UniqueElementData&);

    static constexpr ptrdiff_t attributeVectorMemoryOffset() { return OBJECT_OFFSETOF(UniqueElementData, m_attributeVector); }

    mutable RefPtr<ImmutableStyleProperties> m_presentationalHintStyle;
    typedef Vector<Attribute, 4> AttributeVector;
    AttributeVector m_attributeVector;
};

inline void ElementData::deref()
{
    if (!derefBase())
        return;
    destroy();
}

inline unsigned ElementData::length() const
{
    if (auto* uniqueData = dynamicDowncast<UniqueElementData>(*this))
        return uniqueData->m_attributeVector.size();
    return arraySize();
}

inline const Attribute* ElementData::attributeBase() const
{
    if (auto* uniqueData = dynamicDowncast<UniqueElementData>(*this))
        return uniqueData->m_attributeVector.span().data();
    return uncheckedDowncast<ShareableElementData>(*this).m_attributeArray;
}

inline const ImmutableStyleProperties* ElementData::presentationalHintStyle() const
{
    if (auto* uniqueData = dynamicDowncast<UniqueElementData>(*this))
        return uniqueData->m_presentationalHintStyle.get();
    return nullptr;
}

inline std::span<const Attribute> ElementData::attributes() const LIFETIME_BOUND
{
    if (isUnique())
        return uncheckedDowncast<UniqueElementData>(*this).attributes();
    return uncheckedDowncast<ShareableElementData>(*this).attributes();
}

ALWAYS_INLINE const Attribute* NODELETE ElementData::findAttributeByName(const AtomString& name, bool shouldIgnoreAttributeCase) const
{
    unsigned index = findAttributeIndexByName(name, shouldIgnoreAttributeCase);
    if (index != attributeNotFound)
        return &attributeAt(index);
    return nullptr;
}

SUPPRESS_NODELETE ALWAYS_INLINE unsigned NODELETE ElementData::findAttributeIndexByName(const QualifiedName& name) const
{
    auto attributes = attributeSpan();
    for (auto [i, attribute] : indexedRange(attributes)) {
        if (attribute.name().matches(name))
            return i;
    }
    return attributeNotFound;
}

// We use a boolean parameter instead of calling shouldIgnoreAttributeCase so that the caller
// can tune the behavior (hasAttribute is case sensitive whereas getAttribute is not).
SUPPRESS_NODELETE ALWAYS_INLINE unsigned NODELETE ElementData::findAttributeIndexByName(const AtomString& name, bool shouldIgnoreAttributeCase) const
{
    auto attributes = attributeSpan();
    if (attributes.empty())
        return attributeNotFound;

    auto& caseAdjustedName = shouldIgnoreAttributeCase ? name.convertToASCIILowercase() : name;

    for (auto [i, attribute] : indexedRange(attributes)) {
        if (!attribute.name().hasPrefix()) {
            if (attribute.localName() == caseAdjustedName)
                return i;
        } else {
            if (attribute.name().toString() == caseAdjustedName)
                return i;
        }
    }

    return attributeNotFound;
}

SUPPRESS_NODELETE ALWAYS_INLINE const Attribute* NODELETE ElementData::findAttributeByName(const QualifiedName& name) const
{
    for (auto& attribute : attributeSpan()) {
        if (attribute.name().matches(name))
            return &attribute;
    }
    return nullptr;
}

inline const Attribute& NODELETE ElementData::attributeAt(unsigned index) const
{
    return attributeSpan()[index];
}

inline void UniqueElementData::addAttribute(const QualifiedName& attributeName, const AtomString& value)
{
    m_attributeVector.append(Attribute(attributeName, value));
}

inline void UniqueElementData::removeAttributeAt(unsigned index)
{
    m_attributeVector.removeAt(index);
}

inline Attribute& NODELETE UniqueElementData::attributeAt(unsigned index)
{
    return m_attributeVector.at(index);
}

} // namespace WebCore

SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::ShareableElementData)
    static bool isType(const WebCore::ElementData& elementData) { return !elementData.isUnique(); }
SPECIALIZE_TYPE_TRAITS_END()

SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::UniqueElementData)
    static bool isType(const WebCore::ElementData& elementData) { return elementData.isUnique(); }
SPECIALIZE_TYPE_TRAITS_END()
