/** @file
 * @brief Custom vector implementations using small vector optimisation
 */
/* Copyright (C) 2012-2026 Olly Betts
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#ifndef XAPIAN_INCLUDED_SMALLVECTOR_H
#define XAPIAN_INCLUDED_SMALLVECTOR_H

#ifndef PACKAGE
# error config.h must be included first in each C++ source file
#endif

#include <algorithm>
#include <cstddef> // For std::size_t
#include <cstring> // For std::memcpy
#include <type_traits>

namespace Xapian {

/** Suitable for "simple" type T.
 *
 *  T needs to be trivially copyable.
 *
 *  If sizeof(T) > 2 * sizeof(T*) this isn't going to work, and it's not very
 *  useful when sizeof(T) == 2 * sizeof(T*) as you can only store a single
 *  element inline.
 *
 *  Offers optional Copy-On-Write functionality - if COW is true, then copying
 *  a Vec with external data only makes a copy of that data if you attempt
 *  to modify it.  Current COW is only supported for integral types T.
 *
 *  Offers optional unique pointer handling - if UNIQUEPTR is true it acts in
 *  a similar way to std::vector<std::unique_ptr<T>>.
 */
template<typename T,
         bool COW = false,
         bool UNIQUEPTR = false,
         typename = typename std::enable_if_t<
             (std::is_trivially_copyable_v<T> &&
              (!(COW && UNIQUEPTR)) &&
              (!UNIQUEPTR || std::is_pointer_v<T>) &&
              (!COW || std::is_integral_v<T>))>>
class Vec {
    // This gives capacity() if c > INTERNAL_CAPACITY, or size() otherwise.
    std::size_t c = 0;

    static constexpr std::size_t INTERNAL_CAPACITY = 2 * sizeof(T*) / sizeof(T);

    union {
        T v[INTERNAL_CAPACITY];
        struct {
            T* b;
            T* e;
        } p;
    } u;

    // Only useful if !UNIQUEPTR, but can't be accessed without copy().
    struct Vec_to_copy {
        const Vec& ref;
        explicit Vec_to_copy(const Vec& o) : ref(o) {}
    };

  public:
    typedef std::size_t size_type;

    typedef const T* const_iterator;

    typedef T* iterator;

    Vec() { }

    // Prevent inadvertent copying.
    Vec(const Vec&) = delete;

    // Allow moving.
//    Vec(const Vec&&) = default;

    // Only useful if !UNIQUEPTR, but can't be accessed without copy().
    Vec(const Vec_to_copy& o) {
        do_copy_from(o.ref);
    }

    // Prevent inadvertent copying.
    void operator=(const Vec&) = delete;

    // Only useful if !UNIQUEPTR, but can't be accessed without copy().
    void operator=(const Vec_to_copy& o) {
        clear();
        do_copy_from(o.ref);
    }

    template<bool ENABLE = !UNIQUEPTR>
    auto copy() const -> typename std::enable_if_t<ENABLE, Vec_to_copy> {
        return Vec_to_copy(*this);
    }

    Vec(Vec&& o) noexcept : Vec() {
        std::swap(c, o.c);
        if (c) u = o.u;
    }

    void operator=(Vec&& o) {
        clear();
        std::swap(c, o.c);
        if (c) u = o.u;
    }

    explicit Vec(size_type n) {
        reserve(n);
    }

    ~Vec() {
        clear();
    }

    size_type size() const {
        return is_external() ? u.p.e - u.p.b : c;
    }

    size_type capacity() const {
        return is_external() ? c : INTERNAL_CAPACITY;
    }

    bool empty() const {
        return is_external() ? u.p.b == u.p.e : c == 0;
    }

    void reserve(size_type n) {
        if (n > capacity()) {
            do_reserve(n);
        }
    }

    const_iterator cbegin() const {
        return is_external() ? u.p.b : u.v;
    }

    const_iterator cend() const {
        return is_external() ? u.p.e : u.v + c;
    }

    const_iterator begin() const {
        return cbegin();
    }

    const_iterator end() const {
        return cend();
    }

