text: ///
  /// BSD 3-Clause License
  ///
  /// Copyright (c) 2020, Alexandre Arsenault
  /// All rights reserved.
  ///
  /// Redistribution and use in source and binary forms, with or without
  /// modification, are permitted provided that the following conditions are met:
  ///
  /// * Redistributions of source code must retain the above copyright notice, this
  ///   list of conditions and the following disclaimer.
  ///
  /// * Redistributions in binary form must reproduce the above copyright notice,
  ///   this list of conditions and the following disclaimer in the documentation
  ///   and/or other materials provided with the distribution.
  ///
  /// * Neither the name of the copyright holder nor the names of its
  ///   contributors may be used to endorse or promote products derived from
  ///   this software without specific prior written permission.
  ///
  /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  /// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  /// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  /// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  /// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  /// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  /// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  /// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  /// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  /// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  /// POSSIBILITY OF SUCH DAMAGE.
  ///
  
  #pragma once
  #include "fst/assert.h"
  #include "fst/traits.h"
  #include "fst/meta.h"
  #include <array>
  #include <exception>
  #include <initializer_list>
  #include <type_traits>
  
  namespace fst {
  template <typename _Tp, std::size_t _Size>
  class unordered_array {
  public:
    using value_type = _Tp;
  
  private:
    static constexpr bool has_const_reference = std::is_fundamental<value_type>::value;
  
    template <bool _Dummy, class _D = dependent_type_condition<_Dummy, std::bool_constant<has_const_reference>>>
    using enable_if_has_const_reference = enable_if_same<_Dummy, _D>;
  
  public:
    using array_type = std::array<value_type, _Size>;
    using reference = typename array_type::reference;
    using const_reference = std::conditional_t<has_const_reference, typename array_type::const_reference, value_type>;
    using pointer = typename array_type::pointer;
    using const_pointer = typename array_type::const_pointer;
    using iterator = typename array_type::iterator;
    using const_iterator = typename array_type::const_iterator;
    using reverse_iterator = typename array_type::reverse_iterator;
    using const_reverse_iterator = typename array_type::const_reverse_iterator;
  
    using size_type = typename array_type::size_type;
    using difference_type = typename array_type::difference_type;
  
    static_assert(_Size != 0, "unordered_array size must not be zero.");
  
    inline constexpr unordered_array() noexcept = default;
    inline constexpr unordered_array(const unordered_array&) noexcept = default;
    inline constexpr unordered_array(unordered_array&& ua) noexcept
        : _data(std::move(ua._data))
        , _size(ua._size) {
      ua._size = 0;
    }
  
    inline constexpr unordered_array(std::initializer_list<value_type> list) {
      for (auto n : list) {
        push_back(maybe_move(n));
      }
    }
  
    inline constexpr unordered_array& operator=(const unordered_array&) noexcept = default;
    inline constexpr unordered_array& operator=(unordered_array&& ua) noexcept {
      _data = std::move(ua._data);
      _size = ua._size;
      ua._size = 0;
      return *this;
    }
  
    inline constexpr size_type size() const noexcept { return _size; }
    inline constexpr size_type max_size() const noexcept { return _Size; }
    inline constexpr bool empty() const noexcept { return _size == 0; }
    inline constexpr pointer data() noexcept { return _data.data(); }
    inline constexpr const_pointer data() const noexcept { return _data.data(); }
  
    inline constexpr reference operator[](size_type index) noexcept {
      fst_assert(index < _size, "Try to access out of bounds index.");
      return _data[index];
    }
  
    inline constexpr const_reference operator[](size_type index) const noexcept {
      fst_assert(index < _size, "Try to access out of bounds index.");
      return _data[index];
    }
  
    inline constexpr reference at(size_type index) {
      if (index >= max_size()) {
        throw std::out_of_range("unordered_array::at");
      }
  
      return _data[index];
    }
  
    inline constexpr const_reference at(size_type index) const {
      if (index >= max_size()) {
        throw std::out_of_range("unordered_array::at");
      }
  
      return _data[index];
    }
  
    inline constexpr iterator begin() { return _data.begin(); }
    inline constexpr const_iterator begin() const { return _data.begin(); }
    inline constexpr iterator end() { return _data.begin() + _size; }
    inline constexpr const_iterator end() const { return _data.begin() + _size; }
  
    inline constexpr reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
    inline constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); }
    inline constexpr reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
    inline constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); }
  
    inline constexpr const_iterator cbegin() const noexcept { return begin(); }
    inline constexpr const_iterator cend() const noexcept { return end(); }
    inline constexpr const_reverse_iterator crbegin() const noexcept { return rbegin(); }
    inline constexpr const_reverse_iterator crend() const noexcept { return rend(); }
  
    inline constexpr reference front() noexcept { return _data[0]; }
    inline constexpr const_reference front() const noexcept { return _data[0]; }
    inline constexpr reference back() noexcept { return _data[_size - 1]; }
    inline constexpr const_reference back() const noexcept { return _data[_size - 1]; }
  
    inline constexpr void swap(unordered_array& ua) noexcept {
      _data.swap(ua._data);
      size_type temp_size = _size;
      _size = ua._size;
      ua._size = temp_size;
    }
  
    inline constexpr void clear() {
      if constexpr (!std::is_trivial<value_type>::value) {
        for (size_type i = 0; i < _size; i++) {
          _data[i] = value_type();
        }
      }
  
      _size = 0;
    }
  
    inline constexpr bool is_full() const { return _size == max_size(); }
  
    inline constexpr void push_back(const_reference value) {
      fst_assert(_size != max_size(), "Can't push_back when array is full.");
  
      if (_size == max_size()) {
        return;
      }
  
      _data[_size++] = value;
    }
  
    // Doesn't apply for fundamental types.
    template <bool _Dummy = true, class = enable_if_has_const_reference<_Dummy>>
    inline constexpr void push_back(value_type&& value) {
      fst_assert(_size != max_size(), "Can't push_back when array is full.");
  
      if (_size == max_size()) {
        return;
      }
  
      _data[_size++] = std::move(value);
    }
  
    template <typename... Args>
    inline constexpr void emplace_back(Args&&... args) {
      fst_assert(_size != max_size(), "Can't emplace_back when array is full.");
  
      if (_size == max_size()) {
        return;
      }
  
      _data[_size++] = value_type(std::forward<Args>(args)...);
    }
  
    inline constexpr void pop_back() {
      fst_assert(_size > 0, "Can't pop_back an empty array.");
  
      if (_size == 0) {
        return;
      }
  
      if (_size) {
        --_size;
  
        if constexpr (!std::is_trivial<value_type>::value) {
          _data[_size] = value_type();
        }
      }
    }
  
    inline constexpr void erase(size_type index) {
      fst_assert(index < _size, "Try to erase out of bounds index.");
  
      if (_size == 0) {
        return;
      }
  
      if (index == _size - 1) {
        _size--;
        return;
      }
  
      _data[index] = std::move(_data[_size-- - 1]);
    }
  
    template <class Predicate>
    inline constexpr void erase_if(Predicate predicate) {
      for (size_type i = 0; i < _size;) {
        if (predicate(_data[i])) {
          erase(i);
          continue;
        }
  
        i++;
      }
    }
  
    template <class Predicate>
    inline constexpr void erase_first_if(Predicate predicate) {
      for (size_type i = 0; i < _size; i++) {
        if (predicate(_data[i])) {
          erase(i);
          return;
        }
      }
    }
  
    template <class Operator>
    inline constexpr void operation(Operator oper) {
      for (size_type i = 0; i < _size; i++) {
        oper(_data[i]);
      }
    }
  
    template <class Predicate, class Operator>
    inline constexpr void operation_if(Predicate predicate, Operator oper) {
      for (size_type i = 0; i < _size; i++) {
        if (predicate(_data[i])) {
          oper(_data[i]);
        }
      }
    }
  
    template <class Predicate, class Operator>
    inline constexpr void operation_first_if(Predicate predicate, Operator oper) {
      for (size_type i = 0; i < _size; i++) {
        if (predicate(_data[i])) {
          oper(_data[i]);
          return;
        }
      }
    }
  
  private:
    array_type _data;
    size_type _size = 0;
  };
  } // namespace fst.
  