#ifndef NALL_ARRAY_HPP
#define NALL_ARRAY_HPP

#include <stdlib.h>
#include <algorithm>
#include <initializer_list>
#include <type_traits>
#include <utility>
#include <nall/algorithm.hpp>
#include <nall/bit.hpp>
#include <nall/utility.hpp>

namespace nall {
  //dynamic vector array
  //neither constructor nor destructor is ever invoked;
  //thus, this should only be used for POD objects.
  template<typename T> class array {
  protected:
    T *pool;
    unsigned poolsize, buffersize;

  public:
    unsigned size() const { return buffersize; }
    unsigned capacity() const { return poolsize; }

    void reset() {
      if(pool) free(pool);
      pool = nullptr;
      poolsize = 0;
      buffersize = 0;
    }

    void reserve(unsigned newsize) {
      if(newsize == poolsize) return;

      pool = (T*)realloc(pool, newsize * sizeof(T));
      poolsize = newsize;
      buffersize = min(buffersize, newsize);
    }

    void resize(unsigned newsize) {
      if(newsize > poolsize) reserve(bit::round(newsize));  //round reserve size up to power of 2
      buffersize = newsize;
    }

    T* get(unsigned minsize = 0) {
      if(minsize > buffersize) resize(minsize);
      if(minsize > buffersize) throw "array[] out of bounds";
      return pool;
    }

    void append(const T data) {
      operator[](buffersize) = data;
    }

    void append(const T data[], unsigned length) {
      for(unsigned n = 0; n < length; n++) operator[](buffersize) = data[n];
    }

    void remove() {
      if(size > 0) resize(size - 1);  //remove last element only
    }

    void remove(unsigned index, unsigned count = 1) {
      for(unsigned i = index; count + i < buffersize; i++) {
        pool[i] = pool[count + i];
      }
      if(count + index >= buffersize) resize(index);  //every element >= index was removed
      else resize(buffersize - count);
    }

    optional<unsigned> find(const T data) {
      for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return { true, i };
      return { false, 0 };
    }

    void clear() {
      memset(pool, 0, buffersize * sizeof(T));
    }

    array() : pool(nullptr), poolsize(0), buffersize(0) {
    }

    array(std::initializer_list<T> list) : pool(nullptr), poolsize(0), buffersize(0) {
      for(const T *p = list.begin(); p != list.end(); ++p) append(*p);
    }

    ~array() {
      reset();
    }

    //copy
    array& operator=(const array &source) {
      if(pool) free(pool);
      buffersize = source.buffersize;
      poolsize = source.poolsize;
      pool = (T*)malloc(sizeof(T) * poolsize);            //allocate entire pool size,
      memcpy(pool, source.pool, sizeof(T) * buffersize);  //... but only copy used pool objects
      return *this;
    }

    array(const array &source) : pool(nullptr), poolsize(0), buffersize(0) {
      operator=(source);
    }

    //move
    array& operator=(array &&source) {
      if(pool) free(pool);
      pool = source.pool;
      poolsize = source.poolsize;
      buffersize = source.buffersize;
      source.pool = nullptr;
      source.reset();
      return *this;
    }

    array(array &&source) : pool(nullptr), poolsize(0), buffersize(0) {
      operator=(std::move(source));
    }

    //access
    inline T& operator[](unsigned position) {
      if(position >= buffersize) resize(position + 1);
      if(position >= buffersize) throw "array[] out of bounds";
      return pool[position];
    }

    inline const T& operator[](unsigned position) const {
      if(position >= buffersize) throw "array[] out of bounds";
      return pool[position];
    }

    inline const T& operator()(unsigned position, const T& data) {
      if(position >= buffersize) return data;
      return pool[position];
    }

    //iteration
    T* begin() { return &pool[0]; }
    T* end() { return &pool[buffersize]; }
    const T* begin() const { return &pool[0]; }
    const T* end() const { return &pool[buffersize]; }
  };
}

#endif
