#ifndef PYTHONIC_TYPES_LIST_HPP
#define PYTHONIC_TYPES_LIST_HPP

#include "pythonic/include/types/list.hpp"
#include "pythonic/types/nditerator.hpp"

#include "pythonic/builtins/len.hpp"
#include "pythonic/types/slice.hpp"
#include "pythonic/types/tuple.hpp"
#include "pythonic/types/bool.hpp"
#include "pythonic/utils/shared_ref.hpp"
#include "pythonic/utils/reserve.hpp"

#include <cassert>
#include <algorithm>

PYTHONIC_NS_BEGIN

namespace types
{

  /// Sliced list

  // Constructors
  template <class T, class S>
  sliced_list<T, S>::sliced_list()
      : data(utils::no_memory())
  {
  }
  template <class T, class S>
  sliced_list<T, S>::sliced_list(sliced_list<T, S> const &s)
      : data(s.data), slicing(s.slicing)
  {
  }
  template <class T, class S>
  sliced_list<T, S>::sliced_list(list<T> const &other, S const &s)
      : data(other.data), slicing(s.normalize(other.size()))
  {
  }
  template <class T, class S>
  template <class Sn>
  sliced_list<T, S>::sliced_list(utils::shared_ref<container_type> const &other,
                                 Sn const &s)
      : data(other), slicing(s)
  {
  }

  // iterators
  template <class T, class S>
  typename sliced_list<T, S>::iterator sliced_list<T, S>::begin()
  {
    return {*this, 0};
  }
  template <class T, class S>
  typename sliced_list<T, S>::const_iterator sliced_list<T, S>::begin() const
  {
    return {*this, 0};
  }
  template <class T, class S>
  typename sliced_list<T, S>::iterator sliced_list<T, S>::end()
  {
    return {*this, size()};
  }
  template <class T, class S>
  typename sliced_list<T, S>::const_iterator sliced_list<T, S>::end() const
  {
    return {*this, size()};
  }

  // size
  template <class T, class S>
  long sliced_list<T, S>::size() const
  {
    return slicing.size();
  }
  template <class T, class S>
  sliced_list<T, S>::operator bool() const
  {
    return slicing.size();
  }

  // accessor
  template <class T, class S>
  typename sliced_list<T, S>::const_reference
  sliced_list<T, S>::fast(long i) const
  {
    assert(0 <= i && i < size());
    auto const index = slicing.get(i);
    assert(0 <= index && index < data->size());
    return (*data)[index];
  }
  template <class T, class S>
  typename sliced_list<T, S>::const_reference sliced_list<T, S>::
  operator[](long i) const
  {
    assert(i < size());
    auto const index = slicing.get(i);
    assert(0 <= index && index < data->size());
    return (*data)[index];
  }
  template <class T, class S>
  typename sliced_list<T, S>::reference sliced_list<T, S>::operator[](long i)
  {
    assert(i < size());
    auto const index = slicing.get(i);
    assert(0 <= index && index < data->size());
    return (*data)[index];
  }

  template <class T, class S>
  template <class Sp>
  typename std::enable_if<
      is_slice<Sp>::value,
      sliced_list<T, decltype(std::declval<S>() * std::declval<Sp>())>>::type
      sliced_list<T, S>::
      operator[](Sp s) const
  {
    return {data, slicing * s.normalize(this->size())};
  }

  // io
  template <class Tp, class Sp>
  std::ostream &operator<<(std::ostream &os, sliced_list<Tp, Sp> const &v)
  {
    os << '[';
    auto iter = v.begin();
    if (iter != v.end()) {
      while (iter + 1 != v.end()) {
        os << *iter << ", ";
        ++iter;
      }
      os << *iter;
    }
    return os << ']';
  }

