/* -----------------------------------------------------------------------------
 * std_vector.i
 *
 * SWIG typemaps for std::vector<T>, D implementation.
 *
 * The D wrapper is made to loosely resemble a tango.util.container.more.Vector
 * and to provide built-in array-like access.
 *
 * If T does define an operator==, then use the SWIG_STD_VECTOR_ENHANCED
 * macro to obtain enhanced functionality (none yet), for example:
 *
 *   SWIG_STD_VECTOR_ENHANCED(SomeNamespace::Klass)
 *   %template(VectKlass) std::vector<SomeNamespace::Klass>;
 *
 * Warning: heavy macro usage in this file. Use swig -E to get a sane view on
 * the real file contents!
 * ----------------------------------------------------------------------------- */

// Warning: Use the typemaps here in the expectation that the macros they are in will change name.

%include <std_common.i>

// MACRO for use within the std::vector class body
%define SWIG_STD_VECTOR_MINIMUM_INTERNAL(CONST_REFERENCE, CTYPE...)

%typemap(dimports) std::vector< CTYPE > %{
static import std.algorithm;
static import std.exception;
static import std.range;
static import std.traits;
%}
%proxycode %{
alias size_t KeyType;
alias $typemap(dtype, CTYPE) ValueType;

this(ValueType[] values...) {
  this();
  reserve(values.length);
  foreach (e; values) {
    this ~= e;
  }
}

struct Range {
  private $typemap(dtype, std::vector< CTYPE >) _outer;
  private size_t _a, _b;

  this($typemap(dtype, std::vector< CTYPE >) data, size_t a, size_t b) {
    _outer = data;
    _a = a;
    _b = b;
  }

  @property bool empty() const {
    assert((cast($typemap(dtype, std::vector< CTYPE >))_outer).length >= _b);
    return _a >= _b;
  }

  @property Range save() {
    return this;
  }

  @property ValueType front() {
    std.exception.enforce(!empty);
    return _outer[_a];
  }

  @property void front(ValueType value) {
    std.exception.enforce(!empty);
    _outer[_a] = std.algorithm.move(value);
  }

  void popFront() {
    std.exception.enforce(!empty);
    ++_a;
  }

  void opIndexAssign(ValueType value, size_t i) {
    i += _a;
    std.exception.enforce(i < _b && _b <= _outer.length);
    _outer[i] = value;
  }

  void opIndexOpAssign(string op)(ValueType value, size_t i) {
    std.exception.enforce(_outer && _a + i < _b && _b <= _outer.length);
    auto element = _outer[i];
    mixin("element "~op~"= value;");
    _outer[i] = element;
  }
}

// TODO: dup?

Range opSlice() {
  return Range(this, 0, length);
}

Range opSlice(size_t a, size_t b) {
  std.exception.enforce(a <= b && b <= length);
  return Range(this, a, b);
}

size_t opDollar() const {
  return length;
}

@property ValueType front() {
  std.exception.enforce(!empty);
  return getElement(0);
}

@property void front(ValueType value) {
  std.exception.enforce(!empty);
  setElement(0, value);
}

@property ValueType back() {
  std.exception.enforce(!empty);
  return getElement(length - 1);
}

@property void back(ValueType value) {
  std.exception.enforce(!empty);
  setElement(length - 1, value);
}

ValueType opIndex(size_t i) {
  return getElement(i);
}

void opIndexAssign(ValueType value, size_t i) {
  setElement(i, value);
}

void opIndexOpAssign(string op)(ValueType value, size_t i) {
  auto element = this[i];
  mixin("element "~op~"= value;");
  this[i] = element;
}

ValueType[] opBinary(string op, Stuff)(Stuff stuff) if (op == "~") {
  ValueType[] result;
  result ~= this[];
  assert(result.length == length);
  result ~= stuff[];
  return result;
}

void opOpAssign(string op, Stuff)(Stuff stuff) if (op == "~") {
  static if (is(typeof(insertBack(stuff)))) {
    insertBack(stuff);
  } else if (is(typeof(insertBack(stuff[])))) {
    insertBack(stuff[]);
  } else {
    static assert(false, "Cannot append " ~ Stuff.stringof ~ " to " ~ typeof(this).stringof);
  }
}

alias size length;

alias remove removeAny;
alias removeAny stableRemoveAny;

size_t insertBack(Stuff)(Stuff stuff)
if (std.traits.isImplicitlyConvertible!(Stuff, ValueType)){
  push_back(stuff);
  return 1;
}
size_t insertBack(Stuff)(Stuff stuff)
if (std.range.isInputRange!Stuff &&
    std.traits.isImplicitlyConvertible!(std.range.ElementType!Stuff, ValueType)) {
  size_t itemCount;
  foreach(item; stuff) {
    insertBack(item);
    ++itemCount;
  }
  return itemCount;
}
alias insertBack insert;

alias pop_back removeBack;
alias pop_back stableRemoveBack;

size_t insertBefore(Stuff)(Range r, Stuff stuff)
if (std.traits.isImplicitlyConvertible!(Stuff, ValueType)) {
  std.exception.enforce(r._outer.swigCPtr == swigCPtr && r._a < length);
  insertAt(r._a, stuff);
  return 1;
}

size_t insertBefore(Stuff)(Range r, Stuff stuff)
if (std.range.isInputRange!Stuff && std.traits.isImplicitlyConvertible!(ElementType!Stuff, ValueType)) {
  std.exception.enforce(r._outer.swigCPtr == swigCPtr && r._a <= length);

  size_t insertCount;
  foreach(i, item; stuff) {
    insertAt(r._a + i, item);
    ++insertCount;
  }

  return insertCount;
}

size_t insertAfter(Stuff)(Range r, Stuff stuff) {
  // TODO: optimize
  immutable offset = r._a + r.length;
  std.exception.enforce(offset <= length);
  auto result = insertBack(stuff);
  std.algorithm.bringToFront(this[offset .. length - result],
    this[length - result .. length]);
  return result;
}

size_t replace(Stuff)(Range r, Stuff stuff)
if (std.range.isInputRange!Stuff &&
    std.traits.isImplicitlyConvertible!(ElementType!Stuff, ValueType)) {
  immutable offset = r._a;
  std.exception.enforce(offset <= length);
  size_t result;
  for (; !stuff.empty; stuff.popFront()) {
    if (r.empty) {
      // append the rest
      return result + insertBack(stuff);
    }
    r.front = stuff.front;
    r.popFront();
    ++result;
  }
  // Remove remaining stuff in r
  remove(r);
  return result;
}

size_t replace(Stuff)(Range r, Stuff stuff)
if (std.traits.isImplicitlyConvertible!(Stuff, ValueType))
{
    if (r.empty)
    {
        insertBefore(r, stuff);
    }
    else
    {
        r.front = stuff;
        r.popFront();
        remove(r);
    }
    return 1;
}

Range linearRemove(Range r) {
  std.exception.enforce(r._a <= r._b && r._b <= length);
  immutable tailLength = length - r._b;
  linearRemove(r._a, r._b);
  return this[length - tailLength .. length];
}
alias remove stableLinearRemove;

int opApply(int delegate(ref $typemap(dtype, CTYPE) value) dg) {
  int result;

  size_t currentSize = size();
  for (size_t i = 0; i < currentSize; ++i) {
    auto value = getElement(i);
    result = dg(value);
    setElement(i, value);
  }
  return result;
}

int opApply(int delegate(ref size_t index, ref $typemap(dtype, CTYPE) value) dg) {
  int result;

  size_t currentSize = size();
  for (size_t i = 0; i < currentSize; ++i) {
    auto value = getElement(i);

    // Workaround for http://d.puremagic.com/issues/show_bug.cgi?id=2443.
    auto index = i;

    result = dg(index, value);
    setElement(i, value);
  }
  return result;
}
%}

  public:
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef CTYPE value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef CONST_REFERENCE const_reference;

    bool empty() const;
    void clear();
    void push_back(CTYPE const& x);
    void pop_back();
    size_type size() const;
    size_type capacity() const;
    void reserve(size_type n) throw (std::length_error);

    vector();
    vector(const vector &other);

    %extend {
      vector(size_type capacity) throw (std::length_error) {
        std::vector< CTYPE >* pv = 0;
        pv = new std::vector< CTYPE >();

        // Might throw std::length_error.
        pv->reserve(capacity);

        return pv;
      }

      const_reference remove() throw (std::out_of_range) {
        if ($self->empty()) {
          throw std::out_of_range("Tried to remove last element from empty vector.");
        }

        std::vector< CTYPE >::const_reference value = $self->back();
        $self->pop_back();
        return value;
      }

      const_reference remove(size_type index) throw (std::out_of_range) {
        if (index >= $self->size()) {
          throw std::out_of_range("Tried to remove element with invalid index.");
        }

        std::vector< CTYPE >::iterator it = $self->begin() + index;
        std::vector< CTYPE >::const_reference value = *it;
        $self->erase(it);
        return value;
      }

      void removeBack(size_type how_many) throw (std::out_of_range) {
        std::vector< CTYPE >::iterator end = $self->end();
        std::vector< CTYPE >::iterator start = end - how_many;
        $self->erase(start, end);
      }

      void linearRemove(size_type start_index, size_type end_index) throw (std::out_of_range) {
        std::vector< CTYPE >::iterator start = $self->begin() + start_index;
        std::vector< CTYPE >::iterator end = $self->begin() + end_index;
        $self->erase(start, end);
      }

      void insertAt(size_type index, CTYPE const& x) throw (std::out_of_range) {
        std::vector< CTYPE >::iterator it = $self->begin() + index;
        $self->insert(it, x);
      }
    }

    // Wrappers for setting/getting items with the possibly thrown exception
    // specified (important for SWIG wrapper generation).
    %extend {
      const_reference getElement(size_type index) throw (std::out_of_range) {
        if ((index < 0) || ($self->size() <= index)) {
          throw std::out_of_range("Tried to get value of element with invalid index.");
        }
        return (*$self)[index];
      }
    }
    // Use CTYPE const& instead of const_reference to work around SWIG code
    // generation issue when using const pointers as vector elements (like
    // std::vector< const int* >).
    %extend {
      void setElement(size_type index, CTYPE const& val) throw (std::out_of_range) {
        if ((index < 0) || ($self->size() <= index)) {
          throw std::out_of_range("Tried to set value of element with invalid index.");
        }
        (*$self)[index] = val;
      }
    }

