/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef CHRE_UTIL_DYNAMIC_VECTOR_H_
#define CHRE_UTIL_DYNAMIC_VECTOR_H_

#include <cstddef>
#include <type_traits>

#include "chre/util/default_allocator_provider.h"
#include "chre/util/dynamic_vector_base.h"

namespace chre {

/**
 * A container for storing a sequential array of elements. This container
 * resizes dynamically using heap allocations.
 *
 * @param AllocatorProviderT The allocator provider to use for allocation.
 * Must implement these functions:
 *   - void* allocate(size_t size) - allocates memory of the given size
 *   - template <typename T> T* allocateAlignedArray<T>(size_t size) - allocates
 *     memory of suitable alignment to hold an array of the given object type,
 *     which may exceed alignment of std::max_align_t and therefore excludes the
 *     use of allocate().
 *   - void deallocate(void *ptr) - deallocates the memory at the given address.
 * @param ElementType The type of elements to store in the vector.
 */
template <typename ElementType,
          typename AllocatorProviderT = DefaultAllocatorProvider>
class DynamicVector : protected AllocatorProviderT, private DynamicVectorBase {
 public:
  /**
   * Random-access iterator that points to some element in the container.
   */
  typedef ElementType *iterator;
  typedef const ElementType *const_iterator;
  typedef ElementType value_type;
  typedef size_t size_type;

  DynamicVector() = default;

  /**
   * Move-constructs a dynamic vector from another. The other dynamic vector is
   * left in an empty state.
   *
   * @param other The other vector to move from.
   */
  DynamicVector(DynamicVector<ElementType, AllocatorProviderT> &&other);

  /**
   * Move assigns a dynamic vector from another. The other dynamic vector is
   * left in an empty state.
   */
  DynamicVector &operator=(
      DynamicVector<ElementType, AllocatorProviderT> &&other);

  /**
   * Destructs the objects and releases the memory owned by the vector.
   */
  ~DynamicVector();

  /**
   * Removes all elements from the vector, but does not change the capacity.
   * All iterators and references are invalidated.
   */
  void clear();

  /**
   * Returns a pointer to the underlying buffer. Note that this should not be
   * considered to be persistent as the vector will be moved and resized
   * automatically.
   *
   * @return The pointer to the underlying buffer.
   */
  ElementType *data();

  /**
   * Returns a const pointer to the underlying buffer. Note that this should not
   * be considered to be persistent as the vector will be moved and resized
   * automatically.
   *
   * @return The const pointer to the underlying buffer.
   */
  const ElementType *data() const;

  /**
   * Returns the current number of elements in the vector.
   *
   * @return The number of elements in the vector.
   */
  size_type size() const;

  /**
   * Returns the maximum number of elements that can be stored in this vector
   * without a resize operation.
   *
   * @return The capacity of the vector.
   */
  size_type capacity() const;

  /**
   * Determines whether the vector is empty or not.
   *
   * @return true if the vector is empty.
   */
  bool empty() const;

  /**
   * Erases the last element in the vector. Invalid to call on an empty vector.
   *
   * Invalidates any references to back() and end()/cend().
   */
  void pop_back();

  /**
   * Copy- or move-constructs an element onto the back of the vector. If the
   * vector requires a resize and that allocation fails this function will
   * return false. All iterators and references are invalidated if the container
   * has been resized. Otherwise, only the past-the-end iterator is invalidated.
   *
   * @param The element to push onto the vector.
   * @return true if the element was pushed successfully.
   */
  bool push_back(const ElementType &element);
  bool push_back(ElementType &&element);

  /**
   * Constructs an element onto the back of the vector. All iterators and
   * references are invalidated if the container has been resized. Otherwise,
   * only the past-the-end iterator is invalidated.
   *
   * @param The arguments to the constructor
   * @return true if the element is constructed successfully.
   */
  template <typename... Args>
  bool emplace_back(Args &&... args);

  /**
   * Obtains an element of the vector given an index. It is illegal to index
   * this vector out of bounds and the user of the API must check the size()
   * function prior to indexing this vector to ensure that they will not read
   * out of bounds.
   *
   * @param The index of the element.
   * @return The element.
   */
  ElementType &operator[](size_type index);

  /**
   * Obtains a const element of the vector given an index. It is illegal to
   * index this vector out of bounds and the user of the API must check the
   * size() function prior to indexing this vector to ensure that they will not
   * read out of bounds.
   *
   * @param The index of the element.
   * @return The element.
   */
  const ElementType &operator[](size_type index) const;

  /**
   * Compares two vectors for equality. It will compare the vector sizes and
   * return false if those are different; if not, it will compare the contents
   * of the vectors element-by-element. The operator == should be defined and
   * meaningful for the vector's element type.
   *
   * @param Right-hand side vector to compared with.
   * @return true if two vectors are equal, false otherwise.
   */
  bool operator==(
      const DynamicVector<ElementType, AllocatorProviderT> &other) const;