  // comparison
  template <class T, class S>
  template <class K>
  bool sliced_list<T, S>::operator==(list<K> const &other) const
  {
    if (size() != other.size())
      return false;
    return std::equal(begin(), end(), other.begin());
  }
  template <class T, class S>
  bool sliced_list<T, S>::operator==(empty_list const &other) const
  {
    return size() == 0;
  }
  template <class T, class S>
  inline sliced_list<T, S> &sliced_list<T, S>::
  operator=(sliced_list<T, S> const &s)
  {
    slicing = s.slicing;
    if (data != s.data)
      data = s.data;
    return *this;
  }
  template <class T, class S>
  sliced_list<T, S> &sliced_list<T, S>::operator=(list<T> const &seq)
  {
    if (slicing.step == 1) {
      data->erase(data->begin() + slicing.lower, data->begin() + slicing.upper);
      data->insert(data->begin() + slicing.lower, seq.begin(), seq.end());
    } else
      assert(!"not implemented yet");
    return *this;
  }
  template <class T, class S>
  list<T> sliced_list<T, S>::operator+(list<T> const &s) const
  {
    list<T> out(size() + s.size());
    std::copy(s.begin(), s.end(), std::copy(begin(), end(), out.begin()));
    return out;
  }
  template <class T, class S>
  template <size_t N, class V>
  list<T> sliced_list<T, S>::operator+(array_base<T, N, V> const &s) const
  {
    list<T> out(size() + s.size());
    std::copy(s.begin(), s.end(), std::copy(begin(), end(), out.begin()));
    return out;
  }
  template <class T, class S>
  template <class Tp, class Sp>
  list<typename __combined<T, Tp>::type> sliced_list<T, S>::
  operator+(sliced_list<Tp, Sp> const &s) const
  {
    list<typename __combined<T, Tp>::type> out(size() + s.size());
    std::copy(s.begin(), s.end(), std::copy(begin(), end(), out.begin()));
    return out;
  }
  template <class N, class T>
  list<T> operator*(N n, list<T> const &l)
  {
    return l * n;
  }
#ifdef USE_XSIMD
  template <class T, class S>
  template <class vectorizer>
  typename sliced_list<T, S>::simd_iterator
      sliced_list<T, S>::vbegin(vectorizer) const
  {
    return {data->data() + slicing.lower};
  }

  template <class T, class S>
  template <class vectorizer>
  typename sliced_list<T, S>::simd_iterator
      sliced_list<T, S>::vend(vectorizer) const
  {
    using vector_type = typename xsimd::simd_type<dtype>;
    static const std::size_t vector_size = vector_type::size;
    return {data->data() + slicing.lower +
            long(size() / vector_size * vector_size)};
  }

#endif

  // other operations
  template <class T, class S>
  template <class V>
  bool sliced_list<T, S>::contains(V const &v) const
  {
    return std::find(data->begin(), data->end(), v) != data->end();
  }
  template <class T, class S>
  intptr_t sliced_list<T, S>::id() const
  {
    // sharing is not implemented for sliced list
    return reinterpret_cast<intptr_t>(this);
  }

  template <class T, class S>
  long sliced_list<T, S>::count(T const &x) const
  {
    return std::count(begin(), end(), x);
  }

  /// List

  // constructors
  template <class T>
  list<T>::list()
      : data(utils::no_memory())
  {
  }
  template <class T>
  template <class InputIterator>
  list<T>::list(InputIterator start, InputIterator stop)
      : data()
  {
    if (std::is_same<
            typename std::iterator_traits<InputIterator>::iterator_category,
            std::random_access_iterator_tag>::value)
      data->reserve(std::distance(start, stop));
    else
      data->reserve(DEFAULT_LIST_CAPACITY);
    std::copy(start, stop, std::back_inserter(*data));
  }
  template <class T>
  list<T>::list(empty_list const &)
      : data(0)
  {
  }
  template <class T>
  list<T>::list(size_type sz)
      : data(sz)
  {
  }
  template <class T>
  list<T>::list(T const &value, single_value, size_type sz)
      : data(sz, value)
  {
  }
  template <class T>
  list<T>::list(std::initializer_list<T> l)
      : data(std::move(l))
  {
  }
  template <class T>
  list<T>::list(list<T> &&other)
      : data(std::move(other.data))
  {
  }
  template <class T>
  list<T>::list(list<T> const &other)
      : data(other.data)
  {
  }
  template <class T>
  template <class F>
  list<T>::list(list<F> const &other)
      : data(other.size())
  {
    std::copy(other.begin(), other.end(), begin());
  }
  template <class T>
  template <class Tp, class S>
  list<T>::list(sliced_list<Tp, S> const &other)
      : data(other.begin(), other.end())
  {
  }

