/*
 * Copyright (C) 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. AND ITS CONTRIBUTORS ``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 ITS 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.
 */

#ifndef StringView_h
#define StringView_h

#include <limits.h>
#include <unicode/utypes.h>
#include <wtf/Forward.h>
#include <wtf/Optional.h>
#include <wtf/RetainPtr.h>
#include <wtf/Vector.h>
#include <wtf/text/ConversionMode.h>
#include <wtf/text/LChar.h>
#include <wtf/text/StringCommon.h>

namespace WTF {

// StringView is a non-owning reference to a string, similar to the proposed std::string_view.
// Whether the string is 8-bit or 16-bit is encoded in the upper bit of the length member.
// This means that strings longer than 2 Gigabytes can not be represented. If that turns out to be
// a problem we can investigate alternative solutions.

class StringView {
public:
	ALWAYS_INLINE StringView();

	ALWAYS_INLINE StringView(const AtomicString&);
	ALWAYS_INLINE StringView(const String& string);
	ALWAYS_INLINE StringView(const StringImpl& string);
	ALWAYS_INLINE StringView(const StringImpl* string);
	ALWAYS_INLINE StringView(const LChar* characters, unsigned length);
	ALWAYS_INLINE StringView(const UChar* characters, unsigned length);
	ALWAYS_INLINE StringView(const char*);

    ALWAYS_INLINE static StringView empty()
    {
        return StringView(reinterpret_cast<const LChar*>(""), 0);
    }

    const LChar* characters8() const
    {
        ASSERT(is8Bit());
        return static_cast<const LChar*>(m_characters);
    }

    const UChar* characters16() const
    {
        ASSERT(!is8Bit());
        return static_cast<const UChar*>(m_characters);
    }

    void getCharactersWithUpconvert(LChar*) const;
    void getCharactersWithUpconvert(UChar*) const;

    WTF_EXPORT_STRING_API CString utf8(ConversionMode = LenientConversion) const;

    class UpconvertedCharacters {
    public:
        explicit UpconvertedCharacters(const StringView&);
        operator const UChar*() const { return m_characters; }
        const UChar* get() const { return m_characters; }
    private:
        Vector<UChar, 32> m_upconvertedCharacters;
        const UChar* m_characters;
    };
    UpconvertedCharacters upconvertedCharacters() const { return UpconvertedCharacters(*this); }

    ALWAYS_INLINE bool isNull() const { return !m_characters; }
    ALWAYS_INLINE bool isEmpty() const { return !length(); }
    ALWAYS_INLINE unsigned length() const { return m_length; }

    ALWAYS_INLINE explicit operator bool() const { return !isNull(); }

    bool is8Bit() const { return m_is8Bit; }

    StringView substring(unsigned start, unsigned length = std::numeric_limits<unsigned>::max()) const
    {
        if (start >= this->length())
            return empty();
        unsigned maxLength = this->length() - start;

        if (length >= maxLength) {
            if (!start)
                return *this;
            length = maxLength;
        }

        if (is8Bit())
            return StringView(characters8() + start, length);

        return StringView(characters16() + start, length);
    }

    StringView left(unsigned len) const { return substring(0, len); }
    StringView right(unsigned len) const { return substring(length() - len, len); }
    WTF_EXPORT_STRING_API Vector<StringView> split(UChar);

    class CodePoints;
    class CodeUnits;

    CodePoints codePoints() const;
    CodeUnits codeUnits() const;

    bool startsWith(const StringView&) const;
    bool startsWithIgnoringASCIICase(const StringView&) const;

    bool endsWith(const StringView&) const;
    bool endsWithIgnoringASCIICase(const StringView&) const;

    String toString() const;

    int toInt(bool& isValid) const;
    float toFloat(bool& isValid) const;

    String toStringWithoutCopying() const;

    UChar operator[](unsigned index) const
    {
        ASSERT(index < length());
        if (is8Bit())
            return characters8()[index];
        return characters16()[index];
    }

    size_t find(UChar, unsigned start = 0) const;
    size_t find(StringView, unsigned start) const;

    size_t reverseFind(UChar, unsigned index = UINT_MAX) const;

    bool contains(UChar c) const { return find(c) != notFound; }