    iterator begin() {
        // FIXME: This is a bit eager - non-const begin() is often invoked when
        // no modification is needed, but doing it lazily is a bit tricky as
        // the pointer will change when we COW.
        if constexpr (COW) {
            if (is_external() && u.p.b[-1] > 0) {
                do_cow();
            }
        }
        return is_external() ? u.p.b : u.v;
    }

    iterator end() {
        if constexpr (COW) {
            if (is_external() && u.p.b[-1] > 0) {
                do_cow();
            }
        }
        return is_external() ? u.p.e : u.v + c;
    }

    void push_back(T elt) {
        auto cap = capacity();
        if (size() == cap) {
            do_reserve(cap * 2);
        }
        if (c >= INTERNAL_CAPACITY) {
            if constexpr (COW) {
                if (u.p.b[-1] > 0)
                    do_cow();
            }
            *(u.p.e++) = elt;
        } else {
            u.v[c++] = elt;
        }
    }

    void pop_back() {
        if (is_external()) {
            if constexpr (COW) {
                if (u.p.b[-1] > 0) {
                    do_cow();
                }
            }
            --u.p.e;
        } else {
            --c;
        }
    }

    void clear() {
        if constexpr (UNIQUEPTR) {
            for (const_iterator i = begin(); i != end(); ++i)
                delete *i;
        }
        if (is_external())
            do_free();
        c = 0;
    }

    void erase(const_iterator it) {
        if constexpr (COW) {
            if (is_external() && u.p.b[-1] > 0) {
                auto i = it - u.p.b;
                do_cow();
                it = u.p.b + i;
            }
        }
        if constexpr (UNIQUEPTR) {
            delete *it;
        }
        T* p = const_cast<T*>(it);
        std::memmove(p, p + 1, (end() - it - 1) * sizeof(T));
        if (is_external()) {
            --u.p.e;
        } else {
            --c;
        }
    }

    void erase(const_iterator b, const_iterator e) {
        auto n_erased = e - b;
        if (n_erased == 0) return;
        if constexpr (COW) {
            if (is_external() && u.p.b[-1] > 0) {
                auto i = b - u.p.b;
                do_cow();
                b = u.p.b + i;
                e = b + n_erased;
            }
        }
        if constexpr (UNIQUEPTR) {
            for (const_iterator i = b; i != e; ++i)
                delete *i;
        }
        std::memmove(const_cast<T*>(b), const_cast<T*>(e),
                     (end() - e) * sizeof(T));
        if (is_external()) {
            u.p.e -= n_erased;
        } else {
            c -= n_erased;
        }
    }

    void insert(const_iterator pos, const T& elt) {
        // Optimised to reduce copying when we need to reallocate.
        T* blk = nullptr;
        auto cap = capacity();
        auto n = size();
        if (n == cap || (COW && is_external() && u.p.b[-1] > 0)) {
            if (n == cap) {
                cap *= 2;
                // Logic error or size_t wrapping.
                if (rare(COW ? cap < c : cap <= c))
                    throw std::bad_alloc();
            }
            blk = new T[cap + COW];
            if constexpr (COW)
                *blk++ = 0;
        }

        auto b = cbegin();
        auto e = cend();
        T* db;
        if (blk) {
            // Copy elements before the insertion point to the new block.
            std::copy(b, pos, blk);
            db = blk;
        } else {
            db = (is_external() ? u.p.b : u.v);
        }
        // Copy elements after the insertion point.
        std::copy_backward(pos, e, db + n + 1);

        // Insert the new element.
        db[pos - b] = elt;

        if (blk) {
            if (is_external()) {
                do_free();
            }
            u.p.b = blk;
            u.p.e = blk + n + 1;
            c = cap;
        } else if (is_external()) {
            ++u.p.e;
        } else {
            ++c;
        }
    }

    const T& operator[](size_type idx) const {
        return begin()[idx];
    }

    T& operator[](size_type idx) {
        if constexpr (COW) {
            if (is_external() && u.p.b[-1] > 0) {
                do_cow();
            }
        }
        return const_cast<T&>(begin()[idx]);
    }

    const T& front() const {
        return *(begin());
    }

    const T& back() const {
        return end()[-1];
    }

    // Like `v[idx].release()` for `std::vector<std::unique_ptr<T>> v`.
    template<bool ENABLE = !UNIQUEPTR>
    T release_at(size_type idx)
    {
        T r = nullptr;
        std::swap(r, begin()[idx]);
        return r;
    }

  protected:
    void do_free() {
        if constexpr (COW) {
            if (u.p.b[-1] > 0)
                --u.p.b[-1];
            else
                delete [] (u.p.b - 1);
        } else {
            delete [] u.p.b;
        }
    }

    void do_reserve(size_type n) {
        // Logic error or size_t wrapping.
        if (rare(COW ? n < c : n <= c))
            throw std::bad_alloc();
        T* blk = new T[n + COW];
        if constexpr (COW)
            *blk++ = 0;
        if (is_external()) {
            u.p.e = std::copy(u.p.b, u.p.e, blk);
            do_free();
        } else {
            u.p.e = std::copy(u.v, u.v + c, blk);
        }
        u.p.b = blk;
        c = n;
    }

    void do_cow() {
        T* blk = new T[c + 1];
        *blk++ = 0;
        u.p.e = std::copy(u.p.b, u.p.e, blk);
        --u.p.b[-1];
        u.p.b = blk;
    }

    void do_copy_from(const Vec& o) {
        c = o.c;
        if (!o.is_external()) {
            if (c) u = o.u;
        } else if constexpr (COW) {
            u = o.u;
            ++u.p.b[-1];
        } else {
            T* blk = new T[c];
            u.p.e = std::copy(o.u.p.b, o.u.p.e, blk);
            u.p.b = blk;
        }
    }

    /// Return true if storage is external to the object.
    bool is_external() const noexcept {
        return c > INTERNAL_CAPACITY;
    }
};

template<typename T>
using VecCOW = Vec<T, true>;

template<typename T>
using VecUniquePtr = Vec<T*, false, true>;

class SmallVector_ {
    std::size_t c = 0;

    static constexpr std::size_t INTERNAL_CAPACITY = 2;

    void * p[INTERNAL_CAPACITY];

  public:
    SmallVector_() { }

    // Prevent inadvertent copying.
    SmallVector_(const SmallVector_&) = delete;

    // Prevent inadvertent copying.
    void operator=(const SmallVector_&) = delete;

    SmallVector_(SmallVector_&& o) noexcept : SmallVector_() {
        std::memcpy(p, o.p, sizeof(p));
        std::swap(c, o.c);
    }

    explicit SmallVector_(std::size_t n) {
        reserve(n);
    }

    std::size_t size() const {
        if (!is_external())
            return c;
        void * const * b = static_cast<void * const *>(p[0]);
        void * const * e = static_cast<void * const *>(p[1]);
        return e - b;
    }

    std::size_t capacity() const {
        return is_external() ? c : INTERNAL_CAPACITY;
    }

    bool empty() const {
        return is_external() ? p[0] == p[1] : c == 0;
    }

    void reserve(std::size_t n) {
        if (n > INTERNAL_CAPACITY && n > c) {
            do_reserve(n);
        }
    }

  protected:
    void do_free();

    void do_reserve(std::size_t n);

    void do_clear() {
        if (is_external())
            do_free();
        c = 0;
    }

    /// Return true if storage is external to the object.
    bool is_external() const {
        return c > INTERNAL_CAPACITY;
    }

    void * const * do_begin() const {
        return is_external() ? static_cast<void * const *>(p[0]) : p;
    }

    void * const * do_end() const {
        return is_external() ? static_cast<void * const *>(p[1]) : p + c;
    }

    void do_push_back(void* elt) {
        std::size_t cap = capacity();
        if (size() == cap) {
            do_reserve(cap * 2);
        }
        if (c >= INTERNAL_CAPACITY) {
            void ** e = static_cast<void **>(p[1]);
            *e++ = elt;
            p[1] = static_cast<void*>(e);
        } else {
            p[c++] = elt;
        }
    }
};