  // operators
  template <class T>
  list<T> &list<T>::operator=(list<T> &&other)
  {
    data = std::move(other.data);
    return *this;
  }
  template <class T>
  template <class F>
  list<T> &list<T>::operator=(list<F> const &other)
  {
    data = utils::shared_ref<container_type>{other.size()};
    std::copy(other.begin(), other.end(), begin());
    return *this;
  }
  template <class T>
  list<T> &list<T>::operator=(list<T> const &other)
  {
    data = other.data;
    return *this;
  }
  template <class T>
  list<T> &list<T>::operator=(empty_list const &)
  {
    data = utils::shared_ref<container_type>();
    return *this;
  }
  template <class T>
  template <class Tp, size_t N, class V>
  list<T> &list<T>::operator=(array_base<Tp, N, V> const &other)
  {
    data = utils::shared_ref<container_type>(other.begin(), other.end());
    return *this;
  }
  template <class T>
  template <class Tp, class S>
  list<T> &list<T>::operator=(sliced_list<Tp, S> const &other)
  {
    if (other.data == data) {
      auto it = std::copy(other.begin(), other.end(), data->begin());
      data->resize(it - data->begin());
    } else
      data = utils::shared_ref<container_type>(other.begin(), other.end());
    return *this;
  }

  template <class T>
  template <class S>
  list<T> &list<T>::operator+=(sliced_list<T, S> const &other)
  {
    data->resize(data->size() + other.size());
    std::copy(other.begin(), other.end(), data->begin());
    return *this;
  }

  template <class T>
  template <class S>
  list<T> list<T>::operator+(sliced_list<T, S> const &other) const
  {
    list<T> new_list(begin(), end());
    new_list.reserve(data->size() + other.size());
    std::copy(other.begin(), other.end(), std::back_inserter(new_list));
    return new_list;
  }

  template <class T>
  template <size_t N, class V>
  list<T> list<T>::operator+(array_base<T, N, V> const &other) const
  {
    list<T> new_list(begin(), end());
    new_list.reserve(data->size() + other.size());
    std::copy(other.begin(), other.end(), std::back_inserter(new_list));
    return new_list;
  }

  // io
  template <class T>
  std::ostream &operator<<(std::ostream &os, list<T> const &v)
  {
    os << '[';
    auto iter = v.begin();
    if (iter != v.end()) {
      while (iter + 1 != v.end())
        os << *iter++ << ", ";
      os << *iter;
    }
    return os << ']';
  }

  // comparison
  template <class T>
  template <class K>
  bool list<T>::operator==(list<K> const &other) const
  {
    if (size() != other.size())
      return false;
    return std::equal(begin(), end(), other.begin());
  }
  template <class T>
  bool list<T>::operator==(empty_list const &) const
  {
    return size() == 0;
  }
  template <class T>
  template <class K>
  bool list<T>::operator!=(list<K> const &other) const
  {
    return !operator==(other);
  }
  template <class T>
  bool list<T>::operator!=(empty_list const &) const
  {
    return size() != 0;
  }

