#ifndef PYTHONIC_INCLUDE_TYPES_STATIC_IF_HPP
#define PYTHONIC_INCLUDE_TYPES_STATIC_IF_HPP

#include <tuple>

PYTHONIC_NS_BEGIN

namespace types
{
  template <class T>
  struct StaticIfReturn {
    T arg;
  };

  template <class T>
  struct StaticIfNoReturn {
    T arg;
    StaticIfNoReturn() = default;
    StaticIfNoReturn(T const &arg) : arg(arg)
    {
    }
    long get(std::integral_constant<size_t, 0>)
    {
      return 0;
    }
    StaticIfNoReturn &get(std::integral_constant<size_t, 1>)
    {
      return *this;
    }
    T &get(std::integral_constant<size_t, 2>)
    {
      return arg;
    }

    template <class OT>
    operator OT() const
    {
      return OT();
    }
  };

  template <class T>
  struct StaticIfBreak {
    T arg;
    StaticIfBreak() = default;
    StaticIfBreak(T const &arg) : arg(arg)
    {
    }
    long get(std::integral_constant<size_t, 0>)
    {
      return 1;
    }
    T &get(std::integral_constant<size_t, 1>)
    {
      return arg;
    }
    T &get(std::integral_constant<size_t, 2>)
    {
      return arg;
    }

    template <class OT>
    operator OT() const
    {
      return OT();
    }
  };

  template <class T>
  struct StaticIfCont {
    T arg;
    StaticIfCont() = default;
    StaticIfCont(T const &arg) : arg(arg)
    {
    }
    long get(std::integral_constant<size_t, 0>)
    {
      return 2;
    }
    StaticIfCont &get(std::integral_constant<size_t, 1>)
    {
      return *this;
    }
    T &get(std::integral_constant<size_t, 2>)
    {
      return arg;
    }

    template <class OT>
    operator OT() const
    {
      return OT();
    }
  };

  template <class T0, class T1>
  struct StaticIfReturnHolder {
    std::tuple<int, T0, T1> args;

    StaticIfReturnHolder() : args(0, T0(), T1())
    {
    }
    StaticIfReturnHolder(StaticIfReturnHolder<T0, T1> const &) = default;

    template <class Tp0, class Tp1>
    StaticIfReturnHolder(StaticIfReturnHolder<Tp0, Tp1> const &other)
        : args(other.args)
    {
    }

    template <class Tp0>
    StaticIfReturnHolder(StaticIfReturn<Tp0> const &arg)
        : args(1, arg.arg, T1())
    {
    }
    StaticIfReturnHolder(StaticIfNoReturn<T1> const &arg)
        : args(0, T0(), arg.arg)
    {
    }
    StaticIfReturnHolder(StaticIfBreak<T1> const &arg) : args(2, T0(), arg.arg)
    {
    }
    StaticIfReturnHolder(StaticIfCont<T1> const &arg) : args(3, T0(), arg.arg)
    {
    }
  };
}

PYTHONIC_NS_END

namespace std
{
  template <size_t I, class T0, class T1>
  struct tuple_element<I, pythonic::types::StaticIfReturnHolder<T0, T1>> {
    using type = typename std::conditional<
        I == 0, bool, typename std::conditional<I == 1, T0, T1>::type>::type;
  };
  template <size_t I, class T0, class T1>
  auto get(pythonic::types::StaticIfReturnHolder<T0, T1> &t)
      -> decltype(std::get<I>(t.args))
  {
    return std::get<I>(t.args);
  }

  template <size_t I, class T>
  struct tuple_element<I, pythonic::types::StaticIfNoReturn<T>> {
    using type =
        decltype(std::declval<pythonic::types::StaticIfNoReturn<T>>().get(
            std::integral_constant<size_t, I>{}));
  };

  template <size_t I, class T>
  auto get(pythonic::types::StaticIfNoReturn<T> &t)
      -> decltype(t.get(std::integral_constant<size_t, I>{}))
  {
    return t.get(std::integral_constant<size_t, I>{});
  }

  template <size_t I, class T>
  struct tuple_element<I, pythonic::types::StaticIfBreak<T>> {
    using type = decltype(std::declval<pythonic::types::StaticIfBreak<T>>().get(
        std::integral_constant<size_t, I>{}));
  };

  template <size_t I, class T>
  auto get(pythonic::types::StaticIfBreak<T> &t)
      -> decltype(t.get(std::integral_constant<size_t, I>{}))
  {
    return t.get(std::integral_constant<size_t, I>{});
  }

