/*
  Copyright(C) 2011-2016 Brazil

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#pragma once

#include "dat.hpp"

#include <new>

namespace grn {
namespace dat {

template <typename T>
class GRN_DAT_API Vector {
 public:
  Vector() : buf_(NULL), size_(0), capacity_(0) {}
  ~Vector() {
    for (UInt32 i = 0; i < size(); ++i) {
      buf_[i].~T();
    }
    delete [] reinterpret_cast<char *>(buf_);
  }

  const T &operator[](UInt32 i) const {
    GRN_DAT_DEBUG_THROW_IF(i >= size());
    return buf_[i];
  }
  T &operator[](UInt32 i) {
    GRN_DAT_DEBUG_THROW_IF(i >= size());
    return buf_[i];
  }

  const T &front() const {
    GRN_DAT_DEBUG_THROW_IF(empty());
    return buf_[0];
  }
  T &front() {
    GRN_DAT_DEBUG_THROW_IF(empty());
    return buf_[0];
  }

  const T &back() const {
    GRN_DAT_DEBUG_THROW_IF(empty());
    return buf_[size() - 1];
  }
  T &back() {
    GRN_DAT_DEBUG_THROW_IF(empty());
    return buf_[size() - 1];
  }

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

  const T *end() const {
    return buf_ + size_;
  }
  T *end() {
    return buf_ + size_;
  }

  void push_back() {
    reserve(size() + 1);
    new (&buf_[size()]) T;
    ++size_;
  }
  void push_back(const T &x) {
    reserve(size() + 1);
    new (&buf_[size()]) T(x);
    ++size_;
  }

  void pop_back() {
    GRN_DAT_DEBUG_THROW_IF(empty());
    back().~T();
    --size_;
  }

  void clear() {
    resize(0);
  }

  void resize(UInt32 new_size) {
    if (new_size > capacity()) {
      reserve(new_size);
    }
    for (UInt32 i = size(); i < new_size; ++i) {
      new (&buf_[i]) T;
    }
    for (UInt32 i = new_size; i < size(); ++i) {
      buf_[i].~T();
    }
    size_ = new_size;
  }
  template <typename U>
  void resize(UInt32 new_size, const U &value) {
    if (new_size > capacity()) {
      reserve(new_size);
    }
    for (UInt32 i = size(); i < new_size; ++i) {
      new (&buf_[i]) T(value);
    }
    for (UInt32 i = new_size; i < size(); ++i) {
      buf_[i].~T();
    }
    size_ = new_size;
  }

  void reserve(UInt32 new_capacity) {
    if (new_capacity <= capacity()) {
      return;
    } else if ((new_capacity / 2) < capacity()) {
      if (capacity() < (MAX_UINT32 / 2)) {
        new_capacity = capacity() * 2;
      } else {
        new_capacity = MAX_UINT32;
      }
    }

    T *new_buf = reinterpret_cast<T *>(
        new (std::nothrow) char[sizeof(new_capacity) * new_capacity]);
    GRN_DAT_THROW_IF(MEMORY_ERROR, new_buf == NULL);

    for (UInt32 i = 0; i < size(); ++i) {
      new (&new_buf[i]) T(buf_[i]);
    }
    for (UInt32 i = 0; i < size(); ++i) {
      buf_[i].~T();
    }

    T *old_buf = buf_;
    buf_ = new_buf;
    delete [] reinterpret_cast<char *>(old_buf);

    capacity_ = new_capacity;
  }

  void swap(Vector *rhs) {
    T * const temp_buf = buf_;
    buf_ = rhs->buf_;
    rhs->buf_ = temp_buf;

    const UInt32 temp_size = size_;
    size_ = rhs->size_;
    rhs->size_ = temp_size;

    const UInt32 temp_capacity = capacity_;
    capacity_ = rhs->capacity_;
    rhs->capacity_ = temp_capacity;
  }

  bool empty() const {
    return size_ == 0;
  }
  UInt32 size() const {
    return size_;
  }
  UInt32 capacity() const {
    return capacity_;
  }

 private:
  T *buf_;
  UInt32 size_;
  UInt32 capacity_;

  // Disallows copy and assignment.
  Vector(const Vector &);
  Vector &operator=(const Vector &);
};

}  // namespace dat
}  // namespace grn