  /**
   * Resizes the vector to a new capacity returning true if allocation was
   * successful. If the new capacity is smaller than the current capacity,
   * the operation is a no-op and true is returned. If a memory allocation
   * fails, the contents of the vector are not modified and false is returned.
   * This is intended to be similar to the reserve function of the std::vector.
   * All iterators and references are invalidated unless the container did not
   * resize.
   *
   * @param newCapacity The new capacity of the vector.
   * @return true if the resize operation was successful.
   */
  bool reserve(size_type newCapacity);

  /**
   * Resizes the vector to a new size. If the new capacity is smaller than the
   * current size, the extraneous objects at the end are destructed. If the new
   * capacity is larger than the current size, the new objects are
   * default-constructed.
   *
   * @param newSize The new size of the vector.
   * @return true if the resize operation was successful.
   */
  bool resize(size_type newSize);

  /**
   * Inserts an element into the vector at a given index. If a resize of the
   * vector is required and the allocation fails, false will be returned. This
   * will shift all vector elements after the given index one position backward
   * in the list. The supplied index must be <= the size of the vector. It is
   * not possible to have a sparse list of items. If the index is > the current
   * size of the vector, false will be returned. All iterators and references
   * to and after the indexed element are invalidated. Iterators and references
   * to before the indexed elements are unaffected if the container did not
   * resize.
   *
   * @param index The index to insert an element at.
   * @param element The element to insert.
   * @return Whether or not the insert operation was successful.
   */
  bool insert(size_type index, const ElementType &element);
  bool insert(size_type index, ElementType &&element);

  /**
   * Removes an element from the vector given an index. All elements after the
   * indexed one are moved forward one position. The destructor is invoked on
   * on the invalid item left at the end of the vector. The index passed in
   * must be less than the size() of the vector. If the index is greater than or
   * equal to the size no operation is performed. All iterators and references
   * to and after the indexed element are invalidated.
   *
   * @param index The index to remove an element at.
   */
  void erase(size_type index);

  /**
   * Searches the vector for an element.
   *
   * @param element The element to comare against.
   * @return The index of the element found. If the return is equal to size()
   *         then the element was not found.
   */
  size_type find(const ElementType &element) const;

  /**
   * Swaps the location of two elements stored in the vector. The indices
   * passed in must be less than the size() of the vector. If the index is
   * greater than or equal to the size, no operation is performed. All
   * iterators and references to these two indexed elements are invalidated.
   *
   * @param index0 The index of the first element
   * @param index1 The index of the second element
   */
  void swap(size_type index0, size_type index1);

  /**
   * Returns a reference to the first element in the vector. It is illegal to
   * call this on an empty vector.
   *
   * @return The first element in the vector.
   */
  ElementType &front();

  /**
   * Returns a const reference to the first element in the vector. It is illegal
   * to call this on an empty vector.
   *
   * @return The first element in the vector.
   */
  const ElementType &front() const;

  /**
   * Returns a reference to the last element in the vector. It is illegal to
   * call this on an empty vector.
   *
   * @return The last element in the vector.
   */
  ElementType &back();

  /**
   * Returns a const reference to the last element in the vector. It is illegal
   * to call this on an empty vector.
   *
   * @return The last element in the vector.
   */
  const ElementType &back() const;

  /**
   * Prepares a vector to push a minimum of one element onto the back. The
   * vector may be resized if required. The capacity of the vector increases
   * with the growth policy of this vector (doubles for each resize for now).
   *
   * @return Whether or not the resize was successful.
   */
  bool prepareForPush();

  /**
   * @return A random-access iterator to the beginning.
   */
  typename DynamicVector<ElementType, AllocatorProviderT>::iterator begin();
  typename DynamicVector<ElementType, AllocatorProviderT>::const_iterator
  begin() const;
  typename DynamicVector<ElementType, AllocatorProviderT>::const_iterator
  cbegin() const;

  /**
   * @return A random-access iterator to the end.
   */
  typename DynamicVector<ElementType, AllocatorProviderT>::iterator end();
  typename DynamicVector<ElementType, AllocatorProviderT>::const_iterator end()
      const;
  typename DynamicVector<ElementType, AllocatorProviderT>::const_iterator cend()
      const;

 private:
  static constexpr bool canUseDynamicVectorBase() {
    return (std::is_trivially_copyable_v<ElementType> &&
            alignof(ElementType) <= alignof(std::max_align_t) &&
            std::is_same_v<AllocatorProviderT, DefaultAllocatorProvider>);
  }

  /**
   * Prepares the vector for insertion - upon successful return, the memory at
   * the given index will be allocated but uninitialized
   */
  bool prepareInsert(size_t index);
};

}  // namespace chre

#include "chre/util/dynamic_vector_impl.h"  // IWYU pragma: export

#endif  // CHRE_UTIL_DYNAMIC_VECTOR_H_
