// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

//===-- StringExtras.cpp - Implement the StringExtras header --------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements the StringExtras.h header
//
//===----------------------------------------------------------------------===//

#include "wpi/StringExtras.h"

#include <algorithm>
#include <cstdlib>
#include <string_view>

#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"

// strncasecmp() is not available on non-POSIX systems, so define an
// alternative function here.
static int ascii_strncasecmp(const char* lhs, const char* rhs,
                             size_t length) noexcept {
  for (size_t i = 0; i < length; ++i) {
    unsigned char lhc = wpi::toLower(lhs[i]);
    unsigned char rhc = wpi::toLower(rhs[i]);
    if (lhc != rhc) {
      return lhc < rhc ? -1 : 1;
    }
  }
  return 0;
}

int wpi::compare_lower(std::string_view lhs, std::string_view rhs) noexcept {
  if (int Res = ascii_strncasecmp(lhs.data(), rhs.data(),
                                  (std::min)(lhs.size(), rhs.size()))) {
    return Res;
  }
  if (lhs.size() == rhs.size()) {
    return 0;
  }
  return lhs.size() < rhs.size() ? -1 : 1;
}

std::string_view::size_type wpi::find_lower(
    std::string_view str, char ch, std::string_view::size_type from) noexcept {
  char lch = toLower(ch);
  auto s = drop_front(str, from);
  while (!s.empty()) {
    if (toLower(s.front()) == lch) {
      return str.size() - s.size();
    }
    s.remove_prefix(1);
  }
  return std::string_view::npos;
}

std::string_view::size_type wpi::find_lower(
    std::string_view str, std::string_view other,
    std::string_view::size_type from) noexcept {
  auto s = substr(str, from);
  while (s.size() >= other.size()) {
    if (starts_with_lower(s, other)) {
      return from;
    }
    s.remove_prefix(1);
    ++from;
  }
  return std::string_view::npos;
}

std::string_view::size_type wpi::rfind_lower(
    std::string_view str, char ch, std::string_view::size_type from) noexcept {
  from = (std::min)(from, str.size());
  auto data = str.data();
  std::string_view::size_type i = from;
  while (i != 0) {
    --i;
    if (toLower(data[i]) == toLower(ch)) {
      return i;
    }
  }
  return std::string_view::npos;
}

std::string_view::size_type wpi::rfind_lower(std::string_view str,
                                             std::string_view other) noexcept {
  std::string_view::size_type n = other.size();
  if (n > str.size()) {
    return std::string_view::npos;
  }
  for (size_t i = str.size() - n + 1, e = 0; i != e;) {
    --i;
    if (equals_lower(substr(str, i, n), other)) {
      return i;
    }
  }
  return std::string_view::npos;
}

bool wpi::starts_with_lower(std::string_view str,
                            std::string_view prefix) noexcept {
  return str.size() >= prefix.size() &&
         ascii_strncasecmp(str.data(), prefix.data(), prefix.size()) == 0;
}

bool wpi::ends_with_lower(std::string_view str,
                          std::string_view suffix) noexcept {
  return str.size() >= suffix.size() &&
         ascii_strncasecmp(str.data() + str.size() - suffix.size(),
                           suffix.data(), suffix.size()) == 0;
}

void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
                std::string_view separator, int maxSplit,
                bool keepEmpty) noexcept {
  std::string_view s = str;

  // Count down from maxSplit. When maxSplit is -1, this will just split
  // "forever". This doesn't support splitting more than 2^31 times
  // intentionally; if we ever want that we can make maxSplit a 64-bit integer
  // but that seems unlikely to be useful.
  while (maxSplit-- != 0) {
    auto idx = s.find(separator);
    if (idx == std::string_view::npos) {
      break;
    }

    // Push this split.
    if (keepEmpty || idx > 0) {
      arr.push_back(slice(s, 0, idx));
    }

    // Jump forward.
    s = slice(s, idx + separator.size(), std::string_view::npos);
  }

  // Push the tail.
  if (keepEmpty || !s.empty()) {
    arr.push_back(s);
  }
}

void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
                char separator, int maxSplit, bool keepEmpty) noexcept {
  std::string_view s = str;

  // Count down from maxSplit. When maxSplit is -1, this will just split
  // "forever". This doesn't support splitting more than 2^31 times
  // intentionally; if we ever want that we can make maxSplit a 64-bit integer
  // but that seems unlikely to be useful.
  while (maxSplit-- != 0) {
    size_t idx = s.find(separator);
    if (idx == std::string_view::npos) {
      break;
    }

    // Push this split.
    if (keepEmpty || idx > 0) {
      arr.push_back(slice(s, 0, idx));
    }

    // Jump forward.
    s = slice(s, idx + 1, std::string_view::npos);
  }

  // Push the tail.
  if (keepEmpty || !s.empty()) {
    arr.push_back(s);
  }
}

