/*
 * 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 <bitset>
#include <cstddef>
#include <cstdint>
#include <type_traits>

#include "calculate_size.hpp"
#include "endian_wrapper.hpp"
#include "reflection.hpp"
#include "ylt/struct_pack/type_id.hpp"
#include "ylt/struct_pack/util.h"
#include "ylt/struct_pack/varint.hpp"
namespace struct_pack::detail {
template <
#if __cpp_concepts >= 201907L
    writer_t writer,
#else
    typename writer,
#endif
    typename serialize_type>
class packer {

 public:
  packer(writer &writer_, const serialize_buffer_size &info)
      : writer_(writer_), info(info) {
#if __cpp_concepts < 201907L
    static_assert(writer_t<writer>,
                  "The writer type must satisfy requirements!");
#endif
  }
  packer(const packer &) = delete;
  packer &operator=(const packer &) = delete;

  template <typename DerivedClasses, typename size_type, typename version>
  friend struct serialize_one_derived_class_helper;

  template <std::size_t size_type, typename T, std::size_t... I,
            typename... Args>
  void serialize_expand_compatible_helper(const T &t, std::index_sequence<I...>,
                                          const Args &...args) {
    using Type = get_args_type<T, Args...>;
    (serialize_many<size_type, compatible_version_number<Type>[I]>(t, args...),
     ...);
  }

  template <uint64_t conf, std::size_t size_type, typename T, typename... Args>
  STRUCT_PACK_INLINE void serialize(const T &t, const Args &...args) {
    serialize_metainfo<conf, size_type == 1, T, Args...>();
    serialize_many<size_type, UINT64_MAX>(t, args...);
    using Type = get_args_type<T, Args...>;
    if constexpr (serialize_static_config<Type>::has_compatible) {
      constexpr std::size_t sz = compatible_version_number<Type>.size();
      return serialize_expand_compatible_helper<size_type, T, Args...>(
          t, std::make_index_sequence<sz>{}, args...);
    }
  }

 private:
  template <typename T, typename... Args>
  static constexpr uint32_t STRUCT_PACK_INLINE calculate_raw_hash() {
    if constexpr (sizeof...(Args) == 0) {
      return get_types_code<remove_cvref_t<T>>();
    }
    else {
      return get_types_code<
          std::tuple<remove_cvref_t<T>, remove_cvref_t<Args>...>>();
    }
  }
  template <uint64_t conf, typename T, typename... Args>
  static constexpr uint32_t STRUCT_PACK_INLINE calculate_hash_head() {
    constexpr uint32_t raw_types_code = calculate_raw_hash<T, Args...>();
    if constexpr (check_has_metainfo<conf, T>()) {
      return raw_types_code + 1;
    }
    else {  // default case, only has hash_code
      return raw_types_code;
    }
  }
  template <uint64_t conf, bool is_default_size_type, typename T,
            typename... Args>
  constexpr void STRUCT_PACK_INLINE serialize_metainfo() {
    constexpr auto hash_head = calculate_hash_head<conf, T, Args...>() |
                               (is_default_size_type ? 0 : 1);
    if constexpr (!check_if_disable_hash_head<conf, serialize_type>()) {
      write_wrapper<sizeof(uint32_t)>(writer_, (char *)&hash_head);
    }
    if constexpr (hash_head % 2) {  // has more metainfo
      auto metainfo = info.metainfo();
      write_wrapper<sizeof(char)>(writer_, (char *)&metainfo);
      if constexpr (serialize_static_config<serialize_type>::has_compatible) {
        std::size_t sz = info.size();
        switch (metainfo & 0b11) {
          case 1:
            low_bytes_write_wrapper<2>(writer_, sz);
            break;
          case 2:
            low_bytes_write_wrapper<4>(writer_, sz);
            break;
          case 3:
            if constexpr (sizeof(std::size_t) >= 8) {
              low_bytes_write_wrapper<8>(writer_, sz);
            }
            else {
              unreachable();
            }
            break;
          default:
            unreachable();
        }
      }
      if constexpr (check_if_add_type_literal<conf, serialize_type>()) {
        constexpr auto type_literal =
            struct_pack::get_type_literal<T, Args...>();
        write_bytes_array(writer_, type_literal.data(),
                          type_literal.size() + 1);
      }
    }
  }

 private:
  template <std::size_t size_type, uint64_t version,
            std::uint64_t parent_tag = 0, typename First, typename... Args>
  constexpr void STRUCT_PACK_INLINE serialize_many(const First &first_item,
                                                   const Args &...items) {
    serialize_one<size_type, version, parent_tag>(first_item);
    if constexpr (sizeof...(items) > 0) {
      serialize_many<size_type, version, parent_tag>(items...);
    }
  }
  constexpr void STRUCT_PACK_INLINE write_padding(std::size_t sz) {
    if (sz > 0) {
      constexpr char buf = 0;
      for (std::size_t i = 0; i < sz; ++i) write_wrapper<1>(writer_, &buf);
    }
  }

  template <uint64_t parent_tag, std::size_t sz, typename Arg,
            typename unsigned_t, typename signed_t>
  static constexpr void STRUCT_PACK_INLINE
  get_fast_varint_width_impl(char (&vec)[sz], unsigned int &i, const Arg &item,
                             unsigned_t &unsigned_max, signed_t &signed_max) {
    if constexpr (varint_t<Arg, parent_tag>) {
      if (get_varint_value(item) != 0) {
        vec[i / 8] |= (0b1 << (i % 8));
        if constexpr (std::is_unsigned_v<std::remove_reference_t<
                          decltype(get_varint_value(item))>>) {
          unsigned_max =
              std::max<unsigned_t>(unsigned_max, get_varint_value(item));
        }
        else {
          signed_max = std::max<signed_t>(signed_max,
                                          get_varint_value(item) > 0
                                              ? get_varint_value(item)
                                              : -(get_varint_value(item) + 1));
        }
      }
      ++i;
    }
  }

  template <uint64_t parent_tag, std::size_t sz, typename... Args>
  static constexpr int STRUCT_PACK_INLINE
  get_fast_varint_width(char (&vec)[sz], const Args &...items) {
    typename uint_t<get_int_len<parent_tag, Args...>()>::type unsigned_max = 0;
    typename int_t<get_int_len<parent_tag, Args...>()>::type signed_max = 0;
    unsigned int i = 0;
    (get_fast_varint_width_impl<parent_tag>(vec, i, items, unsigned_max,
                                            signed_max),
     ...);
    return get_fast_varint_width_from_max<parent_tag, Args...>(unsigned_max,
                                                               signed_max);
  }

  template <uint64_t parent_tag, std::size_t sz, typename Arg>
  constexpr void STRUCT_PACK_INLINE serialize_one_fast_varint(const Arg &item) {
    if constexpr (varint_t<Arg, parent_tag>) {
      if (get_varint_value(item))
        low_bytes_write_wrapper<(std::min)(sz, sizeof(Arg))>(
            writer_, get_varint_value(item));
    }
  }

  template <uint64_t parent_tag, typename... Args>
  constexpr void STRUCT_PACK_INLINE
  serialize_fast_varint(const Args &...items) {
    constexpr auto cnt = calculate_fast_varint_count<parent_tag, Args...>();
    constexpr auto bitset_size = ((cnt + 2) + 7) / 8;
    if constexpr (cnt == 0) {
      return;
    }
    else {
      char vec[bitset_size]{};
      auto width = get_fast_varint_width<parent_tag>(vec, items...);
      vec[cnt / 8] |= (width & 0b1) << (cnt % 8);
      vec[(cnt + 1) / 8] |= (!!(width & 0b10)) << ((cnt + 1) % 8);
      write_bytes_array(writer_, vec, bitset_size);
      switch (width) {
        case 0:
          (serialize_one_fast_varint<parent_tag, 1>(items), ...);
          break;
        case 1:
          (serialize_one_fast_varint<parent_tag, 2>(items), ...);
          break;
        case 2:
          (serialize_one_fast_varint<parent_tag, 4>(items), ...);
          break;
        case 3:
          if constexpr (has_64bits_varint<parent_tag, Args...>()) {
            (serialize_one_fast_varint<parent_tag, 8>(items), ...);
          }
          else {
            unreachable();
          }
          break;
        default:
          unreachable();
      }
    }
  }

  template <std::size_t size_type, uint64_t version, uint64_t parent_tag = 0,
            typename T>
  constexpr void inline serialize_one(const T &item) {
    using type = remove_cvref_t<decltype(item)>;
    static_assert(!std::is_pointer_v<type>);
    constexpr auto id = get_type_id<type, parent_tag>();
    if constexpr (is_trivial_view_v<T>) {
      return serialize_one<size_type, version>(item.get());
    }
    else if constexpr (version == UINT64_MAX) {
      if constexpr (id == type_id::compatible_t) {
        // do nothing
      }
      else if constexpr (std::is_same_v<type, std::monostate>) {
        // do nothing
      }
      else if constexpr (id == type_id::user_defined_type) {
        sp_serialize_to(writer_, item);
      }
      else if constexpr (detail::varint_t<type, parent_tag>) {
        if constexpr (is_enable_fast_varint_coding(parent_tag)) {
          // do nothing
        }
        else {
          detail::serialize_varint(writer_, item);
        }
      }
      else if constexpr (std::is_fundamental_v<type> || std::is_enum_v<type> ||
                         id == type_id::int128_t || id == type_id::uint128_t) {
        write_wrapper<sizeof(item)>(writer_, (char *)&item);
      }
      else if constexpr (id == type_id::bitset_t) {
        write_bytes_array(writer_, (char *)&item, sizeof(item));
      }
      else if constexpr (unique_ptr<type>) {
        bool has_value = (item != nullptr);
        write_wrapper<sizeof(char)>(writer_, (char *)&has_value);
        if (has_value) {
          if constexpr (is_base_class<typename type::element_type>) {
            bool is_ok{};
            uint32_t id = item->get_struct_pack_id();
            auto index = search_type_by_md5<typename type::element_type>(
                item->get_struct_pack_id(), is_ok);
            assert(is_ok);
            write_wrapper<sizeof(uint32_t)>(writer_, (char *)&id);
            template_switch<serialize_one_derived_class_helper<
                derived_class_set_t<typename type::element_type>,
                std::integral_constant<std::size_t, size_type>,
                std::integral_constant<std::uint64_t, version>>>(index, this,
                                                                 item.get());
          }
          else {
            serialize_one<size_type, version>(*item);
          }
        }
      }
      else if constexpr (id == type_id::array_t) {
        if constexpr (is_trivial_serializable<type>::value &&
                      is_little_endian_copyable<sizeof(item[0])>) {
          write_bytes_array(writer_, (char *)&item, sizeof(type));
        }
        else {
          for (const auto &i : item) {
            serialize_one<size_type, version>(i);
          }
        }
      }
      else if constexpr (map_container<type> || container<type>) {
        auto size = item.size();
        if constexpr (size_type == 1) {
          low_bytes_write_wrapper<size_type>(writer_, size);
        }
#ifdef STRUCT_PACK_OPTIMIZE
        else if constexpr (size_type == 2) {
          low_bytes_write_wrapper<size_type>(writer_, size);
        }
        else if constexpr (size_type == 4) {
          low_bytes_write_wrapper<size_type>(writer_, size);
        }
        else if constexpr (size_type == 8) {
          if constexpr (sizeof(std::size_t) >= 8) {
            low_bytes_write_wrapper<size_type>(writer_, size);
          }
          else {
            static_assert(!sizeof(T), "illegal size_type");
          }
        }
        else {
          static_assert(!sizeof(item), "illegal size_type.");
        }
#else
        else {
          auto size = item.size();
          switch ((info.metainfo() & 0b11000) >> 3) {
            case 1:
              low_bytes_write_wrapper<2>(writer_, size);
              break;
            case 2:
              low_bytes_write_wrapper<4>(writer_, size);
              break;
            case 3:
              if constexpr (sizeof(std::size_t) >= 8) {
                low_bytes_write_wrapper<8>(writer_, size);
              }
              else {
                unreachable();
              }
              break;
            default:
              unreachable();
          }
        }
#endif
        if constexpr (trivially_copyable_container<type> &&
                      is_little_endian_copyable<sizeof(
                          typename type::value_type)>) {
          using value_type = typename type::value_type;
          write_bytes_array(writer_, (char *)item.data(),
                            item.size() * sizeof(typename type::value_type));
          return;
        }
        else {
          for (const auto &i : item) {
            serialize_one<size_type, version>(i);
          }
        }
      }
      else if constexpr (container_adapter<type>) {
        static_assert(!sizeof(type),
                      "the container adapter type is not supported");
      }
      else if constexpr (!pair<type> && tuple<type> &&
                         !is_trivial_tuple<type>) {
        std::apply(
            [&](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
              serialize_many<size_type, version>(items...);
            },
            item);
      }
      else if constexpr (optional<type>) {
        bool has_value = item.has_value();
        write_wrapper<sizeof(bool)>(writer_, (char *)&has_value);
        if (has_value) {
          serialize_one<size_type, version>(*item);
        }
      }
      else if constexpr (is_variant_v<type>) {
        static_assert(std::variant_size_v<type> < 256,
                      "variant's size is too large");
        uint8_t index = item.index();
        write_wrapper<sizeof(uint8_t)>(writer_, (char *)&index);
        std::visit(
            [this](auto &&e) {
              this->serialize_one<size_type, version>(e);
            },
            item);
      }
      else if constexpr (expected<type>) {
        bool has_value = item.has_value();
        write_wrapper<sizeof(bool)>(writer_, (char *)&has_value);
        if (has_value) {
          if constexpr (!std::is_same_v<typename type::value_type, void>)
            serialize_one<size_type, version>(item.value());
        }
        else {
          serialize_one<size_type, version>(item.error());
        }
      }
      else if constexpr (std::is_class_v<type>) {
        if constexpr (!pair<type> && !is_trivial_tuple<type>)
          if constexpr (!user_defined_refl<type>)
            static_assert(
                std::is_aggregate_v<remove_cvref_t<type>>,
                "struct_pack only support aggregated type, or you should "
                "add macro STRUCT_PACK_REFL(Type,field1,field2...)");
        if constexpr (is_trivial_serializable<type>::value &&
                      is_little_endian_copyable<sizeof(type)>) {
          write_wrapper<sizeof(type)>(writer_, (char *)&item);
        }
        else if constexpr ((is_trivial_serializable<type>::value &&
                            !is_little_endian_copyable<sizeof(type)>) ||
                           is_trivial_serializable<type, true>::value) {
          visit_members(item, [this](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
            int i = 1;
            ((serialize_one<size_type, version>(items),
              write_padding(align::padding_size<type>[i++])),
             ...);
          });
        }
        else {
          constexpr uint64_t tag = get_parent_tag<type>();
          if constexpr (is_enable_fast_varint_coding(tag)) {
            visit_members(
                item, [this](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
                  constexpr uint64_t tag =
                      get_parent_tag<type>();  // to pass msvc with c++17
                  serialize_fast_varint<tag>(items...);
                });
          }
          visit_members(item, [this](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
            constexpr uint64_t tag =
                get_parent_tag<type>();  // to pass msvc with c++17
            serialize_many<size_type, version, tag>(items...);
          });
        }
      }
      else {
        static_assert(!sizeof(type), "the type is not supported yet");
      }
    }
    else if constexpr (exist_compatible_member<type, version>) {
      if constexpr (id == type_id::compatible_t) {
        if constexpr (version == type::version_number) {
          bool has_value = item.has_value();
          write_wrapper<sizeof(bool)>(writer_, (char *)&has_value);
          if (has_value) {
            serialize_one<size_type, UINT64_MAX>(*item);
          }
        }
      }
      else if constexpr (unique_ptr<type>) {
        if (item != nullptr) {
          if constexpr (is_base_class<typename type::element_type>) {
            bool is_ok{};
            auto index = search_type_by_md5<typename type::element_type>(
                item->get_struct_pack_id(), is_ok);
            assert(is_ok);
            template_switch<serialize_one_derived_class_helper<
                derived_class_set_t<typename type::element_type>,
                std::integral_constant<std::size_t, size_type>,
                std::integral_constant<std::uint64_t, version>>>(index, this,
                                                                 item.get());
          }
          else {
            serialize_one<size_type, version>(*item);
          }
        }
      }
      else if constexpr (id == type_id::array_t) {
        for (const auto &i : item) {
          serialize_one<size_type, version>(i);
        }
      }
      else if constexpr (map_container<type> || container<type>) {
        for (const auto &i : item) {
          serialize_one<size_type, version>(i);
        }
      }
      else if constexpr (!pair<type> && tuple<type> &&
                         !is_trivial_tuple<type>) {
        std::apply(
            [&](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
              serialize_many<size_type, version>(items...);
            },
            item);
      }
      else if constexpr (optional<type>) {
        if (item.has_value()) {
          serialize_one<size_type, version>(*item);
        }
      }
      else if constexpr (is_variant_v<type>) {
        std::visit(
            [this](const auto &e) {
              this->serialize_one<size_type, version>(e);
            },
            item);
      }
      else if constexpr (expected<type>) {
        if (item.has_value()) {
          if constexpr (!std::is_same_v<typename type::value_type, void>)
            serialize_one<size_type, version>(item.value());
        }
        else {
          serialize_one<size_type, version>(item.error());
        }
      }
      else if constexpr (std::is_class_v<type>) {
        visit_members(item, [&](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
          serialize_many<size_type, version>(items...);
        });
      }
    }
    return;
  }

  template <typename T>
  friend constexpr serialize_buffer_size get_needed_size(const T &t);
  writer &writer_;
  const serialize_buffer_size &info;
};

template <uint64_t conf = sp_config::DEFAULT,
#if __cpp_concepts >= 201907L
          struct_pack::writer_t Writer,
#else
          typename Writer,
#endif
          typename... Args>
STRUCT_PACK_MAY_INLINE void serialize_to(Writer &writer,
                                         const serialize_buffer_size &info,
                                         const Args &...args) {
#if __cpp_concepts < 201907L
  static_assert(writer_t<Writer>, "The writer type must satisfy requirements!");
#endif
  static_assert(sizeof...(args) > 0);
  detail::packer<Writer, detail::get_args_type<Args...>> o(writer, info);
  if constexpr (!check_if_has_container<detail::get_args_type<Args...>>()) {
    o.template serialize<conf, 1>(args...);
  }
  else {
    switch ((info.metainfo() & 0b11000) >> 3) {
      case 0:
        o.template serialize<conf, 1>(args...);
        break;
#ifdef STRUCT_PACK_OPTIMIZE
      case 1:
        o.template serialize<conf, 2>(args...);
        break;
      case 2:
        o.template serialize<conf, 4>(args...);
        break;
      case 3:
        if constexpr (sizeof(std::size_t) >= 8) {
          o.template serialize<conf, 8>(args...);
        }
        else {
          unreachable();
        }
        break;
#else
      case 1:
      case 2:
      case 3:
        o.template serialize<conf, 2>(args...);
        break;
#endif
      default:
        detail::unreachable();
        break;
    };
  }
}
}  // namespace struct_pack::detail