#ifndef _RSTL_VECTOR
#define _RSTL_VECTOR

#include "types.h"

#include "rstl/iterator.hpp"
#include "rstl/pointer_iterator.hpp"
#include "rstl/rmemory_allocator.hpp"

class CInputStream;

namespace rstl {
// template < typename T, typename Alloc >
// struct allocator_auto_ptr {
//   allocator_auto_ptr(T* ptr, Alloc* alloc) : ptr(ptr) {}
//   ~allocator_auto_ptr() {
//     if (ptr != nullptr) {
//       Alloc::deallocate(ptr);
//       ptr = nullptr;
//     }
//   }

//   T* release() { T* v = ptr; ptr = nullptr; return v; }

// private:
//   T* ptr;
// };

template < typename T, typename Alloc = rmemory_allocator >
class vector {
protected:
  Alloc x0_allocator;
  int x4_count;
  int x8_capacity;
  T* xc_items;

public:
  typedef pointer_iterator< T, vector< T, Alloc >, Alloc > iterator;
  typedef const_pointer_iterator< T, vector< T, Alloc >, Alloc > const_iterator;
  typedef T value_type;

  iterator begin() { return iterator(xc_items); }
  const_iterator begin() const { return const_iterator(xc_items); }
  iterator end() { return iterator(xc_items + x4_count); }
  const_iterator end() const { return const_iterator(xc_items + x4_count); }
  vector(const Alloc& alloc = Alloc())
  : x0_allocator(alloc), x4_count(0), x8_capacity(0), xc_items(nullptr) {}
  vector(int count) : x4_count(0), x8_capacity(0), xc_items(0) { reserve(count); }
  vector(int count, const T& v) : x4_count(count), x8_capacity(count) {
    x0_allocator.allocate(xc_items, x4_count);
    uninitialized_fill_n(xc_items, count, v);
  }
  vector(int count, const T& v, const Alloc& alloc);

  vector(const vector& other) : x4_count(other.x4_count), x8_capacity(other.x8_capacity) {
    if (other.x4_count == 0 && other.x8_capacity == 0) {
      xc_items = nullptr;
    } else {
      x0_allocator.allocate(xc_items, x8_capacity);
      uninitialized_copy_n(other.xc_items, x4_count, xc_items);
    }
  }
  vector(CInputStream& in, const Alloc& alloc = Alloc());
  ~vector() {
    destroy(begin(), end());
    x0_allocator.deallocate(xc_items);
  }

  void reserve(int size);
  void resize(int size, const T& in = T());
  iterator insert(iterator it, const T& value);

  template < typename from_iterator >
  iterator insert(iterator it, from_iterator begin, from_iterator end);

  iterator erase(iterator it);
  iterator erase(iterator first, iterator last);

  void push_back(const T& in) {
    if (x4_count >= x8_capacity) {
      reserve(x8_capacity != 0 ? x8_capacity * 2 : 4);
    }
    rstl::construct(xc_items + x4_count, in);
    ++x4_count;
  }

  vector& operator=(const vector& other);

  void clear() {
    destroy(begin(), end());
    x4_count = 0;
  }

  T* data() { return xc_items; }
  const T* data() const { return xc_items; }
  int size() const { return x4_count; }
  bool empty() const { return x4_count == 0; }
  int capacity() const { return x8_capacity; }
  T& at(int idx) { return xc_items[idx]; }
  const T& at(int idx) const { return xc_items[idx]; }
  T& front() { return at(0); }
  const T& front() const { return at(0); }
  T& back() { return at(x4_count - 1); }
  const T& back() const { return at(x4_count - 1); }
  T& operator[](int idx) { return xc_items[idx]; }
  const T& operator[](int idx) const { return xc_items[idx]; }

protected:
  template < typename In >
  iterator insert_into(iterator at, int n, In in);
};

template < typename T, typename Alloc >
void vector< T, Alloc >::resize(int size, const T& in) {
  if (x4_count != size) {
    if (size > x4_count) {
      reserve(size);
      uninitialized_fill_n(xc_items + x4_count, size - x4_count, in);
    } else {
      destroy(begin() + size, end());
    }
    x4_count = size;
  }
}

template < typename T, typename Alloc >
void vector< T, Alloc >::reserve(int newSize) {
  if (newSize <= x8_capacity) {
    return;
  }
  T* newData;
  x0_allocator.allocate(newData, newSize);
  uninitialized_copy(begin(), end(), newData);
  destroy(xc_items, xc_items + x4_count);
  x0_allocator.deallocate(xc_items);
  xc_items = newData;
  x8_capacity = newSize;
}

template < typename T, typename Alloc >
typename vector< T, Alloc >::iterator vector< T, Alloc >::insert(iterator it, const T& value) {
  typename iterator::difference_type diff = it - begin(); // distance(begin(), it);

  // // TODO: implement
  // const_counting_iterator< T > in(&value, 0);
  // insert_into(it, 1, in);

  return begin() + diff;
}

template < typename T, typename Alloc >
template < typename from_iterator >
typename vector< T, Alloc >::iterator vector< T, Alloc >::insert(iterator it, from_iterator begin,
                                                                 from_iterator end) {
  return insert_into(it, rstl::distance(begin, end), begin);
}

template < typename T, typename Alloc >
template < typename In >
typename vector< T, Alloc >::iterator vector< T, Alloc >::insert_into(iterator at, int n, In in) {
  // TODO: correct
  // An implementation can be found in CAnimationDatabaseGame.o
  int newCount = x4_count + n;
  if (newCount <= x8_capacity) {
    int diffFromAt = at - begin();
    int diff = x4_count - diffFromAt - 1;

    for (int i = diff; 0 <= diff; --i) {
      construct(xc_items + (diffFromAt + i), xc_items[i]);
      destroy(data() + i);
    }

    uninitialized_copy_n(in, n, begin() + diffFromAt);

    x4_count += n;

  } else {
    int newCapacity = x8_capacity != 0 ? x8_capacity * 2 : 4;
    for (; newCapacity < newCount; newCapacity *= 2)
      ;
    T* newData;
    x0_allocator.allocate(newData, newCapacity);

    int diffFromAt = at - begin();
    uninitialized_copy_n(begin(), diffFromAt, newData);
    uninitialized_copy_n(in, n, newData + diffFromAt);
    uninitialized_copy_n(begin() + diffFromAt, x4_count - diffFromAt, newData + diffFromAt + n);
    destroy(xc_items, xc_items + x4_count);
    x0_allocator.deallocate(xc_items);
    xc_items = newData;
    x8_capacity = newCapacity;
    x4_count += n;
  }
}

template < typename T, typename Alloc >
vector< T, Alloc >& vector< T, Alloc >::operator=(const vector< T, Alloc >& other) {
  if (this == &other)
    return *this;
  clear();
  if (other.size() == 0) {
    x0_allocator.deallocate(xc_items);
    x4_count = 0;
    x8_capacity = 0;
    xc_items = nullptr;
  } else {
    reserve(other.size());
    uninitialized_copy(other.data(), other.data() + other.size(), data());
    x4_count = other.x4_count;
  }
  return *this;
}

template < typename T, typename Alloc >
typename vector< T, Alloc >::iterator vector< T, Alloc >::erase(iterator it) {
  return erase(it, it + 1);
}

// TODO nonmatching (CCameraManager::RemoveCinemaCamera)
template < typename T, typename Alloc >
typename vector< T, Alloc >::iterator vector< T, Alloc >::erase(iterator first, iterator last) {
  destroy(first, last);

  iterator start = begin();
  int newCount = start - first;

  iterator moved = start + newCount;
  for (iterator it = last; it != end(); ++moved, ++newCount, ++it) {
    construct(&*moved, *it);
  }
  x4_count = newCount;

  return first;
}

typedef vector< int > unk_vector;
CHECK_SIZEOF(unk_vector, 0x10)
} // namespace rstl

#endif // _RSTL_VECTOR
