#ifndef VTR_VECTOR
#define VTR_VECTOR
#include <vector>
#include <cstddef>
#include <iterator>
#include <algorithm>
#include <tuple>

#include "vtr_range.h"

namespace vtr {

/**
 * @brief A std::vector container which is indexed by K (instead of size_t).
 *
 * The main use of this container is to behave like a std::vector which is
 * indexed by a vtr::StrongId. It assumes that K is explicitly convertable to size_t
 * (i.e. via operator size_t()), and can be explicitly constructed from a size_t.
 *
 * It includes all the following std::vector functions:
 *      - begin
 *      - cbegin
 *      - cend
 *      - crbegin
 *      - crend
 *      - end
 *      - rbegin
 *      - rend
 *      - capacity
 *      - empty
 *      - max_size
 *      - reserve
 *      - resize
 *      - shrink_to_fit
 *      - size
 *      - back
 *      - front
 *      - assign
 *      - clear
 *      - emplace
 *      - emplace_back
 *      - erase
 *      - get_allocator
 *      - insert
 *      - pop_back
 *      - push_back
 *
 * If you need more std::map-like (instead of std::vector-like) behaviour see
 * vtr::vector_map.
 */
template<typename K, typename V, typename Allocator = std::allocator<V>>
class vector : private std::vector<V, Allocator> {
    using storage = std::vector<V, Allocator>;

  public:
    typedef K key_type;

    class key_iterator;
    typedef vtr::Range<key_iterator> key_range;

  public:
    //Pass through std::vector's types
    using typename storage::allocator_type;
    using typename storage::const_iterator;
    using typename storage::const_pointer;
    using typename storage::const_reference;
    using typename storage::const_reverse_iterator;
    using typename storage::difference_type;
    using typename storage::iterator;
    using typename storage::pointer;
    using typename storage::reference;
    using typename storage::reverse_iterator;
    using typename storage::size_type;
    using typename storage::value_type;

    //Pass through storagemethods
    using std::vector<V, Allocator>::vector;

    using storage::begin;
    using storage::cbegin;
    using storage::cend;
    using storage::crbegin;
    using storage::crend;
    using storage::end;
    using storage::rbegin;
    using storage::rend;

    using storage::capacity;
    using storage::empty;
    using storage::max_size;
    using storage::reserve;
    using storage::resize;
    using storage::shrink_to_fit;
    using storage::size;

    using storage::back;
    using storage::front;

    using storage::assign;
    using storage::clear;
    using storage::emplace;
    using storage::emplace_back;
    using storage::erase;
    using storage::get_allocator;
    using storage::insert;
    using storage::pop_back;
    using storage::push_back;

    /*
     * We can't using-forward storage::data, as it might not exist
     * in the particular specialization (typically: vector<bool>)
     * causing compiler complains.
     * Instead, implement it as inline forwarding method whose
     * compilation is deferred to when it is actually requested.
     */
    ///@brief Returns a pointer to the vector's data
    inline V* data() { return storage::data(); }
    ///@brief Returns a pointer to the vector's data (immutable)
    inline const V* data() const { return storage::data(); }

    /*
     * Don't include operator[] and at() from std::vector,
     *
     * since we redine them to take key_type instead of size_t
     */
    ///@brief [] operator
    reference operator[](const key_type id) {
        auto i = size_t(id);
        return storage::operator[](i);
    }
    ///@brief [] operator immutable
    const_reference operator[](const key_type id) const {
        auto i = size_t(id);
        return storage::operator[](i);
    }
    ///@brief at() operator
    reference at(const key_type id) {
        auto i = size_t(id);
        return storage::at(i);
    }
    ///@brief at() operator immutable
    const_reference at(const key_type id) const {
        auto i = size_t(id);
        return storage::at(i);
    }

    // We must re-define swap to avoid inaccessible base class errors
    ///@brief swap function
    void swap(vector<K, V, Allocator>& other) {
        std::swap(*this, other);
    }

    ///@brief Returns a range containing the keys
    key_range keys() const {
        return vtr::make_range(key_begin(), key_end());
    }

    /**
     * @brief Provides an iterable object to enumerate the vector.
     *
     * This function allows for easy enumeration, yielding a tuple of (index, element)
     * pairs in each iteration. It is similar in functionality to Python's `enumerate()`.
     * This function can be used in range-based with structured binding to iterate over
     * indices and values at the same time.
     *
     *      vtr::vector<IdType, ValueType> vec;
     *      for (const auto& [idx, value] : vec) {
     *          ...
     *      }
     * It should be noted that value is returned by reference even if "&"
     * does not appear after auto keyword. However, it is recommended to use "&"
     * explicitly to avoid any confusion about value's scope.
     *
     * @ return An iterable wrapper that can be used in a range-based for loop to obtain
     * (index, element) pairs.
     */
    auto pairs() const {
        struct enumerated_iterator {
            key_type i;
            vector::const_iterator iter;

            bool operator!=(const enumerated_iterator& other) const { return iter != other.iter; }
            void operator++() { i = key_type(size_t(i) + 1); iter++; }
            std::tuple<key_type, decltype(*iter)&> operator*() { return std::tie(i, *iter); }
        };

        struct enumerated_wrapper {
            const vector& vec;
            auto begin() { return enumerated_iterator{ key_type(0), vec.begin() }; }
            auto end() { return enumerated_iterator{ key_type(vec.size()), vec.end() }; }
        };

        return enumerated_wrapper{ *this };
    }

  public:
    /**
     * @brief Iterator class which is convertable to the key_type
     *
     * This allows end-users to call the parent class's keys() member
     * to iterate through the keys with a range-based for loop
     */
    class key_iterator {
      public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = std::ptrdiff_t;
        using value_type = key_type;
        using pointer = key_type*;
        using reference = key_type&;

        ///@brief constructor
        key_iterator(value_type init)
            : value_(init) {}

        /*
         * vtr::vector assumes that the key time is convertable to size_t.
         *
         * It also assumes all the underlying IDs are zero-based and contiguous. That means
         * we can just increment the underlying Id to build the next key.
         */
        ///@brief ++ operator
        key_iterator& operator++() {
            value_ = value_type(size_t(value_) + 1);
            return *this;
        }
        ///@brief decrement operator
        key_iterator& operator--() {
            value_ = value_type(size_t(value_) - 1);
            return *this;
        }
        ///@brief dereference operator
        reference operator*() { return value_; }
        ///@brief -> operator
        pointer operator->() { return &value_; }

        ///@brief == operator
        friend bool operator==(const key_iterator& lhs, const key_iterator& rhs) { return lhs.value_ == rhs.value_; }
        ///@brief != operator
        friend bool operator!=(const key_iterator& lhs, const key_iterator& rhs) { return !(lhs == rhs); }

      private:
        value_type value_;
    };

  private:
    key_iterator key_begin() const { return key_iterator(key_type(0)); }
    key_iterator key_end() const { return key_iterator(key_type(size())); }
};
} // namespace vtr
#endif
