/* -----------------------------------------------------------------------------
 * std_list.i
 *
 * SWIG typemaps for std::list<T>
 * C# implementation
 * The C# wrapper is made to look and feel like a C# System.Collections.Generic.LinkedList<> collection.
 *
 * Note that IEnumerable<> is implemented in the proxy class which is useful for using LINQ with
 * C++ std::list wrappers. The ICollection<> interface is also implemented to provide enhanced functionality
 * whenever we are confident that the required C++ operator== is available. This is the case for when
 * T is a primitive type or a pointer. If T does define an operator==, then use the SWIG_STD_LIST_ENHANCED
 * macro to obtain this enhanced functionality, for example:
 *
 *   SWIG_STD_LIST_ENHANCED(SomeNamespace::Klass)
 *   %template(ListKlass) std::list<SomeNamespace::Klass>;
 * ----------------------------------------------------------------------------- */

%include <std_common.i>

// MACRO for use within the std::list class body
%define SWIG_STD_LIST_MINIMUM_INTERNAL(CSINTERFACE, CTYPE...)
%typemap(csinterfaces) std::list< CTYPE > "global::System.IDisposable, global::System.Collections.IEnumerable, global::System.Collections.Generic.CSINTERFACE<$typemap(cstype, CTYPE)>\n"

%apply void *VOID_INT_PTR { std::list< CTYPE >::iterator * };

