/*
 * Copyright 2022 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Interned String type, 100% interned on creation. Comparisons are always just
// a pointer comparison

#ifndef wasm_support_istring_h
#define wasm_support_istring_h

#include <set>
#include <string_view>
#include <unordered_set>

#include <assert.h>

#include "threads.h"
#include "utilities.h"

namespace wasm {

struct IString {
private:
  static const char* interned(std::string_view s);

public:
  // Strings are stored in Pascal style: a size followed by the characters. We
  // keep the internal pointer pointing to the data, so that data() is a no-op;
  // computing the size, which is more rare, requires looking back and doing a
  // load.
  //
  // The size is limited to 4 bytes, so the maximum string we support is 4GB.
  //
  // The alternative approach of using a string_view here, i.e., keeping the
  // pointer and size in the IString, uses more more memory. That is, this
  // optimization saves a lot of space, because while it adds 4 bytes to each
  // interned string itself, we tend to have many views on each.
  //
  // We provide a View here, which is a simple interface. Users that need more
  // convert to a std::string_view with .view() or a cast.
  struct View {
    const char* internal = nullptr;
    const char* data() const { return internal; }
    size_t size() const {
      return internal ? *(const uint32_t*)(internal - 4) : 0;
    }
    char operator[](size_t x) const { return internal[x]; }
    std::string_view view() const {
      if (!internal) {
        // No size to read.
        return {};
      }
      return {internal, size()};
    }
  };
  const View str;

  std::string_view view() const { return str.view(); }

  // Use an explicit constructor instead of `= default` because some older
  // compilers (e.g. Apple Clang in older Xcode versions) delete the default
  // constructor if there is a const member without an in-class initializer,
  // even if that member's type has a default constructor.
  // FIXME: Use `= default` once we bump the min clang/Xcode version that
  // we support.
  IString() : str({nullptr}) {}

  IString(View v) : str(v) {}

  IString(std::string_view s) : str{interned(s)} {}
  IString(const char* str) : str{interned(str)} {}
  IString(const std::string& str) : str{interned(str)} {}

  IString(const IString& other) = default;

  IString& operator=(const IString& other) {
    return *(new (this) IString(other));
  }

  bool operator==(const IString& other) const {
    // Fast! No need to compare contents due to interning
    return str.internal == other.str.internal;
  }
  bool operator!=(const IString& other) const { return !(*this == other); }
  bool operator<(const IString& other) const {
    if (str.internal == other.str.internal) {
      return false;
    }
    return view() < other.view();
  }
  bool operator<=(const IString& other) const {
    return *this == other || *this < other;
  }
  bool operator>(const IString& other) const { return !(*this <= other); }
  bool operator>=(const IString& other) const { return !(*this < other); }

  char operator[](int x) const { return str[x]; }

  explicit operator bool() const { return str.internal != nullptr; }

  // TODO: deprecate?
  bool is() const { return bool(*this); }
  bool isNull() const { return !bool(*this); }

  std::string toString() const { return {str.data(), str.size()}; }

  bool equals(std::string_view other) const { return str.view() == other; }

  bool startsWith(std::string_view prefix) const {
    return view().starts_with(prefix);
  }
  bool startsWith(IString other) const { return startsWith(other.view()); }

  // Disambiguate for string literals.
  template<int N> bool startsWith(const char (&str)[N]) const {
    return startsWith(std::string_view(str));
  }

  bool endsWith(std::string_view suffix) const {
    return view().ends_with(suffix);
  }
  bool endsWith(IString other) const { return endsWith(other.view()); }

  // Disambiguate for string literals.
  template<int N> bool endsWith(const char (&str)[N]) const {
    return endsWith(std::string_view(str));
  }

  IString substr(size_t pos, size_t len = std::string_view::npos) const {
    return IString(view().substr(pos, len));
  }

  size_t size() const { return str.size(); }
};

} // namespace wasm

namespace std {

template<> struct hash<wasm::IString> {
  size_t operator()(const wasm::IString& str) const {
    return std::hash<size_t>{}(uintptr_t(str.str.data()));
  }
};

inline std::ostream& operator<<(std::ostream& os, const wasm::IString& str) {
  return os << str.view();
}

} // namespace std

#endif // wasm_support_istring_h
