/*
 * Copyright 2020 The Android Open Source Project
 *
 * 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.
 */

#include "common/strings.h"

#include <bluetooth/log.h>

#include <algorithm>
#include <charconv>
#include <cstdlib>
#include <functional>
#include <iomanip>
#include <iterator>
#include <sstream>
#include <system_error>

namespace {

struct IsSpace {
  bool operator()(std::string::value_type v) { return isspace(static_cast<int>(v)); }
};

struct IsHexDigit {
  bool operator()(std::string::value_type v) { return isxdigit(static_cast<int>(v)); }
};

}  // namespace

namespace bluetooth {
namespace common {

std::string ToHexString(const std::vector<uint8_t>& value) {
  return ToHexString(value.begin(), value.end());
}

bool IsValidHexString(const std::string& str) {
  return std::find_if_not(str.begin(), str.end(), IsHexDigit{}) == str.end();
}

std::optional<std::vector<uint8_t>> FromHexString(const std::string& str) {
  if (str.size() % 2 != 0) {
    log::info("str size is not divisible by 2, size is {}", str.size());
    return std::nullopt;
  }
  if (std::find_if_not(str.begin(), str.end(), IsHexDigit{}) != str.end()) {
    log::info("value contains none hex digit");
    return std::nullopt;
  }
  std::vector<uint8_t> value;
  value.reserve(str.size() / 2);
  for (size_t i = 0; i < str.size(); i += 2) {
    uint8_t v = 0;
    auto ret = std::from_chars(str.c_str() + i, str.c_str() + i + 2, v, 16);
    if (std::make_error_code(ret.ec)) {
      log::info("failed to parse hex char at index {}", i);
      return std::nullopt;
    }
    value.push_back(v);
  }
  return value;
}

std::string StringTrim(std::string str) {
  str.erase(str.begin(), std::find_if_not(str.begin(), str.end(), IsSpace{}));
  str.erase(std::find_if_not(str.rbegin(), str.rend(), IsSpace{}).base(), str.end());
  return str;
}

std::vector<std::string> StringSplit(const std::string& str, const std::string& delim,
                                     size_t max_token) {
  log::assert_that(!delim.empty(), "delim cannot be empty");
  std::vector<std::string> tokens;
  // Use std::string::find and std::string::substr to avoid copying str into a stringstream
  std::string::size_type starting_index = 0;
  auto index_of_delim = str.find(delim);
  while ((max_token == 0 || tokens.size() < (max_token - 1)) &&
         index_of_delim != std::string::npos) {
    tokens.push_back(str.substr(starting_index, index_of_delim - starting_index));
    starting_index = index_of_delim + delim.size();
    index_of_delim = str.find(delim, starting_index);
  }
  // Append last item to the vector if there are anything left
  if (starting_index < (str.size() + 1)) {
    tokens.push_back(str.substr(starting_index));
  }
  return tokens;
}

std::string StringJoin(const std::vector<std::string>& strings, const std::string& delim) {
  std::stringstream ss;
  for (auto it = strings.begin(); it != strings.end(); it++) {
    ss << *it;
    if (std::next(it) != strings.end()) {
      ss << delim;
    }
  }
  return ss.str();
}

std::optional<int64_t> Int64FromString(const std::string& str) {
  char* ptr = nullptr;
  errno = 0;
  int64_t value = std::strtoll(str.c_str(), &ptr, 10);
  if (errno != 0) {
    log::info("cannot parse string '{}' with error '{}'", str, strerror(errno));
    return std::nullopt;
  }
  if (ptr == str.c_str()) {
    log::info("string '{}' is empty or has wrong format", str);
    return std::nullopt;
  }
  if (ptr != (str.c_str() + str.size())) {
    log::info("cannot parse whole string '{}'", str);
    return std::nullopt;
  }
  return value;
}

std::string ToString(int64_t value) { return std::to_string(value); }

std::optional<uint64_t> Uint64FromString(const std::string& str) {
  if (str.find('-') != std::string::npos) {
    log::info("string '{}' contains minus sign, this function is for unsigned", str);
    return std::nullopt;
  }
  char* ptr = nullptr;
  errno = 0;
  uint64_t value = std::strtoull(str.c_str(), &ptr, 10);
  if (errno != 0) {
    log::info("cannot parse string '{}' with error '{}'", str, strerror(errno));
    return std::nullopt;
  }
  if (ptr == str.c_str()) {
    log::info("string '{}' is empty or has wrong format", str);
    return std::nullopt;
  }
  if (ptr != (str.c_str() + str.size())) {
    log::info("cannot parse whole string '{}'", str);
    return std::nullopt;
  }
  return value;
}

std::string ToString(uint64_t value) { return std::to_string(value); }

std::optional<bool> BoolFromString(const std::string& str) {
  if (str == "true") {
    return true;
  } else if (str == "false") {
    return false;
  } else {
    log::info("string '{}' is neither true nor false", str);
    return std::nullopt;
  }
}

std::string ToString(bool value) { return value ? "true" : "false"; }

}  // namespace common
}  // namespace bluetooth