%proxycode %{
  public $csclassname(global::System.Collections.IEnumerable c) : this() {
    if (c == null)
      throw new global::System.ArgumentNullException("c");
    foreach ($typemap(cstype, CTYPE) element in c) {
      this.AddLast(element);
    }
  }

  public bool IsReadOnly {
    get {
      return false;
    }
  }

  public int Count {
    get {
      return (int)size();
    }
  }

  public $csclassnameNode First {
    get {
      if (Count == 0)
        return null;
      return new $csclassnameNode(getFirstIter(), this);
    }
  }

  public $csclassnameNode Last {
    get {
      if (Count == 0)
        return null;
      return new $csclassnameNode(getLastIter(), this);
    }
  }

  public $csclassnameNode AddFirst($typemap(cstype, CTYPE) value) {
    push_front(value);
    return new $csclassnameNode(getFirstIter(), this);
  }

  public void AddFirst($csclassnameNode newNode) {
    ValidateNewNode(newNode);
    if (!newNode.inlist) {
      push_front(newNode.csharpvalue);
      newNode.iter = getFirstIter();
      newNode.inlist = true;
    } else {
      throw new global::System.InvalidOperationException("The " + newNode.GetType().Name + " node already belongs to a " + this.GetType().Name);
    }
  }

  public $csclassnameNode AddLast($typemap(cstype, CTYPE) value) {
    push_back(value);
    return new $csclassnameNode(getLastIter(), this);
  }

  public void AddLast($csclassnameNode newNode) {
    ValidateNewNode(newNode);
    if (!newNode.inlist) {
      push_back(newNode.csharpvalue);
      newNode.iter = getLastIter();
      newNode.inlist = true;
    } else {
      throw new global::System.InvalidOperationException("The " + newNode.GetType().Name + " node already belongs to a " + this.GetType().Name);
    }
  }

  public $csclassnameNode AddBefore($csclassnameNode node, $typemap(cstype, CTYPE) value) {
    return new $csclassnameNode(insertNode(node.iter, value), this);
  }

  public void AddBefore($csclassnameNode node, $csclassnameNode newNode) {
    ValidateNode(node);
    ValidateNewNode(newNode);
    if (!newNode.inlist) {
      newNode.iter = insertNode(node.iter, newNode.csharpvalue);
      newNode.inlist = true;
    } else {
      throw new global::System.InvalidOperationException("The " + newNode.GetType().Name + " node already belongs to a " + this.GetType().Name);
    }
  }

  public $csclassnameNode AddAfter($csclassnameNode node, $typemap(cstype, CTYPE) value) {
    node = node.Next;
    return new $csclassnameNode(insertNode(node.iter, value), this);
  }

  public void AddAfter($csclassnameNode node, $csclassnameNode newNode) {
    ValidateNode(node);
    ValidateNewNode(newNode);
    if (!newNode.inlist) {
      if (node == this.Last)
        AddLast(newNode);
      else
      {
        node = node.Next;
        newNode.iter = insertNode(node.iter, newNode.csharpvalue);
        newNode.inlist = true;
      }
    } else {
      throw new global::System.InvalidOperationException("The " + newNode.GetType().Name + " node already belongs to a " + this.GetType().Name);
    }
  }

  public void Add($typemap(cstype, CTYPE) value) {
    AddLast(value);
  }

  public void Remove($csclassnameNode node) {
    ValidateNode(node);
    eraseIter(node.iter);
  }

  public void CopyTo($typemap(cstype, CTYPE)[] array, int index) {
    if (array == null)
      throw new global::System.ArgumentNullException("array");
    if (index < 0 || index > array.Length)
      throw new global::System.ArgumentOutOfRangeException("index", "Value is less than zero");
    if (array.Rank > 1)
      throw new global::System.ArgumentException("Multi dimensional array.", "array");
    $csclassnameNode node = this.First;
    if (node != null) {
      do {
        array[index++] = node.Value;
        node = node.Next;
      } while (node != null);
    }
  }

  internal void ValidateNode($csclassnameNode node) {
    if (node == null) {
      throw new System.ArgumentNullException("node");
    }
    if (!node.inlist || node.list != this) {
      throw new System.InvalidOperationException("node");
    }
  }

  internal void ValidateNewNode($csclassnameNode node) {
    if (node == null) {
      throw new System.ArgumentNullException("node");
    }
  }

  global::System.Collections.Generic.IEnumerator<$typemap(cstype, CTYPE)> global::System.Collections.Generic.IEnumerable<$typemap(cstype, CTYPE)>.GetEnumerator() {
    return new $csclassnameEnumerator(this);
  }

  global::System.Collections.IEnumerator global::System.Collections.IEnumerable.GetEnumerator() {
    return new $csclassnameEnumerator(this);
  }

  public $csclassnameEnumerator GetEnumerator() {
    return new $csclassnameEnumerator(this);
  }

  public sealed class $csclassnameEnumerator : global::System.Collections.IEnumerator,
    global::System.Collections.Generic.IEnumerator<$typemap(cstype, CTYPE)>
  {
    private $csclassname collectionRef;
    private $csclassnameNode currentNode;
    private int currentIndex;
    private object currentObject;
    private int currentSize;

    public $csclassnameEnumerator($csclassname collection) {
      collectionRef = collection;
      currentNode = collection.First;
      currentIndex = 0;
      currentObject = null;
      currentSize = collectionRef.Count;
    }

    // Type-safe iterator Current
    public $typemap(cstype, CTYPE) Current {
      get {
        if (currentIndex == -1)
          throw new global::System.InvalidOperationException("Enumeration not started.");
        if (currentIndex > currentSize)
          throw new global::System.InvalidOperationException("Enumeration finished.");
        if (currentObject == null)
          throw new global::System.InvalidOperationException("Collection modified.");
        return ($typemap(cstype, CTYPE))currentObject;
      }
    }

    // Type-unsafe IEnumerator.Current
    object global::System.Collections.IEnumerator.Current {
      get {
        return Current;
      }
    }

    public bool MoveNext() {
      if (currentNode == null) {
        currentIndex = collectionRef.Count + 1;
        return false;
      }
      ++currentIndex;
      currentObject = currentNode.Value;
      currentNode = currentNode.Next;
      return true;
    }

    public void Reset() {
      currentIndex = -1;
      currentObject = null;
      if (collectionRef.Count != currentSize) {
        throw new global::System.InvalidOperationException("Collection modified.");
      }
    }

    public void Dispose() {
      currentIndex = -1;
      currentObject = null;
    }
  }

  public sealed class $csclassnameNode {
    internal $csclassname list;
    internal System.IntPtr iter;
    internal $typemap(cstype, CTYPE) csharpvalue;
    internal bool inlist;

    public $csclassnameNode($typemap(cstype, CTYPE) value) {
      csharpvalue = value;
      inlist = false;
    }

    internal $csclassnameNode(System.IntPtr iter, $csclassname list) {
      this.list = list;
      this.iter = iter;
      inlist = true;
    }

    public $csclassname List {
      get {
        return this.list;
      }
    }

    public $csclassnameNode Next {
      get {
        if (list.getNextIter(iter) == System.IntPtr.Zero)
          return null;
        return new $csclassnameNode(list.getNextIter(iter), list);
      }
    }

    public $csclassnameNode Previous {
      get {
        if (list.getPrevIter(iter) == System.IntPtr.Zero)
          return null;
        return new $csclassnameNode(list.getPrevIter(iter), list);
      }
    }

    public $typemap(cstype, CTYPE) Value {
      get {
        return list.getItem(this.iter);
      }
      set {
        list.setItem(this.iter, value);
      }
    }

    public static bool operator==($csclassnameNode node1, $csclassnameNode node2) {
      if (object.ReferenceEquals(node1, null) && object.ReferenceEquals(node2, null))
        return true;
      if (object.ReferenceEquals(node1, null) || object.ReferenceEquals(node2, null))
        return false;
      return node1.Equals(node2);
    }

    public static bool operator!=($csclassnameNode node1, $csclassnameNode node2) {
      if (node1 == null && node2 == null)
        return false;
      if (node1 == null || node2 == null)
        return true;
      return !node1.Equals(node2);
    }

    public bool Equals($csclassnameNode node) {
      if (node == null)
        return false;
      if (!node.inlist || !this.inlist)
        return object.ReferenceEquals(this, node);
      return list.equals(this.iter, node.iter);
    }

    public override bool Equals(object node) {
      return Equals(($csclassnameNode)node);
    }

    public override int GetHashCode() {
      int hash = 13;
      if (inlist) {
        hash = (hash * 7) + this.list.GetHashCode();
        hash = (hash * 7) + this.Value.GetHashCode();
        hash = (hash * 7) + this.list.getNextIter(this.iter).GetHashCode();
        hash = (hash * 7) + this.list.getPrevIter(this.iter).GetHashCode();
      } else {
        hash = (hash * 7) + this.csharpvalue.GetHashCode();
      }
      return hash;
    }

    public void Dispose() {
      list.deleteIter(this.iter);
    }
  }
%}

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 value_type& const_reference;

  class iterator;

  void push_front(CTYPE const& x);
  void push_back(CTYPE const& x);
  %rename(RemoveFirst) pop_front;
  void pop_front();
  %rename(RemoveLast) pop_back;
  void pop_back();
  size_type size() const;
  %rename(Clear) clear;
  void clear();
  %extend {
    const_reference getItem(iterator *iter) {
      return **iter;
    }

    void setItem(iterator *iter, CTYPE const& val) {
      *(*iter) = val;
    }

    iterator *getFirstIter() {
      if ($self->size() == 0)
        return NULL;
      return new std::list< CTYPE >::iterator($self->begin());
    }

    iterator *getLastIter() {
      if ($self->size() == 0)
        return NULL;
      return new std::list< CTYPE >::iterator(--$self->end());
    }

    iterator *getNextIter(iterator *iter) {
      std::list< CTYPE >::iterator it = *iter;
      if (std::distance(it, --$self->end()) != 0) {
        std::list< CTYPE >::iterator* itnext = new std::list< CTYPE >::iterator(++it);
        return itnext;
      }
      return NULL;
    }

    iterator *getPrevIter(iterator *iter) {
      std::list< CTYPE >::iterator it = *iter;
      if (std::distance($self->begin(), it) != 0) {
        std::list< CTYPE >::iterator* itprev = new std::list< CTYPE >::iterator(--it);
        return itprev;
      }
      return NULL;
    }

    iterator *insertNode(iterator *iter, CTYPE const& value) {
      std::list< CTYPE >::iterator it = $self->insert(*iter, value);
      return new std::list< CTYPE >::iterator(it);
    }

    void eraseIter(iterator *iter) {
      std::list< CTYPE >::iterator it = *iter;
      $self->erase(it);
    }

    void deleteIter(iterator *iter) {
      delete iter;
    }

    bool equals(iterator *iter1, iterator *iter2) {
      if (iter1 == NULL && iter2 == NULL)
        return true;
      if (iter1 == NULL || iter2 == NULL)
        return false;
      std::list< CTYPE >::iterator it1 = *iter1;
      std::list< CTYPE >::iterator it2 = *iter2;
      return it1 == it2;
    }
  }
%enddef

// Extra methods added to the collection class if operator== is defined for the class being wrapped
// The class will then implement ICollection<>, which adds extra functionality
%define SWIG_STD_LIST_EXTRA_OP_EQUALS_EQUALS(CTYPE...)
  %extend {
    bool Contains(CTYPE const& value) {
      return std::find($self->begin(), $self->end(), value) != $self->end();
    }

    bool Remove(CTYPE const& value) {
      std::list< CTYPE >::iterator it = std::find($self->begin(), $self->end(), value);
      if (it != $self->end()) {
        $self->erase(it);
        return true;
      }
      return false;
    }

    iterator *find(CTYPE const& value) {
      if (std::find($self->begin(), $self->end(), value) != $self->end()) {
        return new std::list< CTYPE >::iterator(std::find($self->begin(), $self->end(), value));
      }
      return NULL;
    }
  }
%proxycode %{
  public $csclassnameNode Find($typemap(cstype, CTYPE) value) {
    System.IntPtr tmp = find(value);
    if (tmp != System.IntPtr.Zero) {
      return new $csclassnameNode(tmp, this);
    }
    return null;
  }
%}
%enddef

// Macros for std::list class specializations/enhancements
%define SWIG_STD_LIST_ENHANCED(CTYPE...)
namespace std {
  template<> class list< CTYPE > {
    SWIG_STD_LIST_MINIMUM_INTERNAL(ICollection, %arg(CTYPE));
    SWIG_STD_LIST_EXTRA_OP_EQUALS_EQUALS(CTYPE)
  };
}
%enddef


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

%csmethodmodifiers std::list::size "private"
%csmethodmodifiers std::list::getItem "private"
%csmethodmodifiers std::list::setItem "private"
%csmethodmodifiers std::list::push_front "private"
%csmethodmodifiers std::list::push_back "private"
%csmethodmodifiers std::list::getFirstIter "private"
%csmethodmodifiers std::list::getNextIter "private"
%csmethodmodifiers std::list::getPrevIter "private"
%csmethodmodifiers std::list::getLastIter "private"
%csmethodmodifiers std::list::find "private"
%csmethodmodifiers std::list::deleteIter "private"

namespace std {
  // primary (unspecialized) class template for std::list
  // does not require operator== to be defined
  template<class T>
  class list {
    SWIG_STD_LIST_MINIMUM_INTERNAL(IEnumerable, T)
  };
  // specialization for pointers
  template<class T>
  class list<T *> {
    SWIG_STD_LIST_MINIMUM_INTERNAL(ICollection, T *)
    SWIG_STD_LIST_EXTRA_OP_EQUALS_EQUALS(T *)
  };
}

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