/* -----------------------------------------------------------------------------
 * std_unordered_set.i
 *
 * SWIG typemaps for std::unordered_set<T>.
 *
 * Note that ISet<> used here requires .NET 4 or later.
 *
 * The C# wrapper implements ISet<> interface and is similar in
 * characteristics to the C# System.Collections.Generic.HashSet<> class, but
 * doesn't provide quite all of its methods.
 * ----------------------------------------------------------------------------- */

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

%csmethodmodifiers std::unordered_set::empty "private"
%csmethodmodifiers std::unordered_set::size "private"
%csmethodmodifiers std::unordered_set::getitem "private"
%csmethodmodifiers std::unordered_set::create_iterator_begin "private"
%csmethodmodifiers std::unordered_set::get_next "private"
%csmethodmodifiers std::unordered_set::destroy_iterator "private"

namespace std {

// TODO: Add support for comparator and allocator template parameters.
template <class T>
class unordered_set {

%typemap(csinterfaces) std::unordered_set<T> "global::System.IDisposable, global::System.Collections.Generic.ISet<$typemap(cstype, T)>\n"
%proxycode %{
  void global::System.Collections.Generic.ICollection<$typemap(cstype, T)>.Add($typemap(cstype, T) item) {
      ((global::System.Collections.Generic.ISet<$typemap(cstype, T)>)this).Add(item);
  }

  public bool TryGetValue($typemap(cstype, T) equalValue, out $typemap(cstype, T) actualValue) {
    try {
      actualValue = getitem(equalValue);
      return true;
    } catch {
      actualValue = default($typemap(cstype, T));
      return false;
    }
  }

  public bool IsEmpty {
    get {
      return empty();
    }
  }

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

  public bool IsReadOnly {
    get {
      return false;
    }
  }

  public void CopyTo($typemap(cstype, T)[] array) {
    CopyTo(array, 0);
  }

  public void CopyTo($typemap(cstype, T)[] array, int arrayIndex) {
    if (array == null)
      throw new global::System.ArgumentNullException("array");
    if (arrayIndex < 0)
      throw new global::System.ArgumentOutOfRangeException("arrayIndex", "Value is less than zero");
    if (array.Rank > 1)
      throw new global::System.ArgumentException("Multi dimensional array.", "array");
    if (arrayIndex+this.Count > array.Length)
      throw new global::System.ArgumentException("Number of elements to copy is too large.");

    foreach ($typemap(cstype, T) item in this) {
      array.SetValue(item, arrayIndex++);
    }
  }

  public void ExceptWith(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    foreach ($typemap(cstype, T) item in other) {
      Remove(item);
    }
  }

  public void IntersectWith(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    $csclassname old = new $csclassname(this);

    Clear();
    foreach ($typemap(cstype, T) item in other) {
      if (old.Contains(item))
        Add(item);
    }
  }

  private static int count_enum(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    int count = 0;
    foreach ($typemap(cstype, T) item in other) {
      count++;
    }

    return count;
  }

  public bool IsProperSubsetOf(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    return IsSubsetOf(other) && Count < count_enum(other);
  }

  public bool IsProperSupersetOf(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    return IsSupersetOf(other) && Count > count_enum(other);
  }

  public bool IsSubsetOf(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    int countContained = 0;

    foreach ($typemap(cstype, T) item in other) {
      if (Contains(item))
        countContained++;
    }

    return countContained == Count;
  }

  public bool IsSupersetOf(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    foreach ($typemap(cstype, T) item in other) {
      if (!Contains(item))
        return false;
    }

    return true;
  }

  public bool Overlaps(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    foreach ($typemap(cstype, T) item in other) {
      if (Contains(item))
        return true;
    }

    return false;
  }

  public bool SetEquals(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    return IsSupersetOf(other) && Count == count_enum(other);
  }

  public void SymmetricExceptWith(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    foreach ($typemap(cstype, T) item in other) {
      if (!Remove(item))
        Add(item);
    }
  }

  public void UnionWith(global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)> other) {
    foreach ($typemap(cstype, T) item in other) {
      Add(item);
    }
  }

  private global::System.Collections.Generic.ICollection<$typemap(cstype, T)> Items {
    get {
      global::System.Collections.Generic.ICollection<$typemap(cstype, T)> items = new global::System.Collections.Generic.List<$typemap(cstype, T)>();
      int size = this.Count;
      if (size > 0) {
        global::System.IntPtr iter = create_iterator_begin();
        for (int i = 0; i < size; i++) {
          items.Add(get_next(iter));
        }
        destroy_iterator(iter);
      }
      return items;
    }
  }

  global::System.Collections.Generic.IEnumerator<$typemap(cstype, T)> global::System.Collections.Generic.IEnumerable<$typemap(cstype, T)>.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);
  }

  // Type-safe enumerator
  /// Note that the IEnumerator documentation requires an InvalidOperationException to be thrown
  /// whenever the collection is modified. This has been done for changes in the size of the
  /// collection but not when one of the elements of the collection is modified as it is a bit
  /// tricky to detect unmanaged code that modifies the collection under our feet.
  public sealed class $csclassnameEnumerator : global::System.Collections.IEnumerator,
      global::System.Collections.Generic.IEnumerator<$typemap(cstype, T)>
  {
    private $csclassname collectionRef;
    private global::System.Collections.Generic.IList<$typemap(cstype, T)> ItemsCollection;
    private int currentIndex;
    private object currentObject;
    private int currentSize;

    public $csclassnameEnumerator($csclassname collection) {
      collectionRef = collection;
      ItemsCollection = new global::System.Collections.Generic.List<$typemap(cstype, T)>(collection.Items);
      currentIndex = -1;
      currentObject = null;
      currentSize = collectionRef.Count;
    }

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

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

    public bool MoveNext() {
      int size = collectionRef.Count;
      bool moveOkay = (currentIndex+1 < size) && (size == currentSize);
      if (moveOkay) {
        currentIndex++;
        currentObject = ItemsCollection[currentIndex];
      } else {
        currentObject = null;
      }
      return moveOkay;
    }

    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:
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    typedef T key_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;

    unordered_set();
    unordered_set(const unordered_set& other);
    size_type size() const;
    bool empty() const;
    %rename(Clear) clear;
    void clear();
    %extend {
      bool Add(const value_type& item) {
        return $self->insert(item).second;
      }

      bool Contains(const value_type& item) {
        return $self->count(item) != 0;
      }

      bool Remove(const value_type& item) {
        return $self->erase(item) != 0;
      }

      const value_type& getitem(const value_type& item) throw (std::out_of_range) {
        std::unordered_set<T>::iterator iter = $self->find(item);
        if (iter == $self->end())
          throw std::out_of_range("item not found");

        return *iter;
      }

      // create_iterator_begin(), get_next() and destroy_iterator work together to provide a collection of items to C#
      %apply void *VOID_INT_PTR { std::unordered_set<T>::iterator *create_iterator_begin }
      %apply void *VOID_INT_PTR { std::unordered_set<T>::iterator *swigiterator }

      std::unordered_set<T>::iterator *create_iterator_begin() {
        return new std::unordered_set<T>::iterator($self->begin());
      }

      const key_type& get_next(std::unordered_set<T>::iterator *swigiterator) {
        (void)$self;
        std::unordered_set<T>::iterator iter = *swigiterator;
        (*swigiterator)++;
        return *iter;
      }

      void destroy_iterator(std::unordered_set<T>::iterator *swigiterator) {
        (void)$self;
        delete swigiterator;
      }
    }
};

}
