/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#pragma once
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include 
#include 
#include 
namespace td {
namespace detail {
template 
class MaxSizeImpl {};
template 
constexpr const T &constexpr_max(const T &a, const T &b) {
  return a < b ? b : a;
}
template 
class MaxSizeImpl {
 public:
  static constexpr size_t value = MaxSizeImpl::value;
};
template 
class MaxSizeImpl {
 public:
  static constexpr size_t value = Res;
};
template 
class MaxSize {
 public:
  static constexpr size_t value = MaxSizeImpl<0, sizeof(Args)...>::value;
};
template 
class IthTypeImpl {};
template 
class IthTypeImpl<0, Res, Args...> {
 public:
  using type = Res;
};
template 
class IthTypeImpl : public IthTypeImpl {};
class Dummy {};
template 
class IthType : public IthTypeImpl {};
template 
class FindTypeOffsetImpl {};
template 
class FindTypeOffsetImpl {
 public:
  static constexpr int value = offset;
};
template 
class FindTypeOffsetImpl
    : public FindTypeOffsetImpl::value, offset + 1, T, Types...> {};
template 
class FindTypeOffset : public FindTypeOffsetImpl {};
template 
class ForEachTypeImpl {};
template 
class ForEachTypeImpl {
 public:
  template 
  static void visit(F &&f) {
  }
};
template 
class ForEachTypeImpl {
 public:
  template 
  static void visit(F &&f) {
    f(offset, static_cast(nullptr));
    ForEachTypeImpl::visit(f);
  }
};
template 
class ForEachType {
 public:
  template 
  static void visit(F &&f) {
    ForEachTypeImpl<0, Types..., Dummy>::visit(f);
  }
};
}  // namespace detail
template 
class Variant {
 public:
  static constexpr int npos = -1;
  Variant() {
  }
  Variant(Variant &&other) noexcept {
    other.visit([&](auto &&value) { this->init_empty(std::forward(value)); });
  }
  Variant(const Variant &other) {
    other.visit([&](auto &&value) { this->init_empty(std::forward(value)); });
  }
  Variant &operator=(Variant &&other) {
    clear();
    other.visit([&](auto &&value) { this->init_empty(std::forward(value)); });
    return *this;
  }
  Variant &operator=(const Variant &other) {
    clear();
    other.visit([&](auto &&value) { this->init_empty(std::forward(value)); });
    return *this;
  }
  bool operator==(const Variant &other) const {
    if (offset_ != other.offset_) {
      return false;
    }
    bool res = false;
    for_each([&](int offset, auto *ptr) {
      using T = std::decay_t;
      if (offset == offset_) {
        res = this->get() == other.template get();
      }
    });
    return res;
  }
  bool operator<(const Variant &other) const {
    if (offset_ != other.offset_) {
      return offset_ < other.offset_;
    }
    bool res = false;
    for_each([&](int offset, auto *ptr) {
      using T = std::decay_t;
      if (offset == offset_) {
        res = this->get() < other.template get();
      }
    });
    return res;
  }
  template , Variant>::value, int> = 0>
  Variant(T &&t) {
    init_empty(std::forward(t));
  }
  template , Variant>::value, int> = 0>
  Variant &operator=(T &&t) {
    clear();
    init_empty(std::forward(t));
    return *this;
  }
  template 
  static constexpr int offset() {
    return detail::FindTypeOffset, Types...>::value;
  }
  template 
  void init_empty(T &&t) {
    LOG_CHECK(offset_ == npos) << offset_
#if TD_CLANG || TD_GCC
                               << ' ' << __PRETTY_FUNCTION__
#endif
        ;
    offset_ = offset();
    new (&get()) std::decay_t(std::forward(t));
  }
  ~Variant() {
    clear();
  }
  template 
  void visit(F &&f) {
    for_each([&](int offset, auto *ptr) {
      using T = std::decay_t;
      if (offset == offset_) {
        f(std::move(*this->get_unsafe()));
      }
    });
  }
  template 
  void for_each(F &&f) {
    detail::ForEachType::visit(f);
  }
  template 
  void visit(F &&f) const {
    for_each([&](int offset, auto *ptr) {
      using T = std::decay_t;
      if (offset == offset_) {
        f(std::move(*this->get_unsafe()));
      }
    });
  }
  template 
  void for_each(F &&f) const {
    detail::ForEachType::visit(f);
  }
  void clear() {
    visit([](auto &&value) {
      using T = std::decay_t;
      value.~T();
    });
    offset_ = npos;
  }
  template 
  auto &get() {
    CHECK(offset == offset_);
    return *get_unsafe();
  }
  template 
  auto &get() {
    return get()>();
  }
  template 
  const auto &get() const {
    CHECK(offset == offset_);
    return *get_unsafe();
  }
  template 
  const auto &get() const {
    return get()>();
  }
  int32 get_offset() const {
    return offset_;
  }
  bool empty() const {
    return offset_ == npos;
  }
 private:
  union {
    int64 align_;
    char data_[detail::MaxSize::value];
  };
  int offset_{npos};
  template 
  auto *get_unsafe() {
    return reinterpret_cast(data_);
  }
  template 
  auto *get_unsafe() {
    using T = typename detail::IthType::type;
    return get_unsafe();
  }
  template 
  const auto *get_unsafe() const {
    return reinterpret_cast(data_);
  }
  template 
  const auto *get_unsafe() const {
    using T = typename detail::IthType::type;
    return get_unsafe();
  }
};
template 
auto &get(Variant &v) {
  return v.template get();
}
template 
auto &get(const Variant &v) {
  return v.template get();
}
template 
auto &get(Variant &v) {
  return v.template get();
}
template 
auto &get(const Variant &v) {
  return v.template get();
}
}  // namespace td