/*
 * Copyright (c) 2023, Alibaba Group Holding Limited;
 *
 * 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.
 */
#pragma once
#include <array>
#include <cstddef>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

#include "marco.h"

namespace struct_pack::detail {

template <typename T>
constexpr std::string_view get_raw_name() {
#ifdef _MSC_VER
  return __FUNCSIG__;
#else
  return __PRETTY_FUNCTION__;
#endif
}

template <auto T>
constexpr std::string_view get_raw_name() {
#ifdef _MSC_VER
  return __FUNCSIG__;
#else
  return __PRETTY_FUNCTION__;
#endif
}

template <typename T>
inline constexpr std::string_view type_string() {
  constexpr std::string_view sample = get_raw_name<int>();
  constexpr size_t pos = sample.find("int");
  constexpr std::string_view str = get_raw_name<T>();
  constexpr auto next1 = str.rfind(sample[pos + 3]);
#if defined(_MSC_VER)
  constexpr std::size_t npos = str.find_first_of(" ", pos);
  if (npos != std::string_view::npos)
    return str.substr(npos + 1, next1 - npos - 1);
  else
    return str.substr(pos, next1 - pos);
#else
  return str.substr(pos, next1 - pos);
#endif
}

#if __cpp_concepts >= 201907L
constexpr bool is_string_reserve_shrink = requires { std::string{}.reserve(); };
#else
constexpr bool is_string_reserve_shrink = true;
#endif

template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;

[[noreturn]] inline void unreachable() {
  // Uses compiler specific extensions if possible.
  // Even if no extension is used, undefined behavior is still raised by
  // an empty function body and the noreturn attribute.
#ifdef __GNUC__  // GCC, Clang, ICC
  __builtin_unreachable();
#elif defined(_MSC_VER)  // msvc
  __assume(false);
#endif
}

template <typename T>
[[noreturn]] constexpr T declval() {
  unreachable();
}

template <typename T, std::size_t sz>
constexpr void STRUCT_PACK_INLINE compile_time_sort(std::array<T, sz> &array) {
  // FIXME: use faster compile-time sort
  for (std::size_t i = 0; i < array.size(); ++i) {
    for (std::size_t j = i + 1; j < array.size(); ++j) {
      if (array[i] > array[j]) {
        auto tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
      }
    }
  }
  return;
}

template <typename T, std::size_t sz>
constexpr std::size_t STRUCT_PACK_INLINE
calculate_uniqued_size(const std::array<T, sz> &input) {
  std::size_t unique_cnt = sz;
  for (std::size_t i = 1; i < input.size(); ++i) {
    if (input[i] == input[i - 1]) {
      --unique_cnt;
    }
  }
  return unique_cnt;
}

template <typename T, std::size_t sz1, std::size_t sz2>
constexpr void STRUCT_PACK_INLINE compile_time_unique(
    const std::array<T, sz1> &input, std::array<T, sz2> &output) {
  std::size_t j = 0;
  static_assert(sz1 != 0, "not allow empty input!");
  output[0] = input[0];
  for (std::size_t i = 1; i < input.size(); ++i) {
    if (input[i] != input[i - 1]) {
      output[++j] = input[i];
    }
  }
}

#if __cpp_lib_string_resize_and_overwrite >= 202110L
template <typename ch>
inline void resize(std::basic_string<ch> &str, std::size_t sz) {
  str.resize_and_overwrite(sz, [](ch *, std::size_t sz) {
    return sz;
  });
}
#elif (defined(_MSC_VER) && _MSC_VER <= 1920)
// old msvc don't support visit private, discard it.

#else

template <typename Function, Function func_ptr>
class string_thief {
 public:
  friend void string_set_length_hacker(std::string &self, std::size_t sz) {
#if defined(_MSVC_STL_VERSION)
    (self.*func_ptr)._Myval2._Mysize = sz;
#else
#if defined(_LIBCPP_VERSION)
    (self.*func_ptr)(sz);
#else
#if (_GLIBCXX_USE_CXX11_ABI == 0) && defined(__GLIBCXX__)
    (self.*func_ptr)()->_M_set_length_and_sharable(sz);
#else
#if defined(__GLIBCXX__)
    (self.*func_ptr)(sz);
#endif
#endif
#endif
#endif
  }
};

#if defined(__GLIBCXX__)  // libstdc++
#if (_GLIBCXX_USE_CXX11_ABI == 0)
template class string_thief<decltype(&std::string::_M_rep),
                            &std::string::_M_rep>;
#else
template class string_thief<decltype(&std::string::_M_set_length),
                            &std::string::_M_set_length>;
#endif
#elif defined(_LIBCPP_VERSION)
template class string_thief<decltype(&std::string::__set_size),
                            &std::string::__set_size>;
#elif defined(_MSVC_STL_VERSION)
template class string_thief<decltype(&std::string::_Mypair),
                            &std::string::_Mypair>;
#endif

void string_set_length_hacker(std::string &, std::size_t);

#ifndef __clang__
#define __has_feature(X) false
#endif

template <typename ch>
inline void resize(std::basic_string<ch> &raw_str, std::size_t sz) {
  std::string &str = *reinterpret_cast<std::string *>(&raw_str);
#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
  raw_str.resize(sz);
#elif defined(__GLIBCXX__) || defined(_LIBCPP_VERSION) || \
    defined(_MSVC_STL_VERSION)
  if constexpr (is_string_reserve_shrink) {
    if (sz > raw_str.capacity()) {
      str.reserve(sz * sizeof(ch));
    }
  }
  else {
    str.reserve(sz * sizeof(ch));
  }
  string_set_length_hacker(str, sz);
  for (auto i = sz; i < sz + sizeof(ch); ++i) str[i] = '\0';
#else
  raw_str.resize(sz);
#endif
}

#endif

#if (defined(_MSC_VER) && _MSC_VER <= 1920)
#else
void vector_set_length_hacker(std::vector<char> &self, std::size_t sz);

template <typename Function, Function func_ptr>
class vector_thief {
 public:
  friend void vector_set_length_hacker(std::vector<char> &self,
                                       std::size_t sz) {
#if defined(_MSVC_STL_VERSION)
    (self.*func_ptr)._Myval2._Mylast = self.data() + sz;
#else
#if defined(_LIBCPP_VERSION)
#if _LIBCPP_VERSION < 14000
    ((*(std::__vector_base<char, std::allocator<char>> *)(&self)).*func_ptr) =
        self.data() + sz;
#else
    (self.*func_ptr) = self.data() + sz;
#endif
#else
#if defined(__GLIBCXX__)
    ((*(std::_Vector_base<char, std::allocator<char>> *)(&self)).*func_ptr)
        ._M_finish = self.data() + sz;
#endif
#endif
#endif
  }
};

#if defined(__GLIBCXX__)  // libstdc++
template class vector_thief<decltype(&std::vector<char>::_M_impl),
                            &std::vector<char>::_M_impl>;
#elif defined(_LIBCPP_VERSION)
template class vector_thief<decltype(&std::vector<char>::__end_),
                            &std::vector<char>::__end_>;
#elif defined(_MSVC_STL_VERSION)
template class vector_thief<decltype(&std::vector<char>::_Mypair),
                            &std::vector<char>::_Mypair>;
#endif

template <typename ch>
inline void resize(std::vector<ch> &raw_vec, std::size_t sz) {
#if defined(__SANITIZE_ADDRESS__) || __has_feature(address_sanitizer)
  raw_vec.resize(sz);
#elif defined(__GLIBCXX__) ||                                     \
    (defined(_LIBCPP_VERSION) && defined(_LIBCPP_HAS_NO_ASAN)) || \
    defined(_MSVC_STL_VERSION)
  std::vector<char> &vec = *reinterpret_cast<std::vector<char> *>(&raw_vec);
  vec.reserve(sz * sizeof(ch));
  vector_set_length_hacker(vec, sz * sizeof(ch));
#else
  raw_vec.resize(sz);
#endif
}
#endif

#ifndef __clang__
#undef __has_feature
#endif

template <typename T>
inline void resize(T &str, std::size_t sz) {
  str.resize(sz);
}

}  // namespace struct_pack::detail