#pragma once

#include <std/fwd/vector.hpp>

#include <memory>

namespace std {

  template <class T, class Allocator>
  class vector {

  public:
    using size_type = size_t;
    using value_type = T;
    using reference = value_type &;
    using const_reference = const value_type &;

  private:
    Allocator _alloc;
    size_type _capacity;
    size_type _size;
    T *_data;

    constexpr void expand_capacity_to(size_type at_least) {

      size_type new_capacity = _capacity == 0 ? 1 : _capacity;
      while (new_capacity < at_least)
        new_capacity *= 2;
      if (new_capacity == _capacity)
        return;

      T *new_data = _alloc.allocate(new_capacity);
      for (size_type i = 0; i < _size; ++i) {
        std::construct_at(new_data + i, std::move(_data[i]));
        std::destroy_at(_data + i);
      }
      if (_capacity > 0)
        _alloc.deallocate(_data, _capacity);

      _capacity = new_capacity;
      _data = new_data;

    }

  public:
    constexpr vector() noexcept(noexcept(Allocator()))
    : _alloc(), _capacity(0), _size(0), _data(0) {}

    constexpr vector(const vector &other)
    : _alloc(), _capacity(other._size), _size(other._size),
      _data(_alloc.allocate(_capacity)) {
      for (size_type i = 0; i < _size; ++i)
        std::construct_at(_data + i, other._data[i]);
    }

    constexpr vector(vector &&other) noexcept
    : _alloc(std::move(other._alloc)), _capacity(other._capacity),
      _size(other._size), _data(other._data) {
      other._capacity = 0;
      other._size = 0;
    }

    constexpr vector(
      std::initializer_list<T> init, const Allocator &alloc = Allocator())
    : _alloc(alloc), _capacity(init.size()),
      _size(init.size()), _data(_alloc.allocate(_capacity)) {
      for (size_type i = 0; i < _size; ++i)
        std::construct_at(_data + i, init.begin()[i]);
    }

    explicit vector(size_type count)
    : _alloc(), _capacity(count), _size(count),
      _data(_alloc.allocate(_capacity)) {
      for (size_type i = 0; i < _size; ++i)
        std::construct_at(_data + i);
    }

    constexpr ~vector() {
      if (_capacity > 0) {
        for (size_type i = 0; i < _size; ++i)
          std::destroy_at(_data + i);
        _alloc.deallocate(_data, _capacity);
      }
    }

    constexpr vector &operator =(const vector &other) {

      if (&other == this)
        return *this;

      expand_capacity_to(other._size);

      for (size_type i = 0; i < _size; ++i)
        std::destroy_at(_data + i);

      for (size_type i = 0; i < other._size; ++i) {
        std::construct_at(_data + i, std::move(other._data[i]));
        std::destroy_at(other._data + i);
      }

      _size = other._size;
      return *this;

    }

    constexpr vector &operator =(vector &&other)
      noexcept(
        std::allocator_traits<Allocator>::
          propagate_on_container_move_assignment::value ||
        std::allocator_traits<Allocator>::
          is_always_equal::value) {

      if (&other == this)
        return *this;

      if (_capacity > 0) {
        for (size_type i = 0; i < _size; ++i)
          std::destroy_at(_data + i);
        _alloc.deallocate(_data, _capacity);
      }

      _alloc = std::move(other._alloc);
      _capacity = other._capacity;
      _size = other._size;
      _data = other._data;

      other._capacity = 0;
      other._size = 0;

      return *this;

    }

    void clear() {
      for (size_type i = 0; i < _size; ++i)
        std::destroy_at(_data + i);
      _size = 0;
    }

    constexpr size_type size() const noexcept {
      return _size;
    }

    constexpr T *data() noexcept {
      return _data;
    }

    constexpr const T *data() const noexcept {
      return _data;
    }

    constexpr reference operator[](size_type pos) {
      return _data[pos];
    }

    constexpr const_reference operator[](size_type pos) const {
      return _data[pos];
    }

    constexpr reference back() {
      return _data[_size - 1];
    }

    constexpr const_reference back() const {
      return _data[_size - 1];
    }

    constexpr void resize(size_type count) {

      if (count < _size) {
        for (size_type i = count; i < _size; ++i)
          std::destroy_at(_data + i);
        _size = count;
      }

      else if (count > _size) {
        expand_capacity_to(count);
        for (size_type i = _size; i < count; ++i)
          std::construct_at(_data + i);
        _size = count;
      }

    }

    constexpr void push_back(const T &value) {
      expand_capacity_to(_size + 1);
      std::construct_at(_data + _size, value);
      ++_size;
    }

    constexpr void push_back(T &&value) {
      expand_capacity_to(_size + 1);
      std::construct_at(_data + _size, std::move(value));
      ++_size;
    }

    using iterator = T *;
    using const_iterator = const T *;

    iterator begin() noexcept { return _data; }
    iterator   end() noexcept { return _data + _size; }

    const_iterator begin() const noexcept { return _data; }
    const_iterator   end() const noexcept { return _data + _size; }

    const_iterator cbegin() const noexcept { return _data; }
    const_iterator   cend() const noexcept { return _data + _size; }

  };

}
