//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Base/Type/OwningVector.h
//! @brief     Defines and implements templated class OwningVector.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#ifdef SWIG
#error no need to expose this header to Swig
#endif // SWIG
#ifndef BORNAGAIN_BASE_TYPE_OWNINGVECTOR_H
#define BORNAGAIN_BASE_TYPE_OWNINGVECTOR_H

#include "Base/Util/Assert.h"
#include <algorithm>
#include <cstddef>
#include <utility>
#include <vector>

//! A vector of unique pointers to objects.
//!
//! Cannot be copied. For a copyable vector of cloneable objects, use CloneableVector.

template <class T> class OwningVector {
public:
    OwningVector() = default;
    //! Constructor that takes over ownership of elements in given vector
    OwningVector(std::vector<T*> v)
    {
        m_v.reserve(v.size());
        for (T* e : v)
            m_v.push_back(e);
    }
    OwningVector(const OwningVector& other) = delete;
    OwningVector(OwningVector&& other) = default;
    ~OwningVector() { clear(); }

    OwningVector& operator=(const OwningVector& other) = delete;
    OwningVector& operator=(OwningVector&& other) = default;

    void reserve(size_t n) { m_v.reserve(n); }
    void push_back(T* e) { m_v.push_back(e); }
    void insert_at(size_t i, T* e) { m_v.insert(m_v.begin() + i, e); }
    void replace_at(size_t i, T* e)
    {
        ASSERT(i < m_v.size());
        delete m_v[i];
        m_v[i] = e;
    }
    void delete_element(const T* e)
    {
        if (!e)
            return;
        for (size_t i = 0; i < m_v.size(); i++)
            if (m_v[i] == e) {
                delete m_v[i];
                m_v.erase(m_v.begin() + i);
                return;
            }
    }
    void delete_at(size_t i)
    {
        if (i >= m_v.size())
            return;
        delete m_v[i];
        m_v.erase(m_v.begin() + i);
        return;
    }
    T* release_at(size_t i)
    {
        if (i >= m_v.size())
            return nullptr;
        T* result = m_v.at(i);
        m_v.erase(m_v.begin() + i);
        return result;
    }
    T* release_back()
    {
        if (m_v.empty())
            return nullptr;
        T* result = back();
        m_v.pop_back();
        return result;
    }
    T* release_front()
    {
        if (m_v.empty())
            return nullptr;
        T* result = m_v.front();
        m_v.erase(m_v.begin());
        return result;
    }
    void swap(size_t fromIndex, size_t toIndex)
    {
        if (fromIndex > toIndex)
            std::rotate(m_v.rend() - fromIndex - 1, m_v.rend() - fromIndex, m_v.rend() - toIndex);
        else
            std::rotate(m_v.begin() + fromIndex, m_v.begin() + fromIndex + 1,
                        m_v.begin() + toIndex + 1);
    }
    void clear()
    {
        for (T* e : *this)
            delete e;
        m_v.clear();
    }

    const T* operator[](size_t i) const { return m_v.operator[](i); }
    T* operator[](size_t i) { return m_v.operator[](i); }
    const T* at(size_t i) const { return m_v.at(i); }
    T* at(size_t i) { return m_v.at(i); }
    const T* front() const { return m_v.front(); }
    T* front() { return m_v.front(); }
    const T* back() const { return m_v.back(); }
    T* back() { return m_v.back(); }

    size_t size() const { return m_v.size(); }
    bool empty() const { return m_v.empty(); }
    int index_of(const T* t) const
    {
        for (size_t i = 0; i < m_v.size(); i++)
            if (m_v[i] == t)
                return int(i);
        return -1;
    }
    const T* at_or(size_t i, T* defolt) const
    {
        if (i < size())
            return m_v.at(i);
        return defolt;
    }

    const std::vector<T*>& shared() const { return m_v; }

    using Iterator = typename std::vector<T*>::iterator; // "typename" can be dropped under C++20
    using ConstIterator = typename std::vector<T*>::const_iterator;

    ConstIterator begin() const { return m_v.cbegin(); }
    ConstIterator end() const { return m_v.cend(); }
    Iterator begin() { return m_v.begin(); }
    Iterator end() { return m_v.end(); }

protected:
    std::vector<T*> m_v;
};

#endif // BORNAGAIN_BASE_TYPE_OWNINGVECTOR_H