  template <size_t I, class T>
  struct tuple_element<I, pythonic::types::StaticIfCont<T>> {
    using type = decltype(std::declval<pythonic::types::StaticIfCont<T>>().get(
        std::integral_constant<size_t, I>{}));
  };

  template <size_t I, class T>
  auto get(pythonic::types::StaticIfCont<T> &t)
      -> decltype(t.get(std::integral_constant<size_t, I>{}))
  {
    return t.get(std::integral_constant<size_t, I>{});
  }
}

/* type inference stuff { */
#include "pythonic/include/types/combined.hpp"

template <class T0, class T1>
struct __combined<pythonic::types::StaticIfReturn<T0>,
                  pythonic::types::StaticIfNoReturn<T1>> {
  using type = pythonic::types::StaticIfReturnHolder<T0, T1>;
};
template <class T0, class T1>
struct __combined<pythonic::types::StaticIfReturn<T0>,
                  pythonic::types::StaticIfBreak<T1>> {
  using type = pythonic::types::StaticIfReturnHolder<T0, T1>;
};
template <class T0, class T1>
struct __combined<pythonic::types::StaticIfReturn<T0>,
                  pythonic::types::StaticIfCont<T1>> {
  using type = pythonic::types::StaticIfReturnHolder<T0, T1>;
};

template <class T0, class T1>
struct __combined<pythonic::types::StaticIfNoReturn<T1>,
                  pythonic::types::StaticIfReturn<T0>> {
  using type = pythonic::types::StaticIfReturnHolder<T0, T1>;
};

template <class T0>
struct __combined<pythonic::types::StaticIfNoReturn<T0> &,
                  pythonic::types::none_type> {
  using type = pythonic::types::none_type;
};
template <class T0>
struct __combined<pythonic::types::StaticIfNoReturn<T0>,
                  pythonic::types::none_type> {
  using type = pythonic::types::none_type;
};
template <class T0>
struct __combined<pythonic::types::none_type,
                  pythonic::types::StaticIfNoReturn<T0>> {
  using type = pythonic::types::none_type;
};
template <class T0, class T1>
struct __combined<pythonic::types::StaticIfBreak<T1>,
                  pythonic::types::StaticIfReturn<T0>> {
  using type = pythonic::types::StaticIfReturnHolder<T0, T1>;
};
template <class T0, class T1>
struct __combined<pythonic::types::StaticIfCont<T1>,
                  pythonic::types::StaticIfReturn<T0>> {
  using type = pythonic::types::StaticIfReturnHolder<T0, T1>;
};

template <class T0, class T1, class T2, class T3>
struct __combined<pythonic::types::StaticIfReturnHolder<T0, T1>,
                  pythonic::types::StaticIfReturnHolder<T2, T3>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            typename __combined<T1, T3>::type>;
};
template <class T0, class T1, class T2>
struct __combined<pythonic::types::StaticIfReturnHolder<T0, T1>,
                  pythonic::types::StaticIfCont<T2>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            T1>;
};
template <class T0, class T1, class T2>
struct __combined<pythonic::types::StaticIfReturnHolder<T0, T1>,
                  pythonic::types::StaticIfBreak<T2>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            T1>;
};
template <class T0, class T1, class T2>
struct __combined<pythonic::types::StaticIfReturnHolder<T0, T1>,
                  pythonic::types::StaticIfReturn<T2>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            T1>;
};

template <class T0, class T1, class T2>
struct __combined<pythonic::types::StaticIfCont<T2>,
                  pythonic::types::StaticIfReturnHolder<T0, T1>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            T1>;
};

template <class T0, class T1, class T2>
struct __combined<pythonic::types::StaticIfBreak<T2>,
                  pythonic::types::StaticIfReturnHolder<T0, T1>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            T1>;
};

template <class T0, class T1, class T2>
struct __combined<pythonic::types::StaticIfReturn<T2>,
                  pythonic::types::StaticIfReturnHolder<T0, T1>> {
  using type =
      pythonic::types::StaticIfReturnHolder<typename __combined<T0, T2>::type,
                                            T1>;
};

template <class T0, class T1>
struct __combined<T0, pythonic::types::StaticIfNoReturn<T1>> {
  using type = T0;
};

template <class T0, class T1>
struct __combined<pythonic::types::StaticIfNoReturn<T1>, T0> {
  using type = T0;
};

/* } */
#endif
