#ifndef PYTHONIC_TYPES_NDARRAY_HPP
#define PYTHONIC_TYPES_NDARRAY_HPP

#include "pythonic/include/types/ndarray.hpp"

#include "pythonic/types/assignable.hpp"
#include "pythonic/types/empty_iterator.hpp"
#include "pythonic/types/attr.hpp"

#include "pythonic/builtins/ValueError.hpp"

#include "pythonic/utils/nested_container.hpp"
#include "pythonic/utils/shared_ref.hpp"
#include "pythonic/utils/reserve.hpp"
#include "pythonic/utils/int_.hpp"
#include "pythonic/utils/broadcast_copy.hpp"

#include "pythonic/types/slice.hpp"
#include "pythonic/types/tuple.hpp"
#include "pythonic/types/list.hpp"
#include "pythonic/types/raw_array.hpp"

#include "pythonic/numpy/bool_.hpp"
#include "pythonic/numpy/uint8.hpp"
#include "pythonic/numpy/int8.hpp"
#include "pythonic/numpy/uint16.hpp"
#include "pythonic/numpy/int16.hpp"
#include "pythonic/numpy/uint32.hpp"
#include "pythonic/numpy/int32.hpp"
#include "pythonic/numpy/uint64.hpp"
#include "pythonic/numpy/int64.hpp"
#include "pythonic/numpy/float32.hpp"
#include "pythonic/numpy/float64.hpp"
#include "pythonic/numpy/complex64.hpp"
#include "pythonic/numpy/complex128.hpp"

#include "pythonic/types/vectorizable_type.hpp"
#include "pythonic/types/numpy_op_helper.hpp"
#include "pythonic/types/numpy_expr.hpp"
#include "pythonic/types/numpy_texpr.hpp"
#include "pythonic/types/numpy_iexpr.hpp"
#include "pythonic/types/numpy_gexpr.hpp"
#include "pythonic/types/numpy_vexpr.hpp"
#include "pythonic/utils/numpy_traits.hpp"
#include "pythonic/utils/array_helper.hpp"

#include "pythonic/builtins/len.hpp"
#include "pythonic/operator_/iadd.hpp"
#include "pythonic/operator_/iand.hpp"
#include "pythonic/operator_/idiv.hpp"
#include "pythonic/operator_/imul.hpp"
#include "pythonic/operator_/ior.hpp"
#include "pythonic/operator_/ixor.hpp"
#include "pythonic/operator_/isub.hpp"

#include <cassert>
#include <iostream>
#include <iterator>
#include <array>
#include <initializer_list>
#include <numeric>

#if !defined(HAVE_SSIZE_T) || !HAVE_SSIZE_T
#if defined(_MSC_VER)
#include <BaseTsd.h>
typedef SSIZE_T ssize_t;
#endif
#endif

PYTHONIC_NS_BEGIN

namespace types
{
  template <class pS, size_t... Is>
  array<long, std::tuple_size<pS>::value>
  make_strides(pS const &shape, utils::index_sequence<Is...>)
  {
    array<long, std::tuple_size<pS>::value> out;
    out[std::tuple_size<pS>::value - 1] = 1;
    (void)std::initializer_list<long>{
        (out[std::tuple_size<pS>::value - Is - 2] =
             out[std::tuple_size<pS>::value - Is - 1] *
             std::get<std::tuple_size<pS>::value - Is - 1>(shape))...};
    return out;
  }

