typedef import(T);

from collections.iter(T=T) access Iter_T, Iterable_T, Iterable;
from collections.set(T=T) access Set_T;

struct SortedSet_T {
  Set_T set;
  // Returns the least element > item, or nullT if there is no such
  // element.
  T after(T item);
  // Returns the greatest element < item, or nullT if there is no such
  // element.
  T before(T item);
  T atOrAfter(T item) {
    return set.contains(item) ? set.get(item) : after(item);
  }
  T atOrBefore(T item) {
    return set.contains(item) ? set.get(item) : before(item);
  }
  T min();               // Returns nullT if collection is empty.
  T popMin();            // Returns nullT if collection is empty.
  T max();               // Returns nullT if collection is empty.
  T popMax();            // Returns nullT if collection is empty.

  autounravel Iterable_T operator cast(SortedSet_T set) {
    return Iterable(set.set.operator iter);
  }
  
  autounravel Set_T operator cast(SortedSet_T sorted_set) {
    return sorted_set.set;
  }
  from set unravel *;
}

// For testing purposes, we provide a naive implementation of SortedSet_T.
// This implementation is highly inefficient, but it is correct, and can be
// used to test other implementations of SortedSet_T.
struct Naive_T {
  restricted SortedSet_T super;
  from super unravel nullT, isNullT;
  private bool lt(T a, T b) = null;
  private T[] buffer = new T[0];

  private bool leq(T a, T b) { return !lt(b, a); };
  private bool gt(T a, T b) { return lt(b, a); };
  private bool geq(T a, T b) { return leq(b, a); };
  private bool equiv(T a, T b) { return leq(a, b) && leq(b, a); };

  void operator init(bool lessThan(T, T), T nullT) {
    this.lt = lessThan;
    super.operator init(nullT, equiv);
  }

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

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

  super.after = new T(T item) {
    for (T possibility : buffer) {
      if (gt(possibility, item)) return possibility;
    }
    assert(isNullT != null, 'No element after item to return');
    return nullT;
  };

  super.before = new T(T item) {
    for (int ii = buffer.length - 1; ii >= 0; --ii) {
      T possibility = buffer[ii];
      if (lt(possibility, item)) return possibility;
    }
    assert(isNullT != null, 'No element before item to return');
    return nullT;
  };

  super.min = new T() {
    if (buffer.length == 0) {
      assert(isNullT != null, 'No minimum element to return');
      return nullT;
    }
    return buffer[0];
  };

  super.popMin = new T() {
    if (buffer.length == 0) {
      assert(isNullT != null, 'No minimum element to return');
      return nullT;
    }
    T toreturn = buffer[0];
    buffer.delete(0);
    return toreturn;
  };

  super.max = new T() {
    if (buffer.length == 0) {
      assert(isNullT != null, 'No maximum element to return');
      return nullT;
    }
    return buffer[buffer.length - 1];
  };

  super.popMax = new T() {
    if (buffer.length == 0) {
      assert(isNullT != null, 'No maximum element to return');
      return nullT;
    }
    return buffer.pop();
  };

  super.add = new bool(T item) {
    for (int i = 0; i < buffer.length; ++i) {
      if (equiv(buffer[i], item)) return false;
      else if (gt(buffer[i], item)) {
        buffer.insert(i, item);
        return true;
      }
    }
    buffer.push(item);
    return true;
  };

  super.push = new T(T item) {
    for (int i = 0; i < buffer.length; ++i) {
      if (equiv(buffer[i], item)) {
        T toreturn = buffer[i];
        buffer[i] = item;
        return toreturn;
      }
      else if (gt(buffer[i], item)) {
        buffer.insert(i, item);
        return nullT;
      }
    }
    buffer.push(item);
    return nullT;
  };

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

  super.extract = new T(T item) {
    for (int i = 0; i < buffer.length; ++i) {
      T candidate = buffer[i];
      if (equiv(candidate, item)) {
        buffer.delete(i);
        return candidate;
      }
    }
    return nullT;
  };

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

  super.getRandom = new T() {
    if (buffer.length == 0) {
      assert(isNullT != null, 'No element to return');
      return nullT;
    }
    return buffer[rand(0, buffer.length - 1)];
  };

  autounravel SortedSet_T operator cast(Naive_T naive) {
    return naive.super;
  }

  // Compose cast operators, since implicit casting is not transitive.
  autounravel Set_T operator cast(Naive_T naive) {
    return (SortedSet_T)naive;
  }
  autounravel Iterable_T operator cast(Naive_T naive) {
    return Iterable(naive.super.operator iter);
  }


  from super unravel *;
}
