/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Common iterator implementation for array classes e.g. nsTArray.

#ifndef mozilla_ArrayIterator_h
#define mozilla_ArrayIterator_h

#include <iterator>
#include <type_traits>

namespace mozilla {

namespace detail {
template <typename T>
struct AddInnerConst;

template <typename T>
struct AddInnerConst<T&> {
  using Type = const T&;
};

template <typename T>
struct AddInnerConst<T*> {
  using Type = const T*;
};

template <typename T>
using AddInnerConstT = typename AddInnerConst<T>::Type;
}  // namespace detail

// We have implemented a custom iterator class for array rather than using
// raw pointers into the backing storage to improve the safety of C++11-style
// range based iteration in the presence of array mutation, or script execution
// (bug 1299489).
//
// Mutating an array which is being iterated is still wrong, and will either
// cause elements to be missed or firefox to crash, but will not trigger memory
// safety problems due to the release-mode bounds checking found in ElementAt.
//
// Dereferencing this iterator returns type Element. When Element is a reference
// type, this iterator implements the full standard random access iterator spec,
// and can be treated in many ways as though it is a pointer. Otherwise, it is
// just enough to be used in range-based for loop.
template <class Element, class ArrayType>
class ArrayIterator {
 public:
  typedef ArrayType array_type;
  typedef ArrayIterator<Element, ArrayType> iterator_type;
  typedef typename array_type::index_type index_type;
  typedef std::remove_reference_t<Element> value_type;
  typedef ptrdiff_t difference_type;
  typedef value_type* pointer;
  typedef value_type& reference;
  typedef std::random_access_iterator_tag iterator_category;
  typedef ArrayIterator<detail::AddInnerConstT<Element>, ArrayType>
      const_iterator_type;

 private:
  const array_type* mArray;
  index_type mIndex;

 public:
  ArrayIterator() : mArray(nullptr), mIndex(0) {}
  ArrayIterator(const iterator_type& aOther)
      : mArray(aOther.mArray), mIndex(aOther.mIndex) {}
  ArrayIterator(const array_type& aArray, index_type aIndex)
      : mArray(&aArray), mIndex(aIndex) {}

  iterator_type& operator=(const iterator_type& aOther) {
    mArray = aOther.mArray;
    mIndex = aOther.mIndex;
    return *this;
  }

  constexpr operator const_iterator_type() const {
    return mArray ? const_iterator_type{*mArray, mIndex}
                  : const_iterator_type{};
  }

  bool operator==(const iterator_type& aRhs) const {
    return mIndex == aRhs.mIndex;
  }
  bool operator!=(const iterator_type& aRhs) const { return !(*this == aRhs); }
  bool operator<(const iterator_type& aRhs) const {
    return mIndex < aRhs.mIndex;
  }
  bool operator>(const iterator_type& aRhs) const {
    return mIndex > aRhs.mIndex;
  }
  bool operator<=(const iterator_type& aRhs) const {
    return mIndex <= aRhs.mIndex;
  }
  bool operator>=(const iterator_type& aRhs) const {
    return mIndex >= aRhs.mIndex;
  }

  // These operators depend on the release mode bounds checks in
  // ArrayIterator::ElementAt for safety.
  value_type* operator->() const {
    return const_cast<value_type*>(&mArray->ElementAt(mIndex));
  }
  Element operator*() const {
    return const_cast<Element>(mArray->ElementAt(mIndex));
  }

  iterator_type& operator++() {
    ++mIndex;
    return *this;
  }
  iterator_type operator++(int) {
    iterator_type it = *this;
    ++*this;
    return it;
  }
  iterator_type& operator--() {
    --mIndex;
    return *this;
  }
  iterator_type operator--(int) {
    iterator_type it = *this;
    --*this;
    return it;
  }

  iterator_type& operator+=(difference_type aDiff) {
    mIndex += aDiff;
    return *this;
  }
  iterator_type& operator-=(difference_type aDiff) {
    mIndex -= aDiff;
    return *this;
  }

  iterator_type operator+(difference_type aDiff) const {
    iterator_type it = *this;
    it += aDiff;
    return it;
  }
  iterator_type operator-(difference_type aDiff) const {
    iterator_type it = *this;
    it -= aDiff;
    return it;
  }

  difference_type operator-(const iterator_type& aOther) const {
    return static_cast<difference_type>(mIndex) -
           static_cast<difference_type>(aOther.mIndex);
  }

  Element operator[](difference_type aIndex) const {
    return *this->operator+(aIndex);
  }

  constexpr const array_type* GetArray() const { return mArray; }

  constexpr index_type GetIndex() const { return mIndex; }
};

}  // namespace mozilla

#endif  // mozilla_ArrayIterator_h