    String convertToASCIILowercase() const;
    String convertToASCIIUppercase() const;

private:
    friend bool equal(StringView, StringView);

    void initialize(const LChar* characters, unsigned length);
    void initialize(const UChar* characters, unsigned length);

    void clear();

    const void* m_characters;
    unsigned m_length;
    bool m_is8Bit;
};

bool equal(StringView, StringView);
bool equal(StringView, const LChar*);
bool equal(StringView, const char*);

bool equalIgnoringASCIICase(StringView, StringView);
bool equalIgnoringASCIICase(StringView, const char*);

template<unsigned length> bool equalLettersIgnoringASCIICase(StringView, const char (&lowercaseLetters)[length]);

inline bool operator==(StringView a, StringView b) { return equal(a, b); }
inline bool operator==(StringView a, const LChar* b) { return equal(a, b); }
inline bool operator==(StringView a, const char* b) { return equal(a, b); }
inline bool operator==(const LChar* a, StringView b) { return equal(b, a); }
inline bool operator==(const char* a, StringView b) { return equal(b, a); }

inline bool operator!=(StringView a, StringView b) { return !equal(a, b); }
inline bool operator!=(StringView a, const LChar* b) { return !equal(a, b); }
inline bool operator!=(StringView a, const char* b) { return !equal(a, b); }
inline bool operator!=(const LChar* a, StringView b) { return !equal(b, a); }
inline bool operator!=(const char* a, StringView b) { return !equal(b, a); }

}

#include <wtf/text/AtomicString.h>
#include <wtf/text/WTFString.h>

namespace WTF {

ALWAYS_INLINE StringView::StringView()
    : m_characters(0)
    , m_length(0)
	, m_is8Bit(true)
{
}

inline void StringView::initialize(const LChar* characters, unsigned length)
{
    m_characters = characters;
    m_length = length;
    m_is8Bit = true;
}

inline void StringView::initialize(const UChar* characters, unsigned length)
{
    m_characters = characters;
    m_length = length;
    m_is8Bit = false;
}

ALWAYS_INLINE StringView::StringView(const LChar* characters, unsigned length)
{
    initialize(characters, length);
}

ALWAYS_INLINE StringView::StringView(const UChar* characters, unsigned length)
{
    initialize(characters, length);
}

ALWAYS_INLINE StringView::StringView(const char* characters)
{
    initialize(reinterpret_cast<const LChar*>(characters), strlen(characters));
}

ALWAYS_INLINE StringView::StringView(const StringImpl& string)
{
    if (string.is8Bit())
        initialize(string.characters8(), string.length());
    else
        initialize(string.characters16(), string.length());
}

ALWAYS_INLINE StringView::StringView(const StringImpl* string)
{
    if (string->is8Bit())
        initialize(string->characters8(), string->length());
    else
        initialize(string->characters16(), string->length());
}

inline StringView::StringView(const String& string)
{
    if (!string.impl()) {
        clear();
        return;
    }
    if (string.is8Bit()) {
        initialize(string.characters8(), string.length());
        return;
    }
    initialize(string.characters16(), string.length());
}

inline StringView::StringView(const AtomicString& atomicString)
{
	const auto& string = atomicString.string();
    if (!string.impl()) {
        clear();
        return;
    }
    if (string.is8Bit()) {
        initialize(string.characters8(), string.length());
        return;
    }
    initialize(string.characters16(), string.length());
}

class StringView::CodePoints {
public:
    class Iterator {
    public:
        Iterator(StringView&, unsigned index);
        Iterator& operator++();
        UChar32 operator*() const;
        bool operator==(const Iterator&) const;
        bool operator!=(const Iterator&) const;
        Iterator& operator=(const Iterator&);

    private:
        StringView& m_stringView;
        Optional<unsigned> m_nextCodePointOffset;
        UChar32 m_codePoint;
    };

    explicit CodePoints(const StringView&);
    Iterator begin() const;
    Iterator end() const;

private:
    StringView m_stringView;
};

class StringView::CodeUnits {
public:
    class Iterator {
    public:
        Iterator(const StringView&, unsigned index);
        Iterator& operator++();
        UChar operator*() const;
        bool operator==(const Iterator&) const;
        bool operator!=(const Iterator&) const;

    private:
        const StringView& m_stringView;
        unsigned m_index;
    };