  // iterators
  template <class T>
  typename list<T>::iterator list<T>::begin()
  {
    return data->begin();
  }
  template <class T>
  typename list<T>::const_iterator list<T>::begin() const
  {
    return data->begin();
  }
  template <class T>
  typename list<T>::iterator list<T>::end()
  {
    return data->end();
  }
  template <class T>
  typename list<T>::const_iterator list<T>::end() const
  {
    return data->end();
  }
  template <class T>
  typename list<T>::reverse_iterator list<T>::rbegin()
  {
    return data->rbegin();
  }
  template <class T>
  typename list<T>::const_reverse_iterator list<T>::rbegin() const
  {
    return data->rbegin();
  }
  template <class T>
  typename list<T>::reverse_iterator list<T>::rend()
  {
    return data->rend();
  }
  template <class T>
  typename list<T>::const_reverse_iterator list<T>::rend() const
  {
    return data->rend();
  }

  // comparison
  template <class T>
  bool list<T>::operator<(list<T> const &other) const
  {
    return std::lexicographical_compare(begin(), end(), other.begin(),
                                        other.end());
  }
  template <class T>
  bool list<T>::operator>(list<T> const &other) const
  {
    return std::lexicographical_compare(other.begin(), other.end(), begin(),
                                        end());
  }
  template <class T>
  bool list<T>::operator<=(list<T> const &other) const
  {
    return !(*this > other);
  }
  template <class T>
  bool list<T>::operator>=(list<T> const &other) const
  {
    return !(*this < other);
  }

// element access
#ifdef USE_XSIMD
  template <class T>
  template <class vectorizer>
  typename list<T>::simd_iterator list<T>::vbegin(vectorizer) const
  {
    return {data->data()};
  }

  template <class T>
  template <class vectorizer>
  typename list<T>::simd_iterator list<T>::vend(vectorizer) const
  {
    using vector_type = typename xsimd::simd_type<dtype>;
    static const std::size_t vector_size = vector_type::size;
    return {data->data() + long(size() / vector_size * vector_size)};
  }

#endif
  template <class T>
  typename list<T>::reference list<T>::fast(long n)
  {
    return (*data)[n];
  }
  template <class T>
  typename list<T>::reference list<T>::operator[](long n)
  {
    if (n < 0)
      n += data->size();
    assert(0 <= n && n < data->size());
    return fast(n);
  }
  template <class T>
  typename list<T>::const_reference list<T>::fast(long n) const
  {
    assert(n < data->size());
    return (*data)[n];
  }
  template <class T>
  typename list<T>::const_reference list<T>::operator[](long n) const
  {
    if (n < 0)
      n += data->size();
    assert(0 <= n && n < data->size());
    return fast(n);
  }

  template <class T>
  template <class Sp>
  typename std::enable_if<is_slice<Sp>::value, sliced_list<T, Sp>>::type
      list<T>::
      operator[](Sp const &s) const
  {
    return {*this, s};
  }

  // modifiers
  template <class T>
  template <class Tp>
  void list<T>::push_back(Tp &&x)
  {
    // FIXME: clang-3.4 doesn't support emplace_back for vector of bool
    data->push_back(std::forward<Tp>(x));
  }
  template <class T>
  template <class Tp>
  void list<T>::insert(long i, Tp &&x)
  {
    if (i == size())
      data->emplace_back(std::forward<Tp>(x));
    else
      data->insert(data->begin() + i, std::forward<Tp>(x));
  }
  template <class T>
  void list<T>::reserve(size_t n)
  {
    data->reserve(n);
  }
  template <class T>
  void list<T>::resize(size_t n)
  {
    data->resize(n);
  }
  template <class T>
  typename list<T>::iterator list<T>::erase(size_t n)
  {
    return data->erase(data->begin() + n);
  }
  template <class T>
  T list<T>::pop(long x)
  {
    long sz = size();
    x = x % sz;
    if (x < 0)
      x += sz;
    T res = fast(x);
    erase(x);
    return res;
  }
  // TODO: have to raise a valueError
  template <class T>
  none_type list<T>::remove(T const &x)
  {
    erase(index(x));
    return {};
  }

