/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* String utility functions used by the module loader. */

#ifndef shell_StringUtils_h
#define shell_StringUtils_h

#include "js/StableStringChars.h"
#include "js/String.h"

namespace js {
namespace shell {

inline char16_t CharAt(JSLinearString* str, size_t index) {
  return str->latin1OrTwoByteChar(index);
}

inline JSLinearString* SubString(JSContext* cx, JSLinearString* str,
                                 size_t start, size_t end) {
  MOZ_ASSERT(start <= str->length());
  MOZ_ASSERT(end >= start && end <= str->length());
  return NewDependentString(cx, str, start, end - start);
}

inline JSLinearString* SubString(JSContext* cx, JSLinearString* str,
                                 size_t start) {
  return SubString(cx, str, start, str->length());
}

template <size_t NullTerminatedLength>
bool StringStartsWith(JSLinearString* str,
                      const char16_t (&chars)[NullTerminatedLength]) {
  MOZ_ASSERT(NullTerminatedLength > 0);
  const size_t length = NullTerminatedLength - 1;
  MOZ_ASSERT(chars[length] == '\0');

  if (str->length() < length) {
    return false;
  }

  for (size_t i = 0; i < length; i++) {
    if (CharAt(str, i) != chars[i]) {
      return false;
    }
  }

  return true;
}

template <size_t NullTerminatedLength>
bool StringEndsWith(JSLinearString* str,
                    const char16_t (&chars)[NullTerminatedLength]) {
  MOZ_ASSERT(NullTerminatedLength > 0);
  const size_t length = NullTerminatedLength - 1;
  MOZ_ASSERT(chars[length] == '\0');

  if (str->length() < length) {
    return false;
  }

  size_t offset = str->length() - length;
  for (size_t i = 0; i < length; i++) {
    if (CharAt(str, offset + i) != chars[i]) {
      return false;
    }
  }

  return true;
}

template <size_t NullTerminatedLength>
bool StringEquals(JSLinearString* str,
                  const char16_t (&chars)[NullTerminatedLength]) {
  MOZ_ASSERT(NullTerminatedLength > 0);
  const size_t length = NullTerminatedLength - 1;
  MOZ_ASSERT(chars[length] == '\0');

  return str->length() == length && StringStartsWith(str, chars);
}

inline int32_t IndexOf(Handle<JSLinearString*> str, char16_t target,
                       size_t start = 0) {
  int32_t length = str->length();
  for (int32_t i = start; i < length; i++) {
    if (CharAt(str, i) == target) {
      return i;
    }
  }

  return -1;
}

inline int32_t LastIndexOf(Handle<JSLinearString*> str, char16_t target) {
  int32_t length = str->length();
  for (int32_t i = length - 1; i >= 0; i--) {
    if (CharAt(str, i) == target) {
      return i;
    }
  }

  return -1;
}

inline JSLinearString* ReplaceCharGlobally(JSContext* cx,
                                           Handle<JSLinearString*> str,
                                           char16_t target,
                                           char16_t replacement) {
  int32_t i = IndexOf(str, target);
  if (i == -1) {
    return str;
  }

  JS::AutoStableStringChars chars(cx);
  if (!chars.initTwoByte(cx, str)) {
    return nullptr;
  }

  Vector<char16_t> buf(cx);
  if (!buf.append(chars.twoByteChars(), str->length())) {
    return nullptr;
  }

  for (; i < int32_t(buf.length()); i++) {
    if (buf[i] == target) {
      buf[i] = replacement;
    }
  }

  RootedString result(cx, JS_NewUCStringCopyN(cx, buf.begin(), buf.length()));
  if (!result) {
    return nullptr;
  }

  return JS_EnsureLinearString(cx, result);
}

inline JSString* JoinStrings(JSContext* cx,
                             Handle<GCVector<JSLinearString*>> strings,
                             Handle<JSLinearString*> separator) {
  RootedString result(cx, JS_GetEmptyString(cx));

  for (size_t i = 0; i < strings.length(); i++) {
    HandleString str = strings[i];
    if (i != 0) {
      result = JS_ConcatStrings(cx, result, separator);
      if (!result) {
        return nullptr;
      }
    }

    result = JS_ConcatStrings(cx, result, str);
    if (!result) {
      return nullptr;
    }
  }

  return result;
}

}  // namespace shell
}  // namespace js

#endif  // shell_StringUtils_h