/** Vector of Xapian PIMPL internal objects.
 *
 *  A notable feature is that if the vector holds <= 2 objects, there's no
 *  extra storage - the two internal pointers are held in the two
 *  pointers which otherwise point to the first and just after the last
 *  element.
 *
 *  This means that for the fairly common cases of pair-wise Query operators
 *  and Database objects with one or two subdatabases, we use less space than
 *  std::vector<Xapian::Foo::Internal*> would.
 */
template<typename TI>
class SmallVectorI : public SmallVector_ {
  public:
    typedef std::size_t size_type;

    typedef TI*const* const_iterator;

    // Create an empty SmallVectorI.
    SmallVectorI() : SmallVector_() { }

    // Create an empty SmallVectorI with n elements reserved.
    explicit SmallVectorI(size_type n) : SmallVector_(n) { }

    SmallVectorI(SmallVectorI&& o) noexcept : SmallVector_(o) { }

    ~SmallVectorI() {
        clear();
    }

    const_iterator begin() const {
        return const_iterator(do_begin());
    }

    const_iterator end() const {
        return const_iterator(do_end());
    }

    void clear() {
        for (const_iterator i = begin(); i != end(); ++i)
            if ((*i) && --(*i)->_refs == 0)
                delete *i;

        do_clear();
    }

    void push_back(TI* elt) {
        // Cast away potential const-ness in TI.  We can only try to modify an
        // element after casting to TI*, so this is const-safe overall.
        do_push_back(const_cast<void*>(static_cast<const void*>(elt)));
        if (elt)
            ++elt->_refs;
    }

    TI* operator[](size_type idx) const {
        return begin()[idx];
    }

    TI* front() const {
        return *(begin());
    }

    TI* back() const {
        return end()[-1];
    }
};

/** Vector of Xapian PIMPL objects.
 *
 *  A notable feature is that if the vector holds <= 2 objects, there's no
 *  extra storage - the two internal pointers are held in the two
 *  pointers which otherwise point to the first and just after the last
 *  element.
 *
 *  This means that for the fairly common cases of pair-wise Query operators
 *  and Database objects with one or two subdatabases, we use less space than
 *  std::vector<Xapian::Foo> would.
 */
template<typename T>
class SmallVector : public SmallVectorI<typename T::Internal> {
    typedef SmallVectorI<typename T::Internal> super;

  public:
    typedef std::size_t size_type;

    class const_iterator {
        typename super::const_iterator ptr;

      public:
        const_iterator() : ptr(nullptr) { }

        explicit const_iterator(typename super::const_iterator ptr_)
            : ptr(ptr_) { }

        const_iterator & operator++() { ++ptr; return *this; }

        const_iterator operator++(int) { return const_iterator(ptr++); }

        T operator*() const {
            return T(*ptr);
        }

        T operator[](size_type idx) const {
            return T(ptr[idx]);
        }

        bool operator==(const const_iterator& o) const { return ptr == o.ptr; }

        bool operator!=(const const_iterator& o) const { return !(*this == o); }

        template<typename I, typename = std::enable_if_t<std::is_integral_v<I>>>
        const_iterator operator+(I n) { return const_iterator(ptr + n); }

        template<typename I, typename = std::enable_if_t<std::is_integral_v<I>>>
        const_iterator operator-(I n) { return const_iterator(ptr - n); }
    };

    // Create an empty SmallVector.
    SmallVector() { }

    // Create an empty SmallVector with n elements reserved.
    explicit SmallVector(size_type n) : super(n) { }

    const_iterator begin() const {
        return const_iterator(super::begin());
    }

    const_iterator end() const {
        return const_iterator(super::end());
    }

    using super::push_back;

    void push_back(const T & elt) {
        push_back(elt.internal.get());
    }

    T operator[](size_type idx) const {
        return begin()[idx];
    }

    T front() const {
        return *(begin());
    }

    T back() const {
        return end()[-1];
    }
};

}

#endif // XAPIAN_INCLUDED_SMALLVECTOR_H