  // Misc
  template <class T>
  long list<T>::index(T const &x) const
  {
    return std::find(begin(), end(), x) - begin();
  }

  // list interface
  template <class T>
  list<T>::operator bool() const
  {
    return !data->empty();
  }

  template <class T>
  template <class F>
  list<typename __combined<T, F>::type> list<T>::
  operator+(list<F> const &s) const
  {
    list<typename __combined<T, F>::type> clone(data->size() + s.data->size());
    std::copy(s.begin(), s.end(), std::copy(begin(), end(), clone.begin()));
    return clone;
  }

  template <class T>
  template <class F, class S>
  list<decltype(std::declval<T>() +
                std::declval<typename sliced_list<F, S>::value_type>())>
      list<T>::operator+(sliced_list<F, S> const &s) const
  {
    list<decltype(std::declval<T>() +
                  std::declval<typename sliced_list<F, S>::value_type>())>
        clone(data->size() + len(s));
    std::copy(s.begin(), s.end(), std::copy(begin(), end(), clone.begin()));
    return clone;
  }

  template <class T>
  list<T> list<T>::operator+(empty_list const &) const
  {
    return list<T>(begin(), end());
  }

  template <class T>
  list<T> list<T>::operator*(long n) const
  {
    if (size() == 1) {
      return list<T>(fast(0), single_value{}, n);
    } else {
      list<T> r(data->size() * n);
      auto start = r.begin();
      while (start != r.end())
        start = std::copy(this->begin(), this->end(), start);
      return r;
    }
  }
  template <class T>
  list<T> const &list<T>::operator*=(long n)
  {
    if (size() == 1) {
      resize(n);
      std::fill(begin() + 1, end(), fast(0));
    } else {
      auto const initial_size = size();
      resize(n * initial_size);
      // FIXME: could use less calls to std::copy
      auto tgt = begin() + initial_size;
      for (long i = 1; i < n; ++i)
        tgt = std::copy(begin(), begin() + initial_size, tgt);
    }
    return *this;
  }

  template <class T>
  template <class F>
  list<T> &list<T>::operator+=(F const &s)
  {
    reserve(size() + s.size());
    std::copy(s.begin(), s.end(), std::back_inserter(*this));
    return *this;
  }

  template <class T>
  long list<T>::size() const
  {
    return data->size();
  }
  template <class T>
  template <class E>
  long list<T>::_flat_size(E const &e, utils::int_<1>) const
  {
    return std::distance(e.begin(), e.end());
  }
  template <class T>
  template <class E, size_t L>
  long list<T>::_flat_size(E const &e, utils::int_<L>) const
  {
    return std::distance(e.begin(), e.end()) *
           _flat_size(e[0], utils::int_<L - 1>{});
  }
  template <class T>
  long list<T>::flat_size() const
  {
    return _flat_size(*this, utils::int_<value>{});
  }

  template <class T>
  template <class V>
  bool list<T>::contains(V const &v) const
  {
    return std::find(data->begin(), data->end(), v) != data->end();
  }
  template <class T>
  intptr_t list<T>::id() const
  {
    return reinterpret_cast<intptr_t>(&(*data));
  }

  template <class T>
  long list<T>::count(T const &x) const
  {
    return std::count(begin(), end(), x);
  }

  /// Empty list
  template <class T>
  list<T> empty_list::operator+(list<T> const &s) const
  {
    return s;
  }
  template <class T, class S>
  sliced_list<T, S> empty_list::operator+(sliced_list<T, S> const &s) const
  {
    return s;
  }
  template <class T, size_t N, class V>
  static_list<T, N> empty_list::operator+(array_base<T, N, V> const &s) const
  {
    return s.template to_array<list_version>();
  }
  empty_list empty_list::operator+(empty_list const &) const
  {
    return empty_list();
  }
  template <class F>
  typename std::enable_if<!is_numexpr_arg<F>::value,
                          list<typename F::value_type>>::type empty_list::
  operator+(F s) const
  {
    return {s.begin(), s.end()};
  }
  empty_list::operator bool() const
  {
    return false;
  }

