typedef import(T);
from collections.iter(T=T) access Iter_T, Iterable_T;

// Set: set of representatives of equivalence classes. Contains at most one
// element from each equivalence class.


struct Set_T {
  restricted T nullT;
  restricted bool equiv(T, T) = operator ==;
  restricted bool isNullT(T) = null;
  restricted void operator init() {}
  restricted void operator init(T nullT,
      bool equiv(T a, T b) = operator ==,
      bool isNullT(T) = new bool(T t) { return equiv(t, nullT); }) {
    this.nullT = nullT;
    this.equiv = equiv;
    this.isNullT = isNullT;
  }

  // Creates a new, empty Set with the same implemention, nullT,
  // isNullT, and equiv as this one.
  Set_T newEmpty();

  int size();
  bool empty() {
    return size() == 0;
  }
  bool contains(T item);
  // Returns the equivalent item in the set, or nullT if the set
  // contains no equivalent item. Throws error if nullT was never set.
  T get(T item);
  // Returns an iterator over the items in the set.
  Iter_T operator iter();
  // If an equivalent item was already present, returns false. Otherwise, adds
  // the item and returns true. Noop if isNullT is defined and item is empty.
  bool add(T item);  
  // Inserts item, and returns the item that was replaced, or nullT if
  // no item was replaced. Throws error if there is no equivalent item and nullT
  // was never set. Noop if isNullT is defined and isNullT(item).
  T push(T item);
  // Removes the equivalent item from the set, and returns it. Returns
  // nullT if there is no equivalent item. Throws error if
  // there is no equivalent item and nullT was never set.
  T extract(T item);
  // If the item was present, removes it and returns true. Otherwise,
  // returns false. Noop if isNullT is defined and isNullT(item).
  bool delete(T item) {
    if (isNullT == null) {
      if (!contains(item)) {
        return false;
      }
      extract(item);
      return true;
    }
    return !isNullT(extract(item));
  }
  // Returns a random, uniformly distributed element. The default
  // implementation is O(n) in the number of elements. Intended primarily for
  // testing purposes.
  T getRandom() {
    int size = this.size();
    if (size == 0) {
      assert(isNullT != null, 'Cannot get a random item from an empty set');
      return nullT;
    }
    int index = rand(0, size - 1);
    for (T item : this) {
      if (index == 0) {
        return item;
      }
      --index;
    }
    assert(false, 'Unreachable code');
    return nullT;
  }

  autounravel Iterable_T operator cast(Set_T set) {
    return Iterable_T(set.operator iter);
  }

  void add(Iterable_T other) {
    for (T item : other) {
      add(item);
    }
  }
  void delete(Iterable_T other) {
    for (T item : other) {
      delete(item);
    }
  }

  autounravel bool operator <=(Set_T a, Set_T b) {
    for (var item : a) {
      if (!b.contains(item)) {
        return false;
      }
    }
    return true;
  }

  autounravel bool operator >=(Set_T a, Set_T b) {
    return b <= a;
  }

  autounravel bool operator ==(Set_T a, Set_T b) {
    return a <= b && a >= b;
  } 

  autounravel bool operator !=(Set_T a, Set_T b) {
    return !(a == b);
  }

  autounravel bool sameElementsInOrder(Set_T a, Set_T b) {
    bool equiv(T ai, T bi) {
      return a.equiv(ai, bi) && b.equiv(ai, bi);
    }
    var iterA = a.operator iter();
    var iterB = b.operator iter();
    while (iterA.valid() && iterB.valid()) {
      if (!equiv(iterA.get(), iterB.get())) {
        return false;
      }
      iterA.advance();
      iterB.advance();
    }
    return iterA.valid() == iterB.valid();
  }

  autounravel Set_T operator +(Set_T a, Iterable_T b) {
    Set_T result = a.newEmpty();
    for (T item : a) {
      result.add(item);
    }
    for (T item : b) {
      result.add(item);
    }
    return result;
  }

  autounravel Set_T operator -(Set_T a, Set_T b) {
    Set_T result = a.newEmpty();
    for (T item : a) {
      if (!b.contains(item)) {
        result.add(item);
      }
    }
    return result;
  }

  autounravel Set_T operator &(Set_T a, Set_T b) {
    Set_T result = a.newEmpty();
    for (T item : a) {
      if (b.contains(item)) {
        result.add(item);
      }
    }
    return result;
  }

  // Symmetric difference. The set of elements that are in either a or b, but
  // not in both.
  // This is equivalent to (a - b) + (b - a).
  autounravel Set_T operator ^(Set_T a, Set_T b) {
    Set_T result = a.newEmpty();
    for (T item : a) {
      if (!b.contains(item)) {
        result.add(item);
      }
    }
    for (T item : b) {
      if (!a.contains(item)) {
        result.add(item);
      }
    }
    return result;
  }

}


// A reference implementation, inefficient but suitable for testing.
struct NaiveSet_T {
  Set_T super;
  unravel super;
  private T[] items;
  restricted void operator init() {
    typedef void F();
    ((F)super.operator init)();
  }
  restricted void operator init(T nullT,
      bool equiv(T a, T b) = operator ==,
      bool isNullT(T) = new bool(T t) { return equiv(t, nullT); }) {
    typedef void F(T, bool equiv(T, T), bool isNullT(T));
    ((F)super.operator init)(nullT, equiv, isNullT);
  }

  super.size = new int() {
    return items.length;
  };

  super.contains = new bool(T item) {
    for (T i : items) {
      if (equiv(i, item)) {
        return true;
      }
    }
    return false;
  };

  super.get = new T(T item) {
    for (T i : items) {
      if (equiv(i, item)) {
        return i;
      }
    }
    if (isNullT == null) {
      assert(false, 'item not found');
    }
    return nullT;
  };

  super.operator iter = new Iter_T() {
    return Iter_T(items);
  };

  super.add = new bool(T item) {
    if (isNullT != null && isNullT(item)) {
      return false;
    }
    if (contains(item)) {
      return false;
    }
    items.push(item);
    return true;
  };

  super.push = new T(T item) {
    if (isNullT != null && isNullT(item)) {
      return nullT;
    }
    for (int i = 0; i < items.length; ++i) {
      if (equiv(items[i], item)) {
        T result = items[i];
        items[i] = item;
        return result;
      }
    }
    items.push(item);
    assert(isNullT != null, 'item not found');
    return nullT;
  };

  super.extract = new T(T item) {
    for (int i = 0; i < items.length; ++i) {
      if (equiv(items[i], item)) {
        T result = items[i];
        items.delete(i);
        return result;
      }
    }
    assert(isNullT != null, 'item not found');
    return nullT;
  };

  // This implementation is O(1).
  super.getRandom = new T() {
    if (items.length == 0) {
      assert(isNullT != null, 'Cannot get a random item from an empty set');
      return nullT;
    }
    int index = rand(0, items.length - 1);
    return items[index];
  };

  autounravel Iterable_T operator cast(NaiveSet_T set) {
    return Iterable_T(set.operator iter);
  }

  autounravel Set_T operator cast(NaiveSet_T set) {
    return set.super;
  }

  super.newEmpty = new Set_T() {
    return NaiveSet_T(nullT, equiv, isNullT);
  };

  autounravel T[] operator ecast(NaiveSet_T set) {
    T[] result;
    for (T item : set.items) {
      result.push(item);
    }
    return result;
  }
}
