/*
 * 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 <cassert>
#include <cstdint>
#include <memory>
#include <tuple>

#include "error_code.hpp"
#include "marco.h"
#include "size_info.hpp"
#include "util.h"
namespace struct_pack {

template <typename... Args>
STRUCT_PACK_INLINE constexpr std::uint32_t get_type_code();
namespace detail {
struct MD5_pair {
  uint32_t md5;
  uint32_t index;
  constexpr friend bool operator<(const MD5_pair &l, const MD5_pair &r) {
    return l.md5 < r.md5;
  }
  constexpr friend bool operator>(const MD5_pair &l, const MD5_pair &r) {
    return l.md5 > r.md5;
  }
  constexpr friend bool operator==(const MD5_pair &l, const MD5_pair &r) {
    return l.md5 == r.md5;
  }
};

template <typename T>
constexpr uint32_t get_types_code();

template <typename DerivedClasses>
struct MD5_set {
  static constexpr int size = std::tuple_size_v<DerivedClasses>;
  static_assert(size <= 256);

 private:
  template <std::size_t... Index>
  static constexpr std::array<MD5_pair, size> calculate_md5(
      std::index_sequence<Index...>) {
    std::array<MD5_pair, size> md5{};
    ((md5[Index] =
          MD5_pair{
              get_types_code<std::tuple_element_t<Index, DerivedClasses>>(),
              Index}),
     ...);
    compile_time_sort(md5);
    return md5;
  }
  static constexpr std::size_t has_hash_collision_impl() {
    for (std::size_t i = 1; i < size; ++i) {
      if (value[i - 1] == value[i]) {
        return value[i].index;
      }
    }
    return 0;
  }

 public:
  static constexpr std::array<MD5_pair, size> value =
      calculate_md5(std::make_index_sequence<size>());
  static constexpr std::size_t has_hash_collision = has_hash_collision_impl();
};

template <typename BaseClass, typename DerivedClasses>
struct public_base_class_checker {
  static_assert(std::tuple_size_v<DerivedClasses> <= 256);

 private:
  template <std::size_t... Index>
  static constexpr bool calculate_md5(std::index_sequence<Index...>) {
    return (std::is_base_of_v<BaseClass,
                              std::tuple_element_t<Index, DerivedClasses>> &&
            ...);
  }

 public:
  static constexpr bool value = public_base_class_checker::calculate_md5(
      std::make_index_sequence<std::tuple_size_v<DerivedClasses>>());
};

template <typename DerivedClasses>
struct deserialize_derived_class_helper {
  template <size_t index, typename BaseClass, typename unpack>
  static STRUCT_PACK_INLINE constexpr struct_pack::errc run(
      std::unique_ptr<BaseClass> &base, unpack &unpacker) {
    if constexpr (index >= std::tuple_size_v<DerivedClasses>) {
      unreachable();
      return struct_pack::errc{};
    }
    else {
      using derived_class = std::tuple_element_t<index, DerivedClasses>;
      base = std::make_unique<derived_class>();
      return unpacker.deserialize(*(derived_class *)base.get());
    }
  }
};

template <typename T, uint64_t parent_tag = 0>
constexpr size_info inline calculate_one_size(const T &item);

template <typename DerivedClasses>
struct calculate_one_size_derived_class_helper {
  template <size_t index, typename BaseClass>
  static STRUCT_PACK_INLINE constexpr size_info run(BaseClass *base) {
    if constexpr (index >= std::tuple_size_v<DerivedClasses>) {
      unreachable();
    }
    else {
      using derived_class = std::tuple_element_t<index, DerivedClasses>;
#ifdef STRUCT_PACK_RTTI_ENABLED
      assert(dynamic_cast<derived_class *>(base));
#endif
      return calculate_one_size(*(derived_class *)base);
    }
  }
};

template <typename DerivedClasses, typename size_type, typename version>
struct serialize_one_derived_class_helper {
  template <size_t index, typename packer, typename BaseClass>
  static STRUCT_PACK_INLINE constexpr void run(packer *self, BaseClass *base) {
    if constexpr (index >= std::tuple_size_v<DerivedClasses>) {
      unreachable();
    }
    else {
      using derived_class = std::tuple_element_t<index, DerivedClasses>;
#ifdef STRUCT_PACK_RTTI_ENABLED
      assert(dynamic_cast<derived_class *>(base));
#endif
      self->template serialize_one<size_type::value, version::value>(
          *(derived_class *)base);
    }
  }
};

template <typename DerivedClasses, typename size_type, typename version,
          typename NotSkip>
struct deserialize_one_derived_class_helper {
  template <size_t index, typename unpacker, typename Pointer>
  static STRUCT_PACK_INLINE constexpr struct_pack::errc run(unpacker *self,
                                                            Pointer &base) {
    if constexpr (index >= std::tuple_size_v<DerivedClasses>) {
      unreachable();
    }
    else {
      using derived_class = std::tuple_element_t<index, DerivedClasses>;
      base = std::make_unique<derived_class>();
      return self->template deserialize_one<size_type::value, version::value,
                                            NotSkip::value>(
          *(derived_class *)base.get());
    }
  }
};

template <typename Base>
using derived_class_set_t = decltype(struct_pack_derived_decl((Base *)nullptr));

template <typename Base>
constexpr std::size_t search_type_by_md5(uint32_t id, bool &ok) {
  constexpr auto &MD5s = MD5_set<derived_class_set_t<Base>>::value;
  auto result = std::lower_bound(MD5s.begin(), MD5s.end(), MD5_pair{id, 0});
  ok = (result != MD5s.end() && result->md5 == id);
  if (!ok)
    return MD5s.size();
  else
    return result->index;
}

template <typename T>
std::uint32_t get_struct_pack_id_impl() {
  if constexpr (std::is_abstract_v<T>) {
    struct_pack::detail::unreachable();
  }
  else {
    return struct_pack::get_type_code<T>();
  }
}

template <typename Base, typename... Derives>
constexpr auto derived_decl_impl() {
  if constexpr (std::is_abstract_v<Base>) {
    return struct_pack::detail::declval<std::tuple<Derives...>>();
  }
  else {
    return struct_pack::detail::declval<std::tuple<Base, Derives...>>();
  }
}
}  // namespace detail
}  // namespace struct_pack