/* -----------------------------------------------------------------------------
 * std_list.i
 *
 * SWIG typemaps for std::list.
 * The Java proxy class extends java.util.AbstractSequentialList. The std::list
 * container looks and feels much like a java.util.LinkedList from Java.
 * ----------------------------------------------------------------------------- */

%include <std_common.i>

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

%fragment("SWIG_ListSize", "header", fragment="SWIG_JavaIntFromSize_t") {
SWIGINTERN jint SWIG_ListSize(size_t size) {
  jint sz = SWIG_JavaIntFromSize_t(size);
  if (sz == -1)
    throw std::out_of_range("list size is too large to fit into a Java int");
  return sz;
}
}

%javamethodmodifiers std::list::begin "private";
%javamethodmodifiers std::list::insert "private";
%javamethodmodifiers std::list::doSize "private";
%javamethodmodifiers std::list::doPreviousIndex "private";
%javamethodmodifiers std::list::doNextIndex "private";
%javamethodmodifiers std::list::doHasNext "private";

// Match Java style better:
%rename(Iterator) std::list::iterator;

%nodefaultctor std::list::iterator;

namespace std {
  template <typename T> class list {

%typemap(javabase) std::list<T> "java.util.AbstractSequentialList<$typemap(jboxtype, T)>"
%proxycode %{
  public $javaclassname(java.util.Collection c) {
    this();
    java.util.ListIterator<$typemap(jboxtype, T)> it = listIterator(0);
    // Special case the "copy constructor" here to avoid lots of cross-language calls
    for (java.lang.Object o : c) {
      it.add(($typemap(jboxtype, T))o);
    }
  }

  public int size() {
    return doSize();
  }

  public boolean add($typemap(jboxtype, T) value) {
    addLast(value);
    return true;
  }

  public java.util.ListIterator<$typemap(jboxtype, T)> listIterator(int index) {
    return new java.util.ListIterator<$typemap(jboxtype, T)>() {
      private Iterator pos;
      private Iterator last;

      private java.util.ListIterator<$typemap(jboxtype, T)> init(int index) {
        if (index < 0 || index > $javaclassname.this.size())
          throw new IndexOutOfBoundsException("Index: " + index);
        pos = $javaclassname.this.begin();
	pos = pos.advance_unchecked(index);
        return this;
      }

      public void add($typemap(jboxtype, T) v) {
        // Technically we can invalidate last here, but this makes more sense
        last = $javaclassname.this.insert(pos, v);
      }

      public void set($typemap(jboxtype, T) v) {
        if (null == last) {
          throw new IllegalStateException();
        }
        last.set_unchecked(v);
      }

      public void remove() {
        if (null == last) {
          throw new IllegalStateException();
        }
        $javaclassname.this.remove(last);
        last = null;
      }

      public int previousIndex() {
        return $javaclassname.this.doPreviousIndex(pos);
      }

      public int nextIndex() {
        return $javaclassname.this.doNextIndex(pos);
      }

      public $typemap(jboxtype, T) previous() {
        if (previousIndex() < 0) {
          throw new java.util.NoSuchElementException();
        }
        last = pos;
        pos = pos.previous_unchecked();
        return last.deref_unchecked();
      }

      public $typemap(jboxtype, T) next() {
        if (!hasNext()) {
          throw new java.util.NoSuchElementException();
        }
        last = pos;
        pos = pos.next_unchecked();
        return last.deref_unchecked();
      }

      public boolean hasPrevious() {
        // This call to previousIndex() will be much slower than the hasNext() implementation, but it's simpler like this with C++ forward iterators
        return previousIndex() != -1;
      }

      public boolean hasNext() {
        return $javaclassname.this.doHasNext(pos);
      }
    }.init(index);
  }
%}

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

    /*
     * We'd actually be better off having the nested class *not* be static in the wrapper
     * output, but this doesn't actually remove the $static from the nested class still.
     * (This would allow us to somewhat simplify the implementation of the ListIterator
     * interface and give "natural" semantics to Java users of the C++ iterator)
     */
    //%typemap(javaclassmodifiers) iterator "public class"
    //%typemap(javainterfaces) iterator "java.util.ListIterator<$typemap(jboxtype, T)>"

    struct iterator {
      %extend {
	void set_unchecked(const T &v) {
	  **$self = v;
	}

	iterator next_unchecked() const {
	  std::list<T>::iterator ret = *$self;
	  ++ret;
	  return ret;
	}

	iterator previous_unchecked() const {
	  std::list<T>::iterator ret = *$self;
	  --ret;
	  return ret;
	}

	T deref_unchecked() const {
	  return **$self;
	}

	iterator advance_unchecked(int index) const {
	  std::list<T>::iterator ret = *$self;
	  std::advance(ret, index);
	  return ret;
	}
      }
    };

    list();
    list(const list& other);

    %rename(isEmpty) empty;
    bool empty() const;
    void clear();
    %rename(remove) erase;
    iterator erase(iterator pos);
    %rename(removeLast) pop_back;
    void pop_back();
    %rename(removeFirst) pop_front;
    void pop_front();
    %rename(addLast) push_back;
    void push_back(const T &value);
    %rename(addFirst) push_front;
    void push_front(const T &value);
    iterator begin();
    iterator end();
    iterator insert(iterator pos, const T &value);

    %extend {
      %fragment("SWIG_ListSize");

      list(jint count, const T &value) throw (std::out_of_range) {
        if (count < 0)
          throw std::out_of_range("list count must be positive");
        return new std::list<T>(static_cast<std::list<T>::size_type>(count), value);
      }

      jint doSize() const throw (std::out_of_range) {
        return SWIG_ListSize(self->size());
      }

      jint doPreviousIndex(const iterator &pos) const throw (std::out_of_range) {
        return pos == self->begin() ? -1 : SWIG_ListSize(std::distance(self->begin(), static_cast<std::list<T>::const_iterator>(pos)));
      }

      jint doNextIndex(const iterator &pos) const throw (std::out_of_range) {
        return pos == self->end() ? SWIG_ListSize(self->size()) : SWIG_ListSize(std::distance(self->begin(), static_cast<std::list<T>::const_iterator>(pos)));
      }

      bool doHasNext(const iterator &pos) const {
        return pos != $self->end();
      }
    }
  };
}
