/*
 * 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 "Attribute.h"
#include "SpaceSplitString.h"
#include <wtf/RefCounted.h>
#include <wtf/TypeCasts.h>

namespace WebCore {

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

class AttributeConstIterator {
public:
    AttributeConstIterator(const Attribute* array, unsigned offset)
        : m_array(array)
        , m_offset(offset)
    {
    }

    const Attribute& operator*() const { return m_array[m_offset]; }
    const Attribute* operator->() const { return &m_array[m_offset]; }
    AttributeConstIterator& operator++() { ++m_offset; return *this; }

    bool operator==(const AttributeConstIterator& other) const { return m_offset == other.m_offset; }
    bool operator!=(const AttributeConstIterator& other) const { return !(*this == other); }

private:
    const Attribute* m_array;
    unsigned m_offset;
};

class AttributeIteratorAccessor {
public:
    AttributeIteratorAccessor(const Attribute* array, unsigned size)
        : m_array(array)
        , m_size(size)
    {
    }

    AttributeConstIterator begin() const { return AttributeConstIterator(m_array, 0); }
    AttributeConstIterator end() const { return AttributeConstIterator(m_array, m_size); }

    unsigned attributeCount() const { return m_size; }

private:
    const Attribute* m_array;
    unsigned m_size;
};

class ElementData : public RefCounted<ElementData> {
    WTF_MAKE_FAST_ALLOCATED;
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(const SpaceSplitString& classNames) const { m_classNames = classNames; }
    const SpaceSplitString& classNames() const { return m_classNames; }
    static ptrdiff_t classNamesMemoryOffset() { return OBJECT_OFFSETOF(ElementData, m_classNames); }

    const AtomString& idForStyleResolution() const { return m_idForStyleResolution; }
    static 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 StyleProperties* presentationAttributeStyle() const;

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

    AttributeIteratorAccessor attributesIterator() const;
    const Attribute& attributeAt(unsigned index) const;
    const Attribute* findAttributeByName(const QualifiedName&) const;
    unsigned findAttributeIndexByName(const QualifiedName&) const;
    unsigned findAttributeIndexByName(const AtomString& name, bool shouldIgnoreAttributeCase) const;
    const Attribute* findLanguageAttribute() const;

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

    bool isEquivalent(const ElementData* other) const;

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

    static 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_flagPresentationAttributeStyleIsDirty = 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;

    inline void updateFlag(uint32_t flag, bool set) const
    {
        if (set)
            m_arraySizeAndFlags |= flag;
        else
            m_arraySizeAndFlags &= ~flag;
    }
    static inline uint32_t 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 presentationAttributeStyleIsDirty() const { return m_arraySizeAndFlags & s_flagPresentationAttributeStyleIsDirty; }
    void setPresentationAttributeStyleIsDirty(bool isDirty) const { updateFlag(s_flagPresentationAttributeStyleIsDirty, 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;

    void destroy();

    const Attribute* attributeBase() const;
    const Attribute* findAttributeByName(const AtomString& name, bool shouldIgnoreAttributeCase) const;

    Ref<UniqueElementData> makeUniqueCopy() const;
};

#if COMPILER(MSVC)
#pragma warning(push)
#pragma warning(disable: 4200) // Disable "zero-sized array in struct/union" warning
#endif

class ShareableElementData : public ElementData {
public:
    static Ref<ShareableElementData> createWithAttributes(const Vector<Attribute>&);

    explicit ShareableElementData(const Vector<Attribute>&);
    explicit ShareableElementData(const UniqueElementData&);
    ~ShareableElementData();

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

    Attribute m_attributeArray[0];
};

#if COMPILER(MSVC)
#pragma warning(pop)
#endif

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 removeAttribute(unsigned index);

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

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

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

    mutable RefPtr<StyleProperties> m_presentationAttributeStyle;
    typedef Vector<Attribute, 4> AttributeVector;
    AttributeVector m_attributeVector;
};

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

inline unsigned ElementData::length() const
{
    if (is<UniqueElementData>(*this))
        return downcast<UniqueElementData>(*this).m_attributeVector.size();
    return arraySize();
}

inline const Attribute* ElementData::attributeBase() const
{
    if (is<UniqueElementData>(*this))
        return downcast<UniqueElementData>(*this).m_attributeVector.data();
    return downcast<ShareableElementData>(*this).m_attributeArray;
}

inline const StyleProperties* ElementData::presentationAttributeStyle() const
{
    if (!is<UniqueElementData>(*this))
        return nullptr;
    return downcast<UniqueElementData>(*this).m_presentationAttributeStyle.get();
}

inline AttributeIteratorAccessor ElementData::attributesIterator() const
{
    if (is<UniqueElementData>(*this)) {
        const Vector<Attribute, 4>& attributeVector = downcast<UniqueElementData>(*this).m_attributeVector;
        return AttributeIteratorAccessor(attributeVector.data(), attributeVector.size());
    }
    return AttributeIteratorAccessor(downcast<ShareableElementData>(*this).m_attributeArray, arraySize());
}

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

ALWAYS_INLINE unsigned ElementData::findAttributeIndexByName(const QualifiedName& name) const
{
    const Attribute* attributes = attributeBase();
    for (unsigned i = 0, count = length(); i < count; ++i) {
        if (attributes[i].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).
ALWAYS_INLINE unsigned ElementData::findAttributeIndexByName(const AtomString& name, bool shouldIgnoreAttributeCase) const
{
    unsigned attributeCount = length();
    if (!attributeCount)
        return attributeNotFound;

    const Attribute* attributes = attributeBase();
    const AtomString& caseAdjustedName = shouldIgnoreAttributeCase ? name.convertToASCIILowercase() : name;

    unsigned attributeIndex = 0;
    do {
        const Attribute& attribute = attributes[attributeIndex];
        if (!attribute.name().hasPrefix()) {
            if (attribute.localName() == caseAdjustedName)
                return attributeIndex;
        } else {
            if (attribute.name().toString() == caseAdjustedName)
                return attributeIndex;
        }

        ++attributeIndex;
    } while (attributeIndex < attributeCount);

    return attributeNotFound;
}

ALWAYS_INLINE const Attribute* ElementData::findAttributeByName(const QualifiedName& name) const
{
    const Attribute* attributes = attributeBase();
    for (unsigned i = 0, count = length(); i < count; ++i) {
        if (attributes[i].name().matches(name))
            return &attributes[i];
    }
    return 0;
}

inline const Attribute& ElementData::attributeAt(unsigned index) const
{
    RELEASE_ASSERT(index < length());
    return attributeBase()[index];
}

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

inline void UniqueElementData::removeAttribute(unsigned index)
{
    m_attributeVector.remove(index);
}

inline Attribute& 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()