  template <class pS>
  array<long, std::tuple_size<pS>::value> make_strides(pS const &shape)
  {
    return make_strides(
        shape, utils::make_index_sequence<std::tuple_size<pS>::value - 1>());
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pS>>::iterator
  type_helper<ndarray<T, pS>>::make_iterator(ndarray<T, pS> &n, long i)
  {
    return {n, i};
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pS>>::const_iterator
  type_helper<ndarray<T, pS>>::make_iterator(ndarray<T, pS> const &n, long i)
  {
    return {n, i};
  }

  template <class T, class pS>
  template <class S, class Iter>
  T *type_helper<ndarray<T, pS>>::initialize_from_iterable(S &shape, T *from,
                                                           Iter &&iter)
  {
    return type_helper<ndarray<T, pS> const &>::initialize_from_iterable(
        shape, from, std::forward<Iter>(iter));
  }

  template <class T, class pS>
  numpy_iexpr<ndarray<T, pS>>
  type_helper<ndarray<T, pS>>::get(ndarray<T, pS> &&self, long i)
  {
    return {std::move(self), i};
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pS> const &>::iterator
  type_helper<ndarray<T, pS> const &>::make_iterator(ndarray<T, pS> &n, long i)
  {
    return {n, i};
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pS> const &>::const_iterator
  type_helper<ndarray<T, pS> const &>::make_iterator(ndarray<T, pS> const &n,
                                                     long i)
  {
    return {n, i};
  }

  template <class T, class pS>
  template <class S, class Iter>
  T *type_helper<ndarray<T, pS> const &>::initialize_from_iterable(S &shape,
                                                                   T *from,
                                                                   Iter &&iter)
  {
    sutils::assign(
        std::get<std::tuple_size<S>::value - std::tuple_size<pS>::value>(shape),
        iter.size());
    for (auto content : iter)
      from = type_helper<ndarray<T, sutils::pop_tail_t<pS>> const &>::
          initialize_from_iterable(shape, from, content);
    return from;
  }

  template <class T, class pS>
  numpy_iexpr<ndarray<T, pS> const &>
  type_helper<ndarray<T, pS> const &>::get(ndarray<T, pS> const &self, long i)
  {
    return numpy_iexpr<ndarray<T, pS> const &>(self, i);
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pshape<pS>>>::iterator
  type_helper<ndarray<T, pshape<pS>>>::make_iterator(ndarray<T, pshape<pS>> &n,
                                                     long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pshape<pS>>>::const_iterator
  type_helper<ndarray<T, pshape<pS>>>::make_iterator(
      ndarray<T, pshape<pS>> const &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  template <class S, class Iter>
  T *type_helper<ndarray<T, pshape<pS>>>::initialize_from_iterable(S &shape,
                                                                   T *from,
                                                                   Iter &&iter)
  {
    sutils::assign(std::get<std::tuple_size<S>::value - 1>(shape), iter.size());
    return std::copy(iter.begin(), iter.end(), from);
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pshape<pS>>>::type
  type_helper<ndarray<T, pshape<pS>>>::get(ndarray<T, pshape<pS>> &&self,
                                           long i)
  {
    return self.buffer[i];
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pshape<pS>> const &>::iterator
  type_helper<ndarray<T, pshape<pS>> const &>::make_iterator(
      ndarray<T, pshape<pS>> &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pshape<pS>> const &>::const_iterator
  make_iterator(ndarray<T, pshape<pS>> const &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  template <class S, class Iter>
  T *type_helper<ndarray<T, pshape<pS>> const &>::initialize_from_iterable(
      S &shape, T *from, Iter &&iter)
  {
    sutils::assign(std::get<std::tuple_size<S>::value - 1>(shape), iter.size());
    return std::copy(iter.begin(), iter.end(), from);
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, pshape<pS>> const &>::type &
  type_helper<ndarray<T, pshape<pS>> const &>::get(
      ndarray<T, pshape<pS>> const &self, long i)
  {
    return self.buffer[i];
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, array<pS, 1>>>::iterator
      type_helper<ndarray<T, array<pS, 1>>>::make_iterator(
          ndarray<T, array<pS, 1>> &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, array<pS, 1>>>::const_iterator
      type_helper<ndarray<T, array<pS, 1>>>::make_iterator(
          ndarray<T, array<pS, 1>> const &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  template <class S, class Iter>
  T *type_helper<ndarray<T, array<pS, 1>>>::initialize_from_iterable(
      S &shape, T *from, Iter &&iter)
  {
    sutils::assign(std::get<std::tuple_size<S>::value - 1>(shape), iter.size());
    return std::copy(iter.begin(), iter.end(), from);
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, array<pS, 1>>>::type
      type_helper<ndarray<T, array<pS, 1>>>::get(
          ndarray<T, array<pS, 1>> &&self, long i)
  {
    return self.buffer[i];
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, array<pS, 1>> const &>::iterator
      type_helper<ndarray<T, array<pS, 1>> const &>::make_iterator(
          ndarray<T, array<pS, 1>> &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, array<pS, 1>> const &>::const_iterator
      make_iterator(ndarray<T, array<pS, 1>> const &n, long i)
  {
    return n.buffer + i;
  }

  template <class T, class pS>
  template <class S, class Iter>
  T *type_helper<ndarray<T, array<pS, 1>> const &>::initialize_from_iterable(
      S &shape, T *from, Iter &&iter)
  {
    sutils::assign(std::get<std::tuple_size<S>::value - 1>(shape), iter.size());
    return std::copy(iter.begin(), iter.end(), from);
  }

  template <class T, class pS>
  typename type_helper<ndarray<T, array<pS, 1>> const &>::type &
      type_helper<ndarray<T, array<pS, 1>> const &>::get(
          ndarray<T, array<pS, 1>> const &self, long i)
  {
    return self.buffer[i];
  }

  template <class S>
  long patch_index(long index, S const &)
  {
    return index;
  }
  long patch_index(long index, std::integral_constant<long, 1> const &)
  {
    return 0;
  }

  template <size_t L>
  template <class S, class Ty, size_t M>
  long noffset<L>::operator()(S const &strides,
                              array<Ty, M> const &indices) const
  {
    auto index = patch_index(
        indices[M - L],
        typename std::tuple_element<M - L, typename S::shape_t>::type());
    return noffset<L - 1>{}(strides, indices) +
           strides.template strides<M - L>() * index;
  }

  template <size_t L>
  template <class S, class Ty, size_t M, class pS>
  long noffset<L>::operator()(S const &strides, array<Ty, M> const &indices,
                              pS const &shape) const
  {
    auto index = patch_index(
        indices[M - L],
        typename std::tuple_element<M - L, typename S::shape_t>::type());
    if (index < 0)
      index += std::get<M - L>(shape);
    assert(0 <= index and index < std::get<M - L>(shape));
    return noffset<L - 1>{}(strides, indices, shape) +
           strides.template strides<M - L>() *
               ((index < 0) ? index + std::get<M - L>(shape) : index);
  }

  template <>
  template <class S, class Ty, size_t M>
  long noffset<1>::operator()(S const &strides,
                              array<Ty, M> const &indices) const
  {
    auto index = patch_index(
        indices[M - 1],
        typename std::tuple_element<M - 1, typename S::shape_t>::type());
    return strides.template strides<M - 1>() * index;
  }

  template <>
  template <class S, class Ty, size_t M, class pS>
  long noffset<1>::operator()(S const &strides, array<Ty, M> const &indices,
                              pS const &shape) const
  {
    auto index = patch_index(
        indices[M - 1],
        typename std::tuple_element<M - 1, typename S::shape_t>::type());
    if (index < 0)
      index += std::get<M - 1>(shape);
    assert(0 <= index && index < std::get<M - 1>(shape));
    return strides.template strides<M - 1>() *
           ((index < 0) ? index + std::get<M - 1>(shape) : index);
  }

  /* constructors */
  template <class T, class pS>
  ndarray<T, pS>::ndarray()
      : mem(utils::no_memory()), buffer(nullptr), _shape(), _strides()
  {
  }

  /* from other memory */
  template <class T, class pS>
  ndarray<T, pS>::ndarray(utils::shared_ref<raw_array<T>> const &mem,
                          pS const &shape)
      : mem(mem), buffer(mem->data), _shape(shape),
        _strides(make_strides(shape))
  {
  }
  template <class T, class pS>
  ndarray<T, pS>::ndarray(utils::shared_ref<raw_array<T>> &&mem,
                          pS const &shape)
      : mem(std::move(mem)), buffer(this->mem->data), _shape(shape),
        _strides(make_strides(shape))
  {
  }

  /* from other array */
  template <class T, class pS>
  template <class Tp, class pSp>
  ndarray<T, pS>::ndarray(ndarray<Tp, pSp> const &other)
      : mem(other.flat_size()), buffer(mem->data), _shape(other._shape),
        _strides(other._strides)
  {
    static_assert(std::tuple_size<pS>::value == std::tuple_size<pSp>::value,
                  "compatible shapes");
    std::copy(other.fbegin(), other.fend(), fbegin());
  }

  template <class T, class pS>
  template <class pSp>
  ndarray<T, pS>::ndarray(ndarray<T, pSp> const &other)
      : mem(other.mem), buffer(mem->data), _shape(other._shape),
        _strides(other._strides)
  {
    static_assert(std::tuple_size<pS>::value == std::tuple_size<pSp>::value,
                  "compatible shapes");
  }

  /* from a seed */
  template <class T, class pS>
  ndarray<T, pS>::ndarray(pS const &shape, none_type init)
      : mem(sutils::sprod(shape)), buffer(mem->data), _shape(shape),
        _strides(make_strides(shape))
  {
  }

  template <class T, class pS>
  ndarray<T, pS>::ndarray(pS const &shape, T init)
      : ndarray(shape, none_type())
  {
    std::fill(fbegin(), fend(), init);
  }

  /* from a foreign pointer */
  template <class T, class pS>
  template <class S>
  ndarray<T, pS>::ndarray(T *data, S const *pshape, ownership o)
      : mem(data, o), buffer(mem->data), _shape(pshape)
  {
    _strides = make_strides(_shape);
  }
  template <class T, class pS>
  ndarray<T, pS>::ndarray(T *data, pS const &pshape, ownership o)
      : mem(data, o), buffer(mem->data), _shape(pshape)
  {
    _strides = make_strides(_shape);
  }

#ifdef ENABLE_PYTHON_MODULE
  template <class T, class pS>
  template <class S>
  ndarray<T, pS>::ndarray(T *data, S const *pshape, PyObject *obj_ptr)
      : ndarray(data, pshape, ownership::external)
  {
    mem.external(obj_ptr); // mark memory as external to decref at the end of
                           // its lifetime
  }
  template <class T, class pS>
  ndarray<T, pS>::ndarray(T *data, pS const &pshape, PyObject *obj_ptr)
      : ndarray(data, pshape, ownership::external)
  {
    mem.external(obj_ptr); // mark memory as external to decref at the end of
                           // its lifetime
  }

#endif

  template <class T, class pS>
  template <class Iterable, class>
  ndarray<T, pS>::ndarray(Iterable &&iterable)
      : mem(utils::nested_container_size<Iterable>::flat_size(
            std::forward<Iterable>(iterable))),
        buffer(mem->data), _shape()
  {
    type_helper<ndarray>::initialize_from_iterable(
        _shape, mem->data, std::forward<Iterable>(iterable));
    _strides = make_strides(_shape);
  }

  /* from a  numpy expression */
  template <class T, class pS>
  template <class E>
  void ndarray<T, pS>::initialize_from_expr(E const &expr)
  {
    assert(buffer);
    utils::broadcast_copy<ndarray &, E, value, 0,
                          is_vectorizable && E::is_vectorizable &&
                              std::is_same<dtype, typename E::dtype>::value>(
        *this, expr);
  }

  template <class T, class pS>
  template <class Op, class... Args>
  ndarray<T, pS>::ndarray(numpy_expr<Op, Args...> const &expr)
      : mem(expr.flat_size()), buffer(mem->data),
        _shape(sutils::getshape(expr)), _strides(make_strides(_shape))
  {
    initialize_from_expr(expr);
  }

  template <class T, class pS>
  template <class Arg>
  ndarray<T, pS>::ndarray(numpy_texpr<Arg> const &expr)
      : mem(expr.flat_size()), buffer(mem->data),
        _shape(sutils::getshape(expr)), _strides(make_strides(_shape))
  {
    initialize_from_expr(expr);
  }

  template <class T, class pS>
  template <class Arg>
  ndarray<T, pS>::ndarray(numpy_texpr_2<Arg> const &expr)
      : mem(expr.flat_size()), buffer(mem->data),
        _shape(sutils::getshape(expr)), _strides(make_strides(_shape))
  {
    initialize_from_expr(expr);
  }

  template <class T, class pS>
  template <class Arg, class... S>
  ndarray<T, pS>::ndarray(numpy_gexpr<Arg, S...> const &expr)
      : mem(expr.flat_size()), buffer(mem->data),
        _shape(sutils::getshape(expr)), _strides(make_strides(_shape))
  {
    initialize_from_expr(expr);
  }

  template <class T, class pS>
  template <class Arg>
  ndarray<T, pS>::ndarray(numpy_iexpr<Arg> const &expr)
      : mem(expr.flat_size()), buffer(mem->data),
        _shape(sutils::getshape(expr)), _strides(make_strides(_shape))
  {
    initialize_from_expr(expr);
  }

  template <class T, class pS>
  template <class Arg, class F>
  ndarray<T, pS>::ndarray(numpy_vexpr<Arg, F> const &expr)
      : mem(expr.flat_size()), buffer(mem->data),
        _shape(sutils::getshape(expr)), _strides(make_strides(_shape))
  {
    initialize_from_expr(expr);
  }

  /* update operators */

  template <class T, class pS>
  template <class Op, class Expr>
  ndarray<T, pS> &ndarray<T, pS>::update_(Expr const &expr)
  {
    using BExpr =
        typename std::conditional<std::is_scalar<Expr>::value,
                                  broadcast<Expr, T>, Expr const &>::type;
    BExpr bexpr = expr;
    utils::broadcast_update<
        Op, ndarray &, BExpr, value,
        value - (std::is_scalar<Expr>::value + utils::dim_of<Expr>::value),
        is_vectorizable &&
            types::is_vectorizable<typename std::remove_cv<
                typename std::remove_reference<BExpr>::type>::type>::value &&
            std::is_same<dtype, typename dtype_of<typename std::decay<
                                    BExpr>::type>::type>::value>(*this, bexpr);
    return *this;
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator+=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::iadd>(expr);
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator-=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::isub>(expr);
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator*=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::imul>(expr);
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator/=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::idiv>(expr);
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator&=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::iand>(expr);
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator|=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::ior>(expr);
  }

  template <class T, class pS>
  template <class Expr>
  ndarray<T, pS> &ndarray<T, pS>::operator^=(Expr const &expr)
  {
    return update_<pythonic::operator_::functor::ixor>(expr);
  }

  /* element indexing
   * differentiate const from non const, && r-value from l-value
   * */

  template <class T, class pS>
  template <class Ty>
  typename std::enable_if<std::is_integral<Ty>::value, T &>::type
  ndarray<T, pS>::fast(array<Ty, value> const &indices)
  {
    assert(inbound_indices(indices));
    return *(buffer + noffset<std::tuple_size<pS>::value>{}(*this, indices));
  }

  template <class T, class pS>
  template <class Ty>
  typename std::enable_if<std::is_integral<Ty>::value, T>::type
  ndarray<T, pS>::fast(array<Ty, value> const &indices) const
  {
    assert(inbound_indices(indices));
    return *(buffer + noffset<std::tuple_size<pS>::value>{}(*this, indices));
  }

  template <class T, class pS>
  template <class Ty, size_t M>
  auto ndarray<T, pS>::fast(array<Ty, M> const &indices) const & ->
      typename std::enable_if<std::is_integral<Ty>::value,
                              decltype(nget<M - 1>().fast(*this,
                                                          indices))>::type
  {
    return nget<M - 1>().fast(*this, indices);
  }

  template <class T, class pS>
      template <class Ty, size_t M>
      auto ndarray<T, pS>::fast(array<Ty, M> const &indices) &&
      -> typename std::enable_if<std::is_integral<Ty>::value,
                                 decltype(nget<M - 1>().fast(std::move(*this),
                                                             indices))>::type
  {
    return nget<M - 1>().fast(std::move(*this), indices);
  }

  template <class T, class pS>
  template <class Ty>
  typename std::enable_if<std::is_integral<Ty>::value, T const &>::type
      ndarray<T, pS>::
      operator[](array<Ty, value> const &indices) const
  {
    return *(buffer +
             noffset<std::tuple_size<pS>::value>{}(*this, indices, _shape));
  }

  template <class T, class pS>
  template <class Ty>
  typename std::enable_if<std::is_integral<Ty>::value, T &>::type
      ndarray<T, pS>::
      operator[](array<Ty, value> const &indices)
  {
    return *(buffer +
             noffset<std::tuple_size<pS>::value>{}(*this, indices, _shape));
  }

  template <class T, class pS>
  template <class Ty, size_t M>
  auto ndarray<T, pS>::operator[](array<Ty, M> const &indices) const & ->
      typename std::enable_if<std::is_integral<Ty>::value,
                              decltype(nget<M - 1>()(*this, indices))>::type
  {
    return nget<M - 1>()(*this, indices);
  }

  template <class T, class pS>
      template <class Ty, size_t M>
      auto ndarray<T, pS>::operator[](array<Ty, M> const &indices) &&
      -> typename std::enable_if<std::is_integral<Ty>::value,
                                 decltype(nget<M - 1>()(std::move(*this),
                                                        indices))>::type
  {
    return nget<M - 1>()(std::move(*this), indices);
  }

#ifdef USE_XSIMD
  template <class T, class pS>
  template <class vectorizer>
  typename ndarray<T, pS>::simd_iterator
      ndarray<T, pS>::vbegin(vectorizer) const
  {
    return {buffer};
  }

  template <class T, class pS>
  template <class vectorizer>
  typename ndarray<T, pS>::simd_iterator ndarray<T, pS>::vend(vectorizer) const
  {
    using vector_type = typename xsimd::simd_type<dtype>;
    static const std::size_t vector_size = vector_type::size;
    return {buffer + long(std::get<0>(_shape) / vector_size * vector_size)};
  }

#endif

  /* slice indexing */
  template <class T, class pS>
  ndarray<T, sutils::push_front_t<pS, std::integral_constant<long, 1>>>
      ndarray<T, pS>::operator[](none_type) const
  {
    sutils::push_front_t<pS, std::integral_constant<long, 1>> new_shape;
    sutils::copy_shape<1, -1>(
        new_shape, *this,
        utils::make_index_sequence<std::tuple_size<pS>::value>());
    return reshape(new_shape);
  }

  template <class T, class pS>
  template <class S>
  typename std::enable_if<
      is_slice<S>::value,
      numpy_gexpr<ndarray<T, pS> const &, normalize_t<S>>>::type
      ndarray<T, pS>::
      operator[](S const &s) const &
  {
    return make_gexpr(*this, s);
  }

  template <class T, class pS>
  template <class S>
  typename std::enable_if<is_slice<S>::value,
                          numpy_gexpr<ndarray<T, pS>, normalize_t<S>>>::type
      ndarray<T, pS>::
      operator[](S const &s) &&
  {
    return make_gexpr(std::move(*this), s);
  }

  template <class T, class pS>
  long ndarray<T, pS>::size() const
  {
    return std::get<0>(_shape);
  }

  /* extended slice indexing */
  template <class T, class pS>
  template <class S0, class... S>
  auto ndarray<T, pS>::operator()(S0 const &s0, S const &... s) const
      & -> decltype(extended_slice<count_new_axis<S0, S...>::value>{}((*this),
                                                                      s0, s...))
  {
    return extended_slice<count_new_axis<S0, S...>::value>{}((*this), s0, s...);
  }

  template <class T, class pS>
      template <class S0, class... S>
      auto ndarray<T, pS>::operator()(S0 const &s0, S const &... s) &
      -> decltype(extended_slice<count_new_axis<S0, S...>::value>{}((*this), s0,
                                                                    s...))
  {
    return extended_slice<count_new_axis<S0, S...>::value>{}((*this), s0, s...);
  }

  template <class T, class pS>
      template <class S0, class... S>
      auto ndarray<T, pS>::operator()(S0 const &s0, S const &... s) &&
      -> decltype(extended_slice<count_new_axis<S0, S...>::value>{}(
          std::move(*this), s0, s...))
  {
    return extended_slice<count_new_axis<S0, S...>::value>{}(std::move(*this),
                                                             s0, s...);
  }

  /* element filtering */
  template <class T, class pS>
  template <class F> // indexing through an array of boolean -- a mask
  typename std::enable_if<
      is_numexpr_arg<F>::value &&
          std::is_same<bool, typename F::dtype>::value && F::value == 1 &&
          !is_pod_array<F>::value,
      numpy_vexpr<ndarray<T, pS>, ndarray<long, pshape<long>>>>::type
  ndarray<T, pS>::fast(F const &filter) const
  {
    long sz = filter.template shape<0>();
    long *raw = (long *)malloc(sz * sizeof(long));
    long n = 0;
    for (long i = 0; i < sz; ++i)
      if (filter.fast(i))
        raw[n++] = i;
    // realloc(raw, n * sizeof(long));
    return this->fast(ndarray<long, pshape<long>>(raw, pshape<long>(n),
                                                  types::ownership::owned));
  }

  template <class T, class pS>
  template <class F> // indexing through an array of boolean -- a mask
  typename std::enable_if<
      is_numexpr_arg<F>::value &&
          std::is_same<bool, typename F::dtype>::value && F::value == 1 &&
          !is_pod_array<F>::value,
      numpy_vexpr<ndarray<T, pS>, ndarray<long, pshape<long>>>>::type
      ndarray<T, pS>::
      operator[](F const &filter) const
  {
    return fast(filter);
  }
  template <class T, class pS>
  template <class F> // indexing through an array of boolean -- a mask
  typename std::enable_if<
      is_numexpr_arg<F>::value &&
          std::is_same<bool, typename F::dtype>::value && F::value != 1 &&
          !is_pod_array<F>::value,
      numpy_vexpr<ndarray<T, pshape<long>>, ndarray<long, pshape<long>>>>::type
  ndarray<T, pS>::fast(F const &filter) const
  {
    return flat()[ndarray<typename F::dtype, typename F::shape_t>(filter)
                      .flat()];
  }

  template <class T, class pS>
  template <class F> // indexing through an array of boolean -- a mask
  typename std::enable_if<
      is_numexpr_arg<F>::value &&
          std::is_same<bool, typename F::dtype>::value && F::value != 1 &&
          !is_pod_array<F>::value,
      numpy_vexpr<ndarray<T, pshape<long>>, ndarray<long, pshape<long>>>>::type
      ndarray<T, pS>::
      operator[](F const &filter) const
  {
    return fast(filter);
  }

  template <class T, class pS>
  template <class F> // indexing through an array of indices -- a view
  typename std::enable_if<is_numexpr_arg<F>::value &&
                              !is_array_index<F>::value &&
                              !std::is_same<bool, typename F::dtype>::value &&
                              !is_pod_array<F>::value,
                          numpy_vexpr<ndarray<T, pS>, F>>::type ndarray<T, pS>::
  operator[](F const &filter) const
  {
    return {*this, filter};
  }

  template <class T, class pS>
  template <class F> // indexing through an array of indices -- a view
  typename std::enable_if<is_numexpr_arg<F>::value &&
                              !is_array_index<F>::value &&
                              !std::is_same<bool, typename F::dtype>::value &&
                              !is_pod_array<F>::value,
                          numpy_vexpr<ndarray<T, pS>, F>>::type
  ndarray<T, pS>::fast(F const &filter) const
  {
    return {*this, filter};
  }

  template <class T, class pS>
  template <class Ty0, class Ty1, class... Tys, class _>
  auto ndarray<T, pS>::
  operator[](std::tuple<Ty0, Ty1, Tys...> const &indices) const ->
      typename std::enable_if<
          is_numexpr_arg<Ty0>::value,
          decltype(this->_fwdindex(
              indices, utils::make_index_sequence<2 + sizeof...(Tys)>()))>::type
  {
    return _fwdindex(indices, utils::make_index_sequence<2 + sizeof...(Tys)>());
  }

  /* through iterators */
  template <class T, class pS>
  typename ndarray<T, pS>::iterator ndarray<T, pS>::begin()
  {
    return type_helper<ndarray>::make_iterator(*this, 0);
  }

  template <class T, class pS>
  typename ndarray<T, pS>::const_iterator ndarray<T, pS>::begin() const
  {
    return type_helper<ndarray>::make_iterator(*this, 0);
  }

  template <class T, class pS>
  typename ndarray<T, pS>::iterator ndarray<T, pS>::end()
  {
    return type_helper<ndarray>::make_iterator(*this, std::get<0>(_shape));
  }

  template <class T, class pS>
  typename ndarray<T, pS>::const_iterator ndarray<T, pS>::end() const
  {
    return type_helper<ndarray>::make_iterator(*this, std::get<0>(_shape));
  }

  template <class T, class pS>
  typename ndarray<T, pS>::const_flat_iterator ndarray<T, pS>::fbegin() const
  {
    return buffer;
  }

  template <class T, class pS>
  typename ndarray<T, pS>::const_flat_iterator ndarray<T, pS>::fend() const
  {
    return buffer + flat_size();
  }

  template <class T, class pS>
  typename ndarray<T, pS>::flat_iterator ndarray<T, pS>::fbegin()
  {
    return buffer;
  }

  template <class T, class pS>
  typename ndarray<T, pS>::flat_iterator ndarray<T, pS>::fend()
  {
    return buffer + flat_size();
  }

  /* member functions */
  template <class T, class pS>
  long ndarray<T, pS>::flat_size() const
  {
    return sutils::prod(*this);
  }
  template <class T, class pS>
  bool ndarray<T, pS>::may_overlap(ndarray const &expr) const
  {
    return id() == expr.id();
  }

  template <class T, class pS>
  template <class qS>
  ndarray<T, qS> ndarray<T, pS>::reshape(qS const &shape) const &
  {
    return {mem, shape};
  }

  template <class T, class pS>
  template <class qS>
  ndarray<T, qS> ndarray<T, pS>::reshape(qS const &shape) &&
  {
    return {std::move(mem), shape};
  }

  template <class T, class pS>
  ndarray<T, pS>::operator bool() const
  {
    if (sutils::any_of(*this, [](long n) { return n != 1; }))
      throw ValueError("The truth value of an array with more than one element "
                       "is ambiguous. Use a.any() or a.all()");
    return *buffer;
  }

  template <class T, class pS>
  ndarray<T, pshape<long>> ndarray<T, pS>::flat() const
  {
    return {mem, pshape<long>{{flat_size()}}};
  }

  template <class T, class pS>
  ndarray<T, pS> ndarray<T, pS>::copy() const
  {
    ndarray<T, pS> res(_shape, builtins::None);
    std::copy(fbegin(), fend(), res.fbegin());
    return res;
  }

  template <class T, class pS>
  intptr_t ndarray<T, pS>::id() const
  {
    return reinterpret_cast<intptr_t>(&(*mem));
  }

  /* pretty printing { */
  namespace impl
  {
    template <class T, class pS>
    size_t get_spacing(ndarray<T, pS> const &e)
    {
      std::ostringstream oss;
      if (e.flat_size()) {
        oss << *std::max_element(e.fbegin(), e.fend());
        size_t s = oss.str().length();
        for (auto iter = e.fbegin(), end = e.fend(); iter != end; ++iter) {
          oss.str("");
          oss.width(s);
          oss << *iter;
          size_t ts = oss.str().length();
          if (ts > s)
            s = ts;
        }
        return s;
      }
      return 0;
    }
    template <class T, class pS>
    size_t get_spacing(ndarray<std::complex<T>, pS> const &e)
    {
      std::ostringstream oss;
      if (e.flat_size())
        oss << *e.fbegin();
      return oss.str().length() + 2;
    }
  }

  template <class T, class pS>
  std::ostream &operator<<(std::ostream &os, ndarray<T, pS> const &e)
  {
    std::array<long, std::tuple_size<pS>::value> strides;
    auto shape = sutils::getshape(e);
    strides[std::tuple_size<pS>::value - 1] =
        std::get<std::tuple_size<pS>::value - 1>(shape);
    if (strides[std::tuple_size<pS>::value - 1] == 0)
      return os << "[]";
    std::transform(strides.rbegin(), strides.rend() - 1, shape.rbegin() + 1,
                   strides.rbegin() + 1, std::multiplies<long>());
    size_t depth = std::tuple_size<pS>::value;
    int step = -1;
    size_t size = impl::get_spacing(e);
    auto iter = e.fbegin();
    int max_modulo = 1000;

    os << "[";
    if (std::get<0>(shape) != 0)
      do {
        if (depth == 1) {
          os.width(size);
          os << *iter++;
          for (int i = 1; i < std::get<std::tuple_size<pS>::value - 1>(shape);
               i++) {
            os.width(size + 1);
            os << *iter++;
          }
          step = 1;
          depth++;
          max_modulo = std::lower_bound(
                           strides.begin(), strides.end(), iter - e.buffer,
                           [](int comp, int val) { return val % comp != 0; }) -
                       strides.begin();
        } else if (max_modulo + depth == std::tuple_size<pS>::value + 1) {
          depth--;
          step = -1;
          os << "]";
          for (size_t i = 0; i < depth; i++)
            os << std::endl;
          for (size_t i = 0; i < std::tuple_size<pS>::value - depth; i++)
            os << " ";
          os << "[";
        } else {
          depth += step;
          if (step == 1)
            os << "]";
          else
            os << "[";
        }
      } while (depth != std::tuple_size<pS>::value + 1);

    return os << "]";
  }

  template <class E>
  typename std::enable_if<is_array<E>::value, std::ostream &>::type
  operator<<(std::ostream &os, E const &e)
  {
    return os << ndarray<typename E::dtype, typename E::shape_t>{e};
  }

  /* } */
  template <class T>
  template <class pS>
  list<T> &list<T>::operator=(ndarray<T, pshape<pS>> const &other)
  {
    data = utils::shared_ref<T>(other.begin(), other.end());
    return *this;
  }
}
PYTHONIC_NS_END

/* std::get overloads */
namespace std
{

  template <size_t I, class E>
  auto get(E &&a) -> typename std::enable_if<
      pythonic::types::is_array<typename std::remove_cv<
          typename std::remove_reference<E>::type>::type>::value,
      decltype(std::forward<E>(a)[I])>::type
  {
    return std::forward<E>(a)[I];
  }
}

/* pythran attribute system { */
#include "pythonic/numpy/transpose.hpp"
PYTHONIC_NS_BEGIN
namespace builtins
{
  namespace details
  {
    template <size_t N>
    template <class E, class... S>
    auto _build_gexpr<N>::operator()(E const &a, S const &... slices)
        -> decltype(_build_gexpr<N - 1>{}(a, types::contiguous_slice(),
                                          slices...))
    {
      return _build_gexpr<N - 1>{}(a, types::contiguous_slice(0, a.size()),
                                   slices...);
    }

    template <class E, class... S>
    types::numpy_gexpr<E, types::normalize_t<S>...> _build_gexpr<1>::
    operator()(E const &a, S const &... slices)
    {
      return E(a)(slices...);
    }

    template <class E>
    E _make_real(E const &a, utils::int_<0>)
    {
      return a;
    }

    template <class E>
    auto _make_real(E const &a, utils::int_<1>)
        -> decltype(_build_gexpr<E::value>{}(
            types::ndarray<typename types::is_complex<typename E::dtype>::type,
                           types::array<long, E::value>>{},
            types::slice()))
    {
      using stype = typename types::is_complex<typename E::dtype>::type;
      auto new_shape = sutils::getshape(a);
      std::get<E::value - 1>(new_shape) *= 2;
      // this is tricky && dangerous!
      auto translated_mem =
          reinterpret_cast<utils::shared_ref<types::raw_array<stype>> const &>(
              a.mem);
      types::ndarray<stype, types::array<long, E::value>> translated{
          translated_mem, new_shape};
      return _build_gexpr<E::value>{}(
          translated, types::slice{0, std::get<E::value - 1>(new_shape), 2});
    }
    template <class Op, class... Args>
    auto _make_real(types::numpy_expr<Op, Args...> const &a, utils::int_<1>)
        -> decltype(_make_real(
            types::ndarray<typename types::numpy_expr<Op, Args...>::dtype,
                           typename types::numpy_expr<Op, Args...>::shape_t>(a),
            utils::int_<1>{}))
    {
      return _make_real(
          types::ndarray<typename types::numpy_expr<Op, Args...>::dtype,
                         typename types::numpy_expr<Op, Args...>::shape_t>(a),
          utils::int_<1>{});
    }

    template <class E>
    types::ndarray<typename E::dtype, typename E::shape_t>
    _make_imag(E const &a, utils::int_<0>)
    {
      // cannot use numpy.zero: forward declaration issue
      return {
          (typename E::dtype *)calloc(a.flat_size(), sizeof(typename E::dtype)),
          sutils::getshape(a), types::ownership::owned};
    }

    template <class Op, class... Args>
    auto _make_imag(types::numpy_expr<Op, Args...> const &a, utils::int_<1>)
        -> decltype(_make_imag(
            types::ndarray<typename types::numpy_expr<Op, Args...>::dtype,
                           typename types::numpy_expr<Op, Args...>::shape_t>(a),
            utils::int_<1>{}))
    {
      return _make_imag(
          types::ndarray<typename types::numpy_expr<Op, Args...>::dtype,
                         typename types::numpy_expr<Op, Args...>::shape_t>(a),
          utils::int_<1>{});
    }

    template <class E>
    auto _make_imag(E const &a, utils::int_<1>)
        -> decltype(_build_gexpr<E::value>{}(
            types::ndarray<typename types::is_complex<typename E::dtype>::type,
                           types::array<long, E::value>>{},
            types::slice()))
    {
      using stype = typename types::is_complex<typename E::dtype>::type;
      auto new_shape = sutils::getshape(a);
      std::get<E::value - 1>(new_shape) *= 2;
      // this is tricky && dangerous!
      auto translated_mem =
          reinterpret_cast<utils::shared_ref<types::raw_array<stype>> const &>(
              a.mem);
      types::ndarray<stype, types::array<long, E::value>> translated{
          translated_mem, new_shape};
      return _build_gexpr<E::value>{}(
          translated, types::slice{1, std::get<E::value - 1>(new_shape), 2});
    }
  }

  template <class E>
  types::array<long, E::value> getattr(types::attr::SHAPE, E const &a)
  {
    return sutils::getshape(a);
  }

  template <class E>
  long getattr(types::attr::NDIM, E const &a)
  {
    return E::value;
  }

  template <class E>
  types::array<long, E::value> getattr(types::attr::STRIDES, E const &a)
  {
    types::array<long, E::value> strides;
    strides[E::value - 1] = sizeof(typename E::dtype);
    auto shape = sutils::getshape(a);
    std::transform(strides.rbegin(), strides.rend() - 1, shape.rbegin(),
                   strides.rbegin() + 1, std::multiplies<long>());
    return strides;
  }

  template <class E>
  long getattr(types::attr::SIZE, E const &a)
  {
    return a.flat_size();
  }

  template <class E>
  long getattr(types::attr::ITEMSIZE, E const &a)
  {
    return sizeof(typename E::dtype);
  }

  template <class E>
  long getattr(types::attr::NBYTES, E const &a)
  {
    return a.flat_size() * sizeof(typename E::dtype);
  }

  template <class E>
  auto getattr(types::attr::FLAT, E const &a) -> decltype(a.flat())
  {
    return a.flat();
  }

  template <class T, class pS>
  auto getattr(types::attr::REAL, types::ndarray<T, pS> const &a) -> decltype(
      details::_make_real(a, utils::int_<types::is_complex<T>::value>{}))
  {
    return details::_make_real(a, utils::int_<types::is_complex<T>::value>{});
  }

  template <class Op, class... Args>
  auto getattr(types::attr::REAL, types::numpy_expr<Op, Args...> const &a)
      -> decltype(details::_make_real(
          a, utils::int_<types::is_complex<
                 typename types::numpy_expr<Op, Args...>::dtype>::value>{}))
  {
    return details::_make_real(
        a, utils::int_<types::is_complex<
               typename types::numpy_expr<Op, Args...>::dtype>::value>{});
  }

  template <class E>
  auto getattr(types::attr::REAL, types::numpy_texpr<E> const &a) -> decltype(
      types::numpy_texpr<decltype(getattr(types::attr::REAL{}, a.arg))>{
          getattr(types::attr::REAL{}, a.arg)})
  {
    auto ta = getattr(types::attr::REAL{}, a.arg);
    return types::numpy_texpr<decltype(ta)>{ta};
  }

  template <class T, class pS>
  auto getattr(types::attr::IMAG, types::ndarray<T, pS> const &a) -> decltype(
      details::_make_imag(a, utils::int_<types::is_complex<T>::value>{}))
  {
    return details::_make_imag(a, utils::int_<types::is_complex<T>::value>{});
  }

  template <class Op, class... Args>
  auto getattr(types::attr::IMAG, types::numpy_expr<Op, Args...> const &a)
      -> decltype(details::_make_imag(
          a, utils::int_<types::is_complex<
                 typename types::numpy_expr<Op, Args...>::dtype>::value>{}))
  {
    return details::_make_imag(
        a, utils::int_<types::is_complex<
               typename types::numpy_expr<Op, Args...>::dtype>::value>{});
  }

  template <class E>
  auto getattr(types::attr::IMAG, types::numpy_texpr<E> const &a) -> decltype(
      types::numpy_texpr<decltype(getattr(types::attr::IMAG{}, a.arg))>{
          getattr(types::attr::IMAG{}, a.arg)})
  {
    auto ta = getattr(types::attr::IMAG{}, a.arg);
    return types::numpy_texpr<decltype(ta)>{ta};
  }

  template <class E>
  types::dtype_t<typename types::dtype_of<E>::type> getattr(types::attr::DTYPE,
                                                            E const &a)
  {
    return {};
  }
}
PYTHONIC_NS_END

/* } */

#include "pythonic/types/numpy_operators.hpp"

#ifdef ENABLE_PYTHON_MODULE

#include "pythonic/types/int.hpp"

PYTHONIC_NS_BEGIN

/* wrapper around Python array creation
 * its purpose is to hide the difference between the shape stored in pythran
 * (aka long) && the shape stored in numpy (aka npy_intp)
 * it should work (with an extra copy) on 32 bit architecture && without copy
 * on 64 bits architecture
 */
template <class T, size_t N>
struct pyarray_new {

  static_assert(!std::is_same<T, npy_intp>::value, "correctly specialized");

  PyObject *from_descr(PyTypeObject *subtype, PyArray_Descr *descr, T *dims,
                       void *data, int flags, PyObject *obj)
  {
    npy_intp shape[N];
    std::copy(dims, dims + N, shape);
    return pyarray_new<npy_intp, N>{}.from_descr(subtype, descr, shape, data,
                                                 flags, obj);
  }
  PyObject *from_data(T *dims, int typenum, void *data)
  {
    npy_intp shape[N];
    std::copy(dims, dims + N, shape);
    return pyarray_new<npy_intp, N>{}.from_data(shape, typenum, data);
  }
};

template <size_t N>
struct pyarray_new<npy_intp, N> {

  PyObject *from_descr(PyTypeObject *subtype, PyArray_Descr *descr,
                       npy_intp *dims, void *data, int flags, PyObject *obj)
  {
    return PyArray_NewFromDescr(subtype, descr, N, dims, nullptr, data, flags,
                                obj);
  }

  PyObject *from_data(npy_intp *dims, int typenum, void *data)
  {

    return PyArray_SimpleNewFromData(N, dims, typenum, data);
  }
};

template <class T, class pS>
PyObject *
to_python<types::ndarray<T, pS>>::convert(types::ndarray<T, pS> const &cn,
                                          bool transpose)
{
  types::ndarray<T, pS> &n = const_cast<types::ndarray<T, pS> &>(cn);
  if (PyObject *p = n.mem.get_foreign()) {
    PyArrayObject *arr = reinterpret_cast<PyArrayObject *>(p);
    auto const *pshape = PyArray_DIMS(arr);
    Py_INCREF(p);

    // handle complex trick :-/
    if ((long)sizeof(T) != PyArray_ITEMSIZE((PyArrayObject *)(arr))) {
      arr = (PyArrayObject *)PyArray_View(
          (PyArrayObject *)(arr),
          PyArray_DescrFromType(c_type_to_numpy_type<T>::value), nullptr);
    }

    if (sutils::equals(n, pshape)) {
      if (transpose && !(PyArray_FLAGS(arr) & NPY_ARRAY_F_CONTIGUOUS)) {
        PyObject *Transposed = PyArray_Transpose(arr, nullptr);
        Py_DECREF(arr);
        return Transposed;
      } else
        return p;
    } else if (sutils::requals(n, pshape)) {
      if (transpose)
        return p;
      else {
        PyObject *Transposed = PyArray_Transpose(arr, nullptr);
        Py_DECREF(arr);
        return Transposed;
      }
    } else {
      Py_INCREF(PyArray_DESCR(arr));
      auto array = sutils::array(n._shape);
      auto *res = pyarray_new<long, std::tuple_size<pS>::value>{}.from_descr(
          Py_TYPE(arr), PyArray_DESCR(arr), array.data(), PyArray_DATA(arr),
          PyArray_FLAGS(arr) & ~NPY_ARRAY_OWNDATA, p);
      if (transpose && (PyArray_FLAGS(arr) & NPY_ARRAY_F_CONTIGUOUS)) {
        PyObject *Transposed =
            PyArray_Transpose(reinterpret_cast<PyArrayObject *>(arr), nullptr);
        Py_DECREF(arr);
        return Transposed;
      } else
        return res;
    }
  } else {
    auto array = sutils::array(n._shape);
    PyObject *result =
        pyarray_new<long, std::tuple_size<pS>::value>{}.from_data(
            array.data(), c_type_to_numpy_type<T>::value, n.buffer);
    n.mark_memory_external(result);
    Py_INCREF(result); // because it's going to be decrefed when n is destroyed
    if (!result)
      return nullptr;
    PyArray_ENABLEFLAGS(reinterpret_cast<PyArrayObject *>(result),
                        NPY_ARRAY_OWNDATA);
    if (transpose) {
      PyObject *Transposed =
          PyArray_Transpose(reinterpret_cast<PyArrayObject *>(result), nullptr);
      Py_DECREF(result);
      return Transposed;
    } else
      return result;
  }
}

template <class Arg>
PyObject *
to_python<types::numpy_iexpr<Arg>>::convert(types::numpy_iexpr<Arg> const &v,
                                            bool transpose)
{
  PyObject *res =
      ::to_python(types::ndarray<typename types::numpy_iexpr<Arg>::dtype,
                                 typename types::numpy_iexpr<Arg>::shape_t>(v));
  if (transpose) {
    PyObject *Transposed =
        PyArray_Transpose(reinterpret_cast<PyArrayObject *>(res), nullptr);
    Py_DECREF(res);
    return Transposed;
  } else
    return res;
}

template <class Arg, class... S>
PyObject *to_python<types::numpy_gexpr<Arg, S...>>::convert(
    types::numpy_gexpr<Arg, S...> const &v, bool transpose)
{
  PyObject *slices = (sizeof...(S) == 1) ? ::to_python(std::get<0>(v.slices))
                                         : ::to_python(v.slices);
  PyObject *base = ::to_python(v.arg);
  PyObject *res = PyObject_GetItem(base, slices);
  Py_DECREF(base);
  if (transpose) {
    PyObject *Transposed =
        PyArray_Transpose(reinterpret_cast<PyArrayObject *>(res), nullptr);
    Py_DECREF(res);
    return Transposed;
  } else
    return res;
}

namespace impl
{
  template <class T>
  struct is_integral_constant : std::false_type {
  };
  template <class T, T N>
  struct is_integral_constant<std::integral_constant<T, N>> : std::true_type {
  };

  template <class pS, class T, size_t... Is>
  bool check_shape(T const *dims, utils::index_sequence<Is...>)
  {
    types::array<bool, sizeof...(Is)> dims_match = {
        (is_integral_constant<typename std::tuple_element<Is, pS>::type>::value
             ? (dims[Is] ==
                std::conditional<
                    is_integral_constant<
                        typename std::tuple_element<Is, pS>::type>::value,
                    typename std::tuple_element<Is, pS>::type,
                    std::integral_constant<long, 0>>::type::value)
             : true)...};
    return std::find(dims_match.begin(), dims_match.end(), false) ==
           dims_match.end();
  }

  template <typename T, class pS>
  PyArrayObject *check_array_type_and_dims(PyObject *obj)
  {
    if (!PyArray_Check(obj))
      return nullptr;
    // the array must have the same dtype && the same number of dimensions
    PyArrayObject *arr = reinterpret_cast<PyArrayObject *>(obj);
    if (PyArray_TYPE(arr) != c_type_to_numpy_type<T>::value)
      return nullptr;
    if (PyArray_NDIM(arr) != std::tuple_size<pS>::value)
      return nullptr;
    return arr;
  }

  template <class T, class Slice, class S>
  void fill_slice(Slice &slice, long const *strides, long const *offsets,
                  S const *dims, utils::int_<0>)
  {
  }

  void set_slice(types::contiguous_normalized_slice &cs, long lower, long upper,
                 long step)
  {
    cs.lower = lower;
    cs.upper = upper;
    assert(cs.step == step && "consistent steps");
  }
  void set_slice(types::normalized_slice &s, long lower, long upper, long step)
  {
    s.lower = lower;
    s.upper = upper;
    s.step = step;
  }

  template <class T, class Slice, class S, size_t N>
  void fill_slice(Slice &slice, long const *strides, long const *offsets,
                  S const *dims, utils::int_<N>)
  {
    set_slice(std::get<std::tuple_size<Slice>::value - N>(slice),
              *offsets / sizeof(T),
              *offsets / sizeof(T) + *dims * *strides / sizeof(T),
              *strides / sizeof(T));
    fill_slice<T>(slice, strides + 1, offsets + 1, dims + 1,
                  utils::int_<N - 1>());
  }
}

template <typename T, class pS>
bool from_python<types::ndarray<T, pS>>::is_convertible(PyObject *obj)
{
  PyArrayObject *arr = impl::check_array_type_and_dims<T, pS>(obj);
  if (!arr)
    return false;
  auto const *stride = PyArray_STRIDES(arr);
  auto const *dims = PyArray_DIMS(arr);
  long current_stride = PyArray_ITEMSIZE(arr);
  if (PyArray_SIZE(arr)) {
    for (long i = std::tuple_size<pS>::value - 1; i >= 0; i--) {
      if (stride[i] == 0 && dims[i] == 1) {
        // happens when a new dim is added though None/newaxis
      } else if (stride[i] != current_stride && dims[i] > 1) {
        return false;
      }
      current_stride *= dims[i];
    }
    // this is supposed to be a texpr
    if ((PyArray_FLAGS(arr) & NPY_ARRAY_F_CONTIGUOUS) &&
        ((PyArray_FLAGS(arr) & NPY_ARRAY_C_CONTIGUOUS) == 0) &&
        (std::tuple_size<pS>::value > 1)) {
      return false;
    }
  }

  // check if dimension size match
  return impl::check_shape<pS>(
      dims, utils::make_index_sequence<std::tuple_size<pS>::value>());
}
template <typename T, class pS>
types::ndarray<T, pS> from_python<types::ndarray<T, pS>>::convert(PyObject *obj)
{
  PyArrayObject *arr = reinterpret_cast<PyArrayObject *>(obj);
  types::ndarray<T, pS> r((T *)PyArray_BYTES(arr), PyArray_DIMS(arr), obj);
  Py_INCREF(obj);
  return r;
}

template <typename T, class pS, class... S>
bool from_python<types::numpy_gexpr<types::ndarray<T, pS>,
                                    S...>>::is_convertible(PyObject *obj)
{
  PyArrayObject *arr = impl::check_array_type_and_dims<T, pS>(obj);
  if (!arr)
    return false;

  if ((PyArray_FLAGS(arr) & NPY_ARRAY_F_CONTIGUOUS) &&
      ((PyArray_FLAGS(arr) & NPY_ARRAY_C_CONTIGUOUS) == 0) &&
      (std::tuple_size<pS>::value > 1)) {
    return false;
  }

  PyObject *base_obj = PyArray_BASE(arr);
  if (!base_obj || !PyArray_Check(base_obj))
    return false;
  PyArrayObject *base_arr = reinterpret_cast<PyArrayObject *>(base_obj);

  auto const *stride = PyArray_STRIDES(arr);
  auto const *dims = PyArray_DIMS(arr);

  /* FIXME If we have at least one stride, we convert the whole
   * array to a numpy_gexpr, without trying to be smarter with
   * contiguous slices
   */
  long current_stride = PyArray_ITEMSIZE(arr);
  bool at_least_one_stride = false;
  for (long i = std::tuple_size<pS>::value - 1; i >= 0; i--) {
    if (stride[i] < 0) {
      return false;
    }
    if (stride[i] == 0 && dims[i] == 1) {
      // happens when a new dim is added though None/newaxis
    } else if (stride[i] != current_stride) {
      at_least_one_stride = true;
      break;
    }
    current_stride *= dims[i];
  }
  if (at_least_one_stride) {
    if (PyArray_NDIM(base_arr) != std::tuple_size<pS>::value) {
      return false;
    }
    return true;
  } else
    return false;
}

template <typename T, class pS, class... S>
types::numpy_gexpr<types::ndarray<T, pS>, S...>
from_python<types::numpy_gexpr<types::ndarray<T, pS>, S...>>::convert(
    PyObject *obj)
{
  PyArrayObject *arr = reinterpret_cast<PyArrayObject *>(obj);
  PyArrayObject *base_arr =
      reinterpret_cast<PyArrayObject *>(PyArray_BASE(arr));

  /* from the base array pointer && this array pointer, we can recover the
   * full slice informations
   * unfortunately, the PyArray representation is different from our.
   * - PyArray_BYTES gives the start of the base pointer
   * - PyArray_Dims give the dimension array (the shape)
   * - PyArray_STRIDES gives the stride information, but relative to the
   * base
   * pointer && ! relative to the lower dimension
   */
  long offsets[std::tuple_size<pS>::value];
  long strides[std::tuple_size<pS>::value];
  auto const *base_dims = PyArray_DIMS(base_arr);

  auto full_offset = PyArray_BYTES(arr) - PyArray_BYTES(base_arr);
  auto const *arr_strides = PyArray_STRIDES(arr);
  long accumulated_dim = 1;
  offsets[std::tuple_size<pS>::value - 1] =
      full_offset % base_dims[std::tuple_size<pS>::value - 1];
  strides[std::tuple_size<pS>::value - 1] =
      arr_strides[std::tuple_size<pS>::value - 1];
  for (ssize_t i = std::tuple_size<pS>::value - 2; i >= 0; --i) {
    accumulated_dim *= base_dims[i + 1];
    offsets[i] = full_offset / accumulated_dim;
    strides[i] = arr_strides[i] / accumulated_dim;
  }
  types::ndarray<T, pS> base_array((T *)PyArray_BYTES(base_arr),
                                   PyArray_DIMS(base_arr),
                                   (PyObject *)base_arr);
  std::tuple<S...> slices;
  impl::fill_slice<T>(slices, strides, offsets, PyArray_DIMS(arr),
                      utils::int_<sizeof...(S)>());
  types::numpy_gexpr<types::ndarray<T, pS>, S...> r(base_array, slices);

  Py_INCREF(base_arr);
  return r;
}

template <typename E>
bool from_python<types::numpy_texpr<E>>::

    is_convertible(PyObject *obj)
{
  constexpr auto N = E::value;
  PyArrayObject *arr =
      impl::check_array_type_and_dims<typename E::dtype, typename E::shape_t>(
          obj);
  if (!arr)
    return false;
  // check strides. Note that because it's a texpr, the check is done in the
  // opposite direction compared to ndarrays
  auto const *stride = PyArray_STRIDES(arr);
  auto const *dims = PyArray_DIMS(arr);
  long current_stride = PyArray_ITEMSIZE(arr);
  for (size_t i = 0; i < N; i++) {
    if (stride[i] != current_stride)
      return false;
    current_stride *= dims[i];
  }

  return PyArray_FLAGS(arr) & NPY_ARRAY_F_CONTIGUOUS && N > 1;
}

template <typename E>
types::numpy_texpr<E> from_python<types::numpy_texpr<E>>::convert(PyObject *obj)
{
  constexpr size_t N = E::value;
  using T = typename E::dtype;
  PyArrayObject *arr = reinterpret_cast<PyArrayObject *>(obj);
  typename E::shape_t shape;
  auto const *dims = PyArray_DIMS(arr);
  static_assert(N == 2, "only support texpr of matrices");
  sutils::assign(std::get<0>(shape), std::get<1>(dims));
  sutils::assign(std::get<1>(shape), std::get<0>(dims));

  PyObject *tobj = PyArray_Transpose(arr, nullptr);
  types::ndarray<T, typename E::shape_t> base_array((T *)PyArray_BYTES(arr),
                                                    shape, tobj);
  types::numpy_texpr<types::ndarray<T, typename E::shape_t>> r(base_array);
  return r;
}
PYTHONIC_NS_END

#endif

#endif
