#ifndef OPENMC_SHARED_ARRAY_H
#define OPENMC_SHARED_ARRAY_H

//! \file shared_array.h
//! \brief Shared array data structure

#include "openmc/memory.h"

namespace openmc {

//==============================================================================
// Class declarations
//==============================================================================

// This container is an array that is capable of being appended to in an
// thread safe manner by use of atomics. It only provides protection for the
// use cases currently present in OpenMC. Namely, it covers the scenario where
// multiple threads are appending to an array, but no threads are reading from
// or operating on it in any other way at the same time. Multiple threads can
// call the thread_safe_append() function concurrently and store data to the
// object at the index returned from thread_safe_append() safely, but no other
// operations are protected.
template<typename T>
class SharedArray {

public:
  //==========================================================================
  // Constructors

  //! Default constructor.
  SharedArray() = default;

  //! Construct a zero size container with space to hold capacity number of
  //! elements.
  //
  //! \param capacity The number of elements for the container to allocate
  //! space for
  SharedArray(int64_t capacity) : capacity_(capacity)
  {
    data_ = make_unique<T[]>(capacity);
  }

  //==========================================================================
  // Methods and Accessors

  //! Return a reference to the element at specified location i. No bounds
  //! checking is performed.
  T& operator[](int64_t i) { return data_[i]; }
  const T& operator[](int64_t i) const { return data_[i]; }

  //! Allocate space in the container for the specified number of elements.
  //! reserve() does not change the size of the container.
  //
  //! \param capacity The number of elements to allocate in the container
  void reserve(int64_t capacity)
  {
    data_ = make_unique<T[]>(capacity);
    capacity_ = capacity;
  }

  //! Increase the size of the container by one and append value to the
  //! array. Returns an index to the element of the array written to. Also
  //! tests to enforce that the append operation does not read off the end
  //! of the array. In the event that this does happen, set the size to be
  //! equal to the capacity and return -1.
  //
  //! \value The value of the element to append
  //! \return The index in the array written to. In the event that this
  //! index would be greater than what was allocated for the container,
  //! return -1.
  int64_t thread_safe_append(const T& value)
  {
    // Atomically capture the index we want to write to
    int64_t idx;
#pragma omp atomic capture seq_cst
    idx = size_++;

    // Check that we haven't written off the end of the array
    if (idx >= capacity_) {
#pragma omp atomic write seq_cst
      size_ = capacity_;
      return -1;
    }

    // Copy element value to the array
    data_[idx] = value;

    return idx;
  }

  //! Free any space that was allocated for the container. Set the
  //! container's size and capacity to 0.
  void clear()
  {
    data_.reset();
    size_ = 0;
    capacity_ = 0;
  }

  //! Return the number of elements in the container
  int64_t size() { return size_; }

  //! Resize the container to contain a specified number of elements. This is
  //! useful in cases where the container is written to in a non-thread safe
  //! manner, where the internal size of the array needs to be manually updated.
  //
  //! \param size The new size of the container
  void resize(int64_t size) { size_ = size; }

  //! Return whether the array is full
  bool full() const { return size_ == capacity_; }

  //! Return the number of elements that the container has currently allocated
  //! space for.
  int64_t capacity() { return capacity_; }

  //! Return pointer to the underlying array serving as element storage.
  T* data() { return data_.get(); }
  const T* data() const { return data_.get(); }

  //! Classic iterators
  T* begin() { return data_.get(); }
  const T* cbegin() const { return data_.get(); }
  T* end() { return data_.get() + size_; }
  const T* cend() const { return data_.get() + size_; }

private:
  //==========================================================================
  // Data members

  unique_ptr<T[]> data_; //!< An RAII handle to the elements
  int64_t size_ {0};     //!< The current number of elements
  int64_t capacity_ {0}; //!< The total space allocated for elements
};

} // namespace openmc

#endif // OPENMC_SHARED_ARRAY_H
