#pragma once

#include "cstddef"
#include "iterator"
#include "tuple"
#include "utility"

#if __cplusplus >= 201703L
#  define __ESBMC_ARRAY_CONSTEXPR_17 constexpr
#else
#  define __ESBMC_ARRAY_CONSTEXPR_17
#endif

namespace std
{
#if __cplusplus >= 201103L
template <typename T, std::size_t N>
struct array
{
  typedef T value_type;
  typedef std::size_t size_type;
  typedef std::ptrdiff_t difference_type;
  typedef value_type &reference;
  typedef const value_type &const_reference;
  typedef value_type *pointer;
  typedef const value_type *const_pointer;
  typedef std::reverse_iterator<pointer> reverse_iterator;
  typedef std::reverse_iterator<const_pointer> const_reverse_iterator;

  value_type elems[N];

  __ESBMC_ARRAY_CONSTEXPR_17 reference operator[](size_type index)
  {
    return elems[index];
  }

  constexpr const_reference operator[](size_type index) const
  {
    return elems[index];
  }

  __ESBMC_ARRAY_CONSTEXPR_17 reference at(size_type index)
  {
    return elems[index];
  }

  constexpr const_reference at(size_type index) const
  {
    return elems[index];
  }

  constexpr size_type size() const noexcept
  {
    return N;
  }

  constexpr bool empty() const noexcept
  {
    return N == 0;
  }

  constexpr size_type max_size() const noexcept
  {
    return N;
  }

  __ESBMC_ARRAY_CONSTEXPR_17 reference front()
  {
    return elems[0];
  }

  constexpr const_reference front() const
  {
    return elems[0];
  }

  __ESBMC_ARRAY_CONSTEXPR_17 reference back()
  {
    return elems[N - 1];
  }

  constexpr const_reference back() const
  {
    return elems[N - 1];
  }

  __ESBMC_ARRAY_CONSTEXPR_17 pointer begin() noexcept
  {
    return &elems[0];
  }

  constexpr const_pointer begin() const noexcept
  {
    return &elems[0];
  }

  __ESBMC_ARRAY_CONSTEXPR_17 pointer end() noexcept
  {
    return &elems[N];
  }

  constexpr const_pointer end() const noexcept
  {
    return &elems[N];
  }

  constexpr const_pointer cbegin() const noexcept
  {
    return &elems[0];
  }

  constexpr const_pointer cend() const noexcept
  {
    return &elems[N];
  }

#  if 0
  reverse_iterator rbegin() noexcept
  {
    return std::make_reverse_iterator(end());
  }

  const_reverse_iterator rbegin() const noexcept
  {
    return std::make_reverse_iterator(end());
  }

  reverse_iterator rend() noexcept
  {
    return std::make_reverse_iterator(begin());
  }

  const_reverse_iterator rend() const noexcept
  {
    return std::make_reverse_iterator(begin());
  }

  const_reverse_iterator crbegin() const noexcept
  {
    return std::make_reverse_iterator(cend());
  }

  const_reverse_iterator crend() const noexcept
  {
    return std::make_reverse_iterator(cbegin());
  }
#  endif

  __ESBMC_ARRAY_CONSTEXPR_17 void fill(const_reference value)
  {
    for (size_type i = 0; i < N; ++i)
      elems[i] = value;
  }

  __ESBMC_ARRAY_CONSTEXPR_17 void swap(array &other) noexcept
  {
    for (size_type i = 0; i < N; ++i)
    {
      value_type temp = elems[i];
      elems[i] = other.elems[i];
      other.elems[i] = temp;
    }
  }

  __ESBMC_ARRAY_CONSTEXPR_17 pointer data() noexcept
  {
    return elems;
  }

  constexpr const_pointer data() const noexcept
  {
    return elems;
  }
};

template <typename T, std::size_t N>
__ESBMC_ARRAY_CONSTEXPR_17 void swap(array<T, N> &a, array<T, N> &b) noexcept
{
  a.swap(b);
}

template <typename T, std::size_t N>
constexpr bool operator==(const array<T, N> &a, const array<T, N> &b)
{
  for (std::size_t i = 0; i < N; ++i)
  {
    if (!(a[i] == b[i]))
      return false;
  }
  return true;
}

template <typename T, std::size_t N>
constexpr bool operator!=(const array<T, N> &a, const array<T, N> &b)
{
  return !(a == b);
}

template <typename T, std::size_t N>
constexpr bool operator<(const array<T, N> &a, const array<T, N> &b)
{
  for (std::size_t i = 0; i < N; ++i)
  {
    if (a[i] < b[i])
      return true;
    if (b[i] < a[i])
      return false;
  }
  return false;
}

template <typename T, std::size_t N>
constexpr bool operator<=(const array<T, N> &a, const array<T, N> &b)
{
  return !(b < a);
}

template <typename T, std::size_t N>
constexpr bool operator>(const array<T, N> &a, const array<T, N> &b)
{
  return b < a;
}

template <typename T, std::size_t N>
constexpr bool operator>=(const array<T, N> &a, const array<T, N> &b)
{
  return !(a < b);
}

template <std::size_t I, typename T, std::size_t N>
constexpr T &get(array<T, N> &a) noexcept
{
  return a.elems[I];
}

template <std::size_t I, typename T, std::size_t N>
constexpr const T &get(const array<T, N> &a) noexcept
{
  return a.elems[I];
}

template <std::size_t I, typename T, std::size_t N>
constexpr T &&get(array<T, N> &&a) noexcept
{
  return std::move(a.elems[I]);
}

template <typename T, std::size_t N>
struct std::tuple_size<array<T, N>> : std::integral_constant<std::size_t, N>
{
};

template <std::size_t I, typename T, std::size_t N>
struct std::tuple_element<I, array<T, N>>
{
  typedef T type;
};

#endif

} // namespace std
