/**
 * Copyright (c) 2016-present, Facebook, Inc.
 *
 * 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.
 */
// Portions Copyright (c) Microsoft Corporation

#pragma once

#include <locale>
#include <sstream>
#include <type_traits>

namespace onnxruntime {

namespace detail {

inline void MakeStringImpl(std::ostringstream& /*ss*/) noexcept {
}

template <typename T>
inline void MakeStringImpl(std::ostringstream& ss, const T& t) noexcept {
  ss << t;
}

template <typename T, typename... Args>
inline void MakeStringImpl(std::ostringstream& ss, const T& t, const Args&... args) noexcept {
  MakeStringImpl(ss, t);
  MakeStringImpl(ss, args...);
}

// see MakeString comments for explanation of why this is necessary
template <typename... Args>
inline std::string MakeStringImpl(const Args&... args) noexcept {
  std::ostringstream ss;
  MakeStringImpl(ss, args...);
  return ss.str();
}

//
// Infrastructure to convert char[n] to char* to reduce binary size
//

// default is to leave the type as is
template <class T>
struct if_char_array_make_ptr {
  using type = T;
};

// specialization that matches an array reference, which is what the char array from a string literal
// used in a call to MakeString will be.
// if the type is a char[n] array we 'decay' it to a char* so that the usages can be folded.
template <class T, size_t N>
struct if_char_array_make_ptr<T (&)[N]> {
  // remove a single extent (T[x] -> T, but T[x][y] -> T[y]) so we only match char[x],
  // and get the type name without the 'const' so both 'const char (&)[n]' and 'char (&)[n]' are matched.
  using element_type = typename std::remove_const<typename std::remove_extent<T>::type>::type;
  using type = typename std::conditional<std::is_same<char, element_type>::value, T*, T (&)[N]>::type;
};

// helper to make usage simpler in MakeString
template <class T>
using if_char_array_make_ptr_t = typename if_char_array_make_ptr<T>::type;
}  // namespace detail

/**
 * Makes a string by concatenating string representations of the arguments.
 * This version uses the current locale.
 */
template <typename... Args>
std::string MakeString(const Args&... args) {
  // We need to update the types from the MakeString template instantiation to decay any char[n] to char*.
  //   e.g. MakeString("in", "out") goes from MakeString<char[2], char[3]> to MakeStringImpl<char*, char*>
  //        so that MakeString("out", "in") will also match MakeStringImpl<char*, char*> instead of requiring
  //        MakeStringImpl<char[3], char[2]>.
  //
  // We have to do the type processing before any actual work, so this function purely implements the type processing.
  // If we do not do it this way we do not get the full binary size reduction.
  //
  // See https://stackoverflow.com/a/29418212/684911 for overall details of the approach, but note it does not cover
  // the need to do the type processing as a separate step.

  return detail::MakeStringImpl(detail::if_char_array_make_ptr_t<Args const&>(args)...);
}

/**
 * Makes a string by concatenating string representations of the arguments.
 * This version uses std::locale::classic().
 */
template <typename... Args>
std::string MakeStringWithClassicLocale(const Args&... args) {
  std::ostringstream ss;
  ss.imbue(std::locale::classic());
  detail::MakeStringImpl(ss, args...);
  return ss.str();
}

// MakeString versions for already-a-string types.

inline std::string MakeString(const std::string& str) {
  return str;
}

inline std::string MakeString(const char* cstr) {
  return cstr;
}

inline std::string MakeStringWithClassicLocale(const std::string& str) {
  return str;
}

inline std::string MakeStringWithClassicLocale(const char* cstr) {
  return cstr;
}

}  // namespace onnxruntime
