//
// Copyright (c) 2018 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// ImmutableString.h: Wrapper for static or pool allocated char arrays, that are guaranteed to be
// valid and unchanged for the duration of the compilation.
//

#ifndef COMPILER_TRANSLATOR_IMMUTABLESTRING_H_
#define COMPILER_TRANSLATOR_IMMUTABLESTRING_H_

#include <string>

#include "common/string_utils.h"
#include "compiler/translator/Common.h"

namespace sh
{

namespace
{
constexpr size_t constStrlen(const char *str)
{
    if (str == nullptr)
    {
        return 0u;
    }
    size_t len = 0u;
    while (*(str + len) != '\0')
    {
        ++len;
    }
    return len;
}
}

class ImmutableString
{
  public:
    // The data pointer passed in must be one of:
    //  1. nullptr (only valid with length 0).
    //  2. a null-terminated static char array like a string literal.
    //  3. a null-terminated pool allocated char array. This can't be c_str() of a local TString,
    //     since when a TString goes out of scope it clears its first character.
    explicit constexpr ImmutableString(const char *data) : mData(data), mLength(constStrlen(data))
    {
    }

    constexpr ImmutableString(const char *data, size_t length) : mData(data), mLength(length) {}

    ImmutableString(const std::string &str)
        : mData(AllocatePoolCharArray(str.c_str(), str.size())), mLength(str.size())
    {
    }

    ImmutableString(const ImmutableString &) = default;
    ImmutableString &operator=(const ImmutableString &) = default;

    constexpr const char *data() const { return mData ? mData : ""; }
    constexpr size_t length() const { return mLength; }

    char operator[](size_t index) const { return data()[index]; }

    constexpr bool empty() const { return mLength == 0; }
    bool beginsWith(const char *prefix) const { return angle::BeginsWith(data(), prefix); }
    constexpr bool beginsWith(const ImmutableString &prefix) const
    {
        return mLength >= prefix.length() && memcmp(data(), prefix.data(), prefix.length()) == 0;
    }
    bool contains(const char *substr) const { return strstr(data(), substr) != nullptr; }

    constexpr bool operator==(const ImmutableString &b) const
    {
        if (mLength != b.mLength)
        {
            return false;
        }
        return memcmp(data(), b.data(), mLength) == 0;
    }
    constexpr bool operator!=(const ImmutableString &b) const { return !(*this == b); }
    constexpr bool operator==(const char *b) const
    {
        if (b == nullptr)
        {
            return empty();
        }
        return strcmp(data(), b) == 0;
    }
    constexpr bool operator!=(const char *b) const { return !(*this == b); }
    bool operator==(const std::string &b) const
    {
        return mLength == b.length() && memcmp(data(), b.c_str(), mLength) == 0;
    }
    bool operator!=(const std::string &b) const { return !(*this == b); }

    constexpr bool operator<(const ImmutableString &b) const
    {
        if (mLength < b.mLength)
        {
            return true;
        }
        if (mLength > b.mLength)
        {
            return false;
        }
        return (memcmp(data(), b.data(), mLength) < 0);
    }

    template <size_t hashBytes>
    struct FowlerNollVoHash
    {
        static const size_t kFnvOffsetBasis;
        static const size_t kFnvPrime;

        constexpr size_t operator()(const ImmutableString &a) const
        {
            const char *data = a.data();
            size_t hash      = kFnvOffsetBasis;
            while ((*data) != '\0')
            {
                hash = hash ^ (*data);
                hash = hash * kFnvPrime;
                ++data;
            }
            return hash;
        }
    };

  private:
    const char *mData;
    size_t mLength;
};

}  // namespace sh

std::ostream &operator<<(std::ostream &os, const sh::ImmutableString &str);

#endif  // COMPILER_TRANSLATOR_IMMUTABLESTRING_H_