%dmethodmodifiers std::vector::getElement "private"
%dmethodmodifiers std::vector::setElement "private"
%enddef

// Extra methods added to the collection class if operator== is defined for the class being wrapped
// The class will then implement IList<>, which adds extra functionality
%define SWIG_STD_VECTOR_EXTRA_OP_EQUALS_EQUALS(CTYPE...)
    %extend {
    }
%enddef

// For vararg handling in macros, from swigmacros.swg
#define %arg(X...) X

// Macros for std::vector class specializations/enhancements
%define SWIG_STD_VECTOR_ENHANCED(CTYPE...)
namespace std {
  template<> class vector<CTYPE > {
    SWIG_STD_VECTOR_MINIMUM_INTERNAL(const value_type&, %arg(CTYPE))
    SWIG_STD_VECTOR_EXTRA_OP_EQUALS_EQUALS(CTYPE)
  };
}
%enddef

%{
#include <vector>
#include <stdexcept>
%}

namespace std {
  // primary (unspecialized) class template for std::vector
  // does not require operator== to be defined
  template<class T> class vector {
    SWIG_STD_VECTOR_MINIMUM_INTERNAL(const value_type&, T)
  };
  // specializations for pointers
  template<class T> class vector<T *> {
    SWIG_STD_VECTOR_MINIMUM_INTERNAL(const value_type&, T *)
    SWIG_STD_VECTOR_EXTRA_OP_EQUALS_EQUALS(T *)
  };
  // bool is a bit different in the C++ standard - const_reference in particular
  template<> class vector<bool> {
    SWIG_STD_VECTOR_MINIMUM_INTERNAL(bool, bool)
    SWIG_STD_VECTOR_EXTRA_OP_EQUALS_EQUALS(bool)
  };
}

// template specializations for std::vector
// these provide extra collections methods as operator== is defined
SWIG_STD_VECTOR_ENHANCED(char)
SWIG_STD_VECTOR_ENHANCED(signed char)
SWIG_STD_VECTOR_ENHANCED(unsigned char)
SWIG_STD_VECTOR_ENHANCED(short)
SWIG_STD_VECTOR_ENHANCED(unsigned short)
SWIG_STD_VECTOR_ENHANCED(int)
SWIG_STD_VECTOR_ENHANCED(unsigned int)
SWIG_STD_VECTOR_ENHANCED(long)
SWIG_STD_VECTOR_ENHANCED(unsigned long)
SWIG_STD_VECTOR_ENHANCED(long long)
SWIG_STD_VECTOR_ENHANCED(unsigned long long)
SWIG_STD_VECTOR_ENHANCED(float)
SWIG_STD_VECTOR_ENHANCED(double)
SWIG_STD_VECTOR_ENHANCED(std::string) // also requires a %include <std_string.i>