    explicit CodeUnits(const StringView&);
    Iterator begin() const;
    Iterator end() const;

private:
    StringView m_stringView;
};

inline void StringView::clear()
{
    m_characters = 0;
    m_length = 0;
    m_is8Bit = true;
}

inline void StringView::getCharactersWithUpconvert(LChar* destination) const
{
    ASSERT(is8Bit());
    StringImpl::copyChars(destination, characters8(), m_length);
}

inline void StringView::getCharactersWithUpconvert(UChar* destination) const
{
    if (is8Bit()) {
        StringImpl::copyChars(destination, characters8(), m_length);
        return;
    }
    StringImpl::copyChars(destination, characters16(), m_length);
}

inline StringView::UpconvertedCharacters::UpconvertedCharacters(const StringView& string)
{
    if (!string.is8Bit()) {
        m_characters = string.characters16();
        return;
    }
    const LChar* characters8 = string.characters8();
    unsigned length = string.m_length;
    m_upconvertedCharacters.reserveInitialCapacity(length);
    for (unsigned i = 0; i < length; ++i)
        m_upconvertedCharacters.uncheckedAppend(characters8[i]);
    m_characters = m_upconvertedCharacters.data();
}

inline String StringView::toString() const
{
    if (is8Bit())
        return String(characters8(), m_length);
    return String(characters16(), m_length);
}

inline float StringView::toFloat(bool& isValid) const
{
    if (is8Bit())
        return charactersToFloat(characters8(), m_length, &isValid);
    return charactersToFloat(characters16(), m_length, &isValid);
}

inline int StringView::toInt(bool& isValid) const
{
    if (is8Bit())
        return charactersToInt(characters8(), m_length, &isValid);
    return charactersToInt(characters16(), m_length, &isValid);
}

inline String StringView::toStringWithoutCopying() const
{
    if (is8Bit())
        return StringImpl::createWithoutCopying(characters8(), m_length);
    return StringImpl::createWithoutCopying(characters16(), m_length);
}

inline size_t StringView::find(UChar character, unsigned start) const
{
    if (is8Bit())
        return WTF::find(characters8(), m_length, character, start);
    return WTF::find(characters16(), m_length, character, start);
}

inline size_t StringView::reverseFind(UChar character, unsigned index) const
{
    if (is8Bit())
        return WTF::reverseFind(characters8(), m_length, character, index);
    return WTF::reverseFind(characters16(), m_length, character, index);
}

template<typename StringType> class StringTypeAdapter;

template<> class StringTypeAdapter<StringView> {
public:
	ALWAYS_INLINE StringTypeAdapter<StringView>(StringView string)
        : m_string(string)
    {
    }

    ALWAYS_INLINE unsigned length() { return m_string.length(); }
    ALWAYS_INLINE bool is8Bit() { return m_string.is8Bit(); }
    ALWAYS_INLINE void writeTo(LChar* destination) { m_string.getCharactersWithUpconvert(destination); }
    ALWAYS_INLINE void writeTo(UChar* destination) { m_string.getCharactersWithUpconvert(destination); }

    ALWAYS_INLINE String toString() const { return m_string.toString(); }

private:
    StringView m_string;
};

template<typename CharacterType, size_t inlineCapacity> void append(Vector<CharacterType, inlineCapacity>& buffer, StringView string)
{
    unsigned oldSize = buffer.size();
    buffer.grow(oldSize + string.length());
    string.getCharactersWithUpconvert(buffer.data() + oldSize);
}

inline bool equal(StringView a, StringView b)
{
    if (a.m_characters == b.m_characters) {
        ASSERT(a.is8Bit() == b.is8Bit());
        return a.length() == b.length();
    }

    return equalCommon(a, b);
}
inline bool equal(StringView a, const LChar* b)
{
    if (!b)
        return !a.isEmpty();
    if (a.isEmpty())
        return !b;
    unsigned aLength = a.length();
    if (aLength != strlen(reinterpret_cast<const char*>(b)))
        return false;

    if (a.is8Bit())
        return equal(a.characters8(), b, aLength);
    return equal(a.characters16(), b, aLength);
}

inline bool equal(StringView a, const char* b)
{
    return equal(a, reinterpret_cast<const LChar*>(b));
}

inline bool equalIgnoringASCIICase(StringView a, StringView b)
{
    return equalIgnoringASCIICaseCommon(a, b);
}

inline bool equalIgnoringASCIICase(StringView a, const char* b)
{
    if (!b)
        return !a.isEmpty();
    if (a.isEmpty())
        return !b;
    unsigned aLength = a.length();
    if (a.is8Bit())
        return equalIgnoringASCIICase(a.characters8(), b, aLength);
    return equalIgnoringASCIICase(a.characters16(), b, aLength);
}

inline StringView::CodePoints StringView::codePoints() const
{
    return CodePoints(*this);
}

inline StringView::CodeUnits StringView::codeUnits() const
{
    return CodeUnits(*this);
}

inline StringView::CodePoints::CodePoints(const StringView& stringView)
    : m_stringView(stringView)
{
}

inline StringView::CodePoints::Iterator::Iterator(StringView& stringView, unsigned index)
    : m_stringView(stringView)
	, m_nextCodePointOffset(index)
{
	operator++();
}

inline StringView::CodePoints::Iterator& StringView::CodePoints::Iterator::operator++()
{
    ASSERT(m_nextCodePointOffset);
    if (m_nextCodePointOffset.value() == m_stringView.length()) {
        m_nextCodePointOffset = Nullopt;
        return *this;
    }
    if (m_stringView.is8Bit())
        m_codePoint = m_stringView.characters8()[m_nextCodePointOffset.value()++];
    else
        U16_NEXT(m_stringView.characters16(), m_nextCodePointOffset.value(), m_stringView.length(), m_codePoint);
    ASSERT(m_nextCodePointOffset.value() <= m_stringView.length());
    return *this;
}

inline StringView::CodePoints::Iterator& StringView::CodePoints::Iterator::operator=(const Iterator& other)
{
    m_stringView = other.m_stringView;
    m_nextCodePointOffset = other.m_nextCodePointOffset;
    m_codePoint = other.m_codePoint;
    return *this;
}

inline UChar32 StringView::CodePoints::Iterator::operator*() const
{
    ASSERT(m_nextCodePointOffset);
    return m_codePoint;
}

inline bool StringView::CodePoints::Iterator::operator==(const Iterator& other) const
{
    ASSERT(&m_stringView == &other.m_stringView);
    return m_nextCodePointOffset == other.m_nextCodePointOffset;
}

inline bool StringView::CodePoints::Iterator::operator!=(const Iterator& other) const
{
    return !(*this == other);
}

inline StringView::CodePoints::Iterator StringView::CodePoints::begin() const
{
    return Iterator(const_cast<StringView&>(m_stringView), 0);
}

inline StringView::CodePoints::Iterator StringView::CodePoints::end() const
{
    return Iterator(const_cast<StringView&>(m_stringView), m_stringView.length());
}

inline StringView::CodeUnits::CodeUnits(const StringView& stringView)
    : m_stringView(stringView)
{
}

inline StringView::CodeUnits::Iterator::Iterator(const StringView& stringView, unsigned index)
    : m_stringView(stringView)
    , m_index(index)
{
}

inline StringView::CodeUnits::Iterator& StringView::CodeUnits::Iterator::operator++()
{
    ++m_index;
    return *this;
}

inline UChar StringView::CodeUnits::Iterator::operator*() const
{
    return m_stringView[m_index];
}

inline bool StringView::CodeUnits::Iterator::operator==(const Iterator& other) const
{
    ASSERT(&m_stringView == &other.m_stringView);
    return m_index == other.m_index;
}

inline bool StringView::CodeUnits::Iterator::operator!=(const Iterator& other) const
{
    return !(*this == other);
}

inline StringView::CodeUnits::Iterator StringView::CodeUnits::begin() const
{
    return Iterator(m_stringView, 0);
}

inline StringView::CodeUnits::Iterator StringView::CodeUnits::end() const
{
    return Iterator(m_stringView, m_stringView.length());
}

template<unsigned length> inline bool equalLettersIgnoringASCIICase(StringView string, const char (&lowercaseLetters)[length])
{
    return equalLettersIgnoringASCIICaseCommon(string, lowercaseLetters);
}

} // namespace WTF

using WTF::append;
using WTF::StringView;

#endif // StringView_h