static unsigned GetAutoSenseRadix(std::string_view& str) noexcept {
  if (str.empty()) {
    return 10;
  }

  if (wpi::starts_with(str, "0x") || wpi::starts_with(str, "0X")) {
    str.remove_prefix(2);
    return 16;
  }

  if (wpi::starts_with(str, "0b") || wpi::starts_with(str, "0B")) {
    str.remove_prefix(2);
    return 2;
  }

  if (wpi::starts_with(str, "0o")) {
    str.remove_prefix(2);
    return 8;
  }

  if (str[0] == '0' && str.size() > 1 && wpi::isDigit(str[1])) {
    str.remove_prefix(1);
    return 8;
  }

  return 10;
}

bool wpi::detail::ConsumeUnsignedInteger(
    std::string_view& str, unsigned radix,
    unsigned long long& result) noexcept {  // NOLINT(runtime/int)
  // Autosense radix if not specified.
  if (radix == 0) {
    radix = GetAutoSenseRadix(str);
  }

  // Empty strings (after the radix autosense) are invalid.
  if (str.empty()) {
    return true;
  }

  // Parse all the bytes of the string given this radix.  Watch for overflow.
  std::string_view str2 = str;
  result = 0;
  while (!str2.empty()) {
    unsigned charVal;
    if (str2[0] >= '0' && str2[0] <= '9') {
      charVal = str2[0] - '0';
    } else if (str2[0] >= 'a' && str2[0] <= 'z') {
      charVal = str2[0] - 'a' + 10;
    } else if (str2[0] >= 'A' && str2[0] <= 'Z') {
      charVal = str2[0] - 'A' + 10;
    } else {
      break;
    }

    // If the parsed value is larger than the integer radix, we cannot
    // consume any more characters.
    if (charVal >= radix) {
      break;
    }

    // Add in this character.
    unsigned long long prevResult = result;  // NOLINT(runtime/int)
    result = result * radix + charVal;

    // Check for overflow by shifting back and seeing if bits were lost.
    if (result / radix < prevResult) {
      return true;
    }

    str2.remove_prefix(1);
  }

  // We consider the operation a failure if no characters were consumed
  // successfully.
  if (str.size() == str2.size()) {
    return true;
  }

  str = str2;
  return false;
}

bool wpi::detail::ConsumeSignedInteger(
    std::string_view& str, unsigned radix,
    long long& result) noexcept {  // NOLINT(runtime/int)
  unsigned long long ullVal;       // NOLINT(runtime/int)

  // Handle positive strings first.
  if (str.empty() || str.front() != '-') {
    if (wpi::detail::ConsumeUnsignedInteger(str, radix, ullVal) ||
        // Check for value so large it overflows a signed value.
        static_cast<long long>(ullVal) < 0) {  // NOLINT(runtime/int)
      return true;
    }
    result = ullVal;
    return false;
  }

  // Get the positive part of the value.
  std::string_view str2 = wpi::drop_front(str, 1);
  if (wpi::detail::ConsumeUnsignedInteger(str2, radix, ullVal) ||
      // Reject values so large they'd overflow as negative signed, but allow
      // "-0".  This negates the unsigned so that the negative isn't undefined
      // on signed overflow.
      static_cast<long long>(-ullVal) > 0) {  // NOLINT(runtime/int)
    return true;
  }

  str = str2;
  result = -ullVal;
  return false;
}

bool wpi::detail::GetAsUnsignedInteger(
    std::string_view str, unsigned radix,
    unsigned long long& result) noexcept {  // NOLINT(runtime/int)
  if (wpi::detail::ConsumeUnsignedInteger(str, radix, result)) {
    return true;
  }

  // For getAsUnsignedInteger, we require the whole string to be consumed or
  // else we consider it a failure.
  return !str.empty();
}

bool wpi::detail::GetAsSignedInteger(
    std::string_view str, unsigned radix,
    long long& result) noexcept {  // NOLINT(runtime/int)
  if (wpi::detail::ConsumeSignedInteger(str, radix, result)) {
    return true;
  }

  // For getAsSignedInteger, we require the whole string to be consumed or else
  // we consider it a failure.
  return !str.empty();
}

template <>
std::optional<float> wpi::parse_float<float>(std::string_view str) noexcept {
  if (str.empty()) {
    return std::nullopt;
  }
  wpi::SmallString<32> storage{str};
  char* end;
  float val = std::strtof(storage.c_str(), &end);
  if (*end != '\0') {
    return std::nullopt;
  }
  return val;
}

template <>
std::optional<double> wpi::parse_float<double>(std::string_view str) noexcept {
  if (str.empty()) {
    return std::nullopt;
  }
  wpi::SmallString<32> storage{str};
  char* end;
  double val = std::strtod(storage.c_str(), &end);
  if (*end != '\0') {
    return std::nullopt;
  }
  return val;
}

template <>
std::optional<long double> wpi::parse_float<long double>(
    std::string_view str) noexcept {
  if (str.empty()) {
    return std::nullopt;
  }
  wpi::SmallString<32> storage{str};
  char* end;
  long double val = std::strtold(storage.c_str(), &end);
  if (*end != '\0') {
    return std::nullopt;
  }
  return val;
}