  template <class T>
  empty_list::operator list<T>() const
  {
    return list<T>(0);
  }

  constexpr long empty_list::size()
  {
    return 0;
  }

  std::ostream &operator<<(std::ostream &os, empty_list const &)
  {
    return os << "[]";
  }
}

namespace utils
{

  template <class T, class From>
  void reserve(types::list<T> &l, From const &f,
               typename From::const_iterator *)
  {
    l.reserve(builtins::len(f));
  }
}
PYTHONIC_NS_END

/* overload std::get */
namespace std
{
  template <size_t I, class T>
  typename pythonic::types::list<T>::reference get(pythonic::types::list<T> &t)
  {
    return t[I];
  }

  template <size_t I, class T>
  typename pythonic::types::list<T>::const_reference
  get(pythonic::types::list<T> const &t)
  {
    return t[I];
  }

  template <size_t I, class T>
  typename pythonic::types::list<T>::value_type
  get(pythonic::types::list<T> &&t)
  {
    return std::move(t)[I];
  }

  template <size_t I, class T, class S>
  typename pythonic::types::sliced_list<T, S>::reference
  get(pythonic::types::sliced_list<T, S> &t)
  {
    return t[I];
  }

  template <size_t I, class T, class S>
  typename pythonic::types::sliced_list<T, S>::const_reference
  get(pythonic::types::sliced_list<T, S> const &t)
  {
    return t[I];
  }

  template <size_t I, class T, class S>
  typename pythonic::types::sliced_list<T, S>::value_type
  get(pythonic::types::sliced_list<T, S> &&t)
  {
    return std::move(t)[I];
  }
}

#ifdef ENABLE_PYTHON_MODULE

PYTHONIC_NS_BEGIN

PyObject *to_python<typename std::vector<bool>::reference>::convert(
    typename std::vector<bool>::reference const &v)
{
  return ::to_python((bool)v);
}

PyObject *to_python<typename std::conditional<
    std::is_same<bool, typename std::vector<bool>::const_reference>::value,
    phantom_type, typename std::vector<bool>::const_reference>::type>::
    convert(typename std::vector<bool>::const_reference const &v)
{
  return ::to_python((bool)v);
}

template <class T>
PyObject *to_python<types::list<T>>::convert(types::list<T> const &v)
{
  Py_ssize_t n = v.size();
  PyObject *ret = PyList_New(n);
  for (Py_ssize_t i = 0; i < n; i++)
    PyList_SET_ITEM(ret, i, ::to_python(v[i]));
  return ret;
}
template <class T, class S>
PyObject *
to_python<types::sliced_list<T, S>>::convert(types::sliced_list<T, S> const &v)
{
  Py_ssize_t n = v.size();
  PyObject *ret = PyList_New(n);
  for (Py_ssize_t i = 0; i < n; i++)
    PyList_SET_ITEM(ret, i, ::to_python(v[i]));
  return ret;
}

PyObject *to_python<types::empty_list>::convert(types::empty_list const &)
{
  return PyList_New(0);
}

template <class T>
bool from_python<types::list<T>>::is_convertible(PyObject *obj)
{
  return PyList_Check(obj) &&
         (PyObject_Not(obj) ||
          ::is_convertible<T>(PySequence_Fast_GET_ITEM(obj, 0)));
}

template <class T>
types::list<T> from_python<types::list<T>>::convert(PyObject *obj)
{
  Py_ssize_t l = PySequence_Fast_GET_SIZE(obj);
  types::list<T> v(l);

  PyObject **core = PySequence_Fast_ITEMS(obj);
  std::transform(core, core + l, v.begin(),
                 [](PyObject *o) { return ::from_python<T>(o); });

  return v;
}
PYTHONIC_NS_END

#endif

#endif
