//#OPTIONS: CPP

/*
  simple set with reasonably fast iteration though an array, which may contain nulls
  elements must be objects that have a unique _key property
  collections are expected to be homogeneous

  when iterating over a set with an iterator, the following operations are safe:

   - adding an element to the set (the existing iterator will iterate over the new elements)
   - removing the last returned element through the iterator

   behaviour for deleting elements is unpredictable and unsafe
*/

/** @constructor */
function h$Set(s) {
    this._vals = [];
    this._keys = [];
    this._size = 0;
}

h$Set.prototype.size = function() {
    return this._size;
}

h$Set.prototype.add = function(o) {
#ifdef GHCJS_STRUCTS_ASSERTS
    if((typeof o !== 'object' && typeof o !== 'function') || typeof o._key !== 'number') throw ("h$Set.add: invalid element: " + o);
    if(this._size > 0) {
//        if(this._storedProto !== o.prototype) throw ("h$Set.add: unexpected element prototype: " + o)
    } else {
        this._storedProto = o.prototype;
    }
    if(this._keys[o._key] !== undefined && this._vals[this._keys[o._key]] !== o) throw ("h$Set.add: duplicate key: " + o);
#endif
    var k = this._keys, v = this._vals;
    if(k[o._key] === undefined) {
        k[o._key] = this._size;
        v[this._size++] = o;
    }
}

h$Set.prototype.remove = function(o) {
    if(this._size === 0) return;
    var k = this._keys, v = this._vals, x = k[o._key];
    if(x !== undefined) {
        delete k[o._key];
        var ls = --this._size;
        if(ls !== x) {
            var l = v[ls];
            v[x]      = l;
            k[l._key] = x;
        }
        v[ls] = undefined;
        if(v.length > 10 && 2 * v.length > 3 * ls) this._vals = v.slice(0, ls);
    }
}

h$Set.prototype.has = function(o) {
    return this._keys[o._key] !== undefined;
}

h$Set.prototype.clear = function() {
    if(this._size > 0) {
	this._keys = [];
	this._vals = [];
	this._size = 0;
    }
}

h$Set.prototype.iter = function() {
    return new h$SetIter(this);
}

// returns an array with all values, might contain additional nulls at the end
h$Set.prototype.values = function() {
    return this._vals;
}

/** @constructor */
function h$SetIter(s) {
    this._n = 0;
    this._s = s;
    this._r = true;
}

h$SetIter.prototype.next = function() {
    if(this._n < this._s._size) {
        this._r = false;
        return this._s._vals[this._n++];
    } else {
        this._r = true;
        return null;
    }
}

h$SetIter.prototype.peek = function() {
    if(this._n < this._s._size) {
        return this._s._vals[this._n];
    } else {
        return null;
    }
}

// remove the last element returned
h$SetIter.prototype.remove = function() {
    if(!this._r) {
        this._s.remove(this._s._vals[--this._n]);
        this._r = true;
    }
}

/*
  map, iteration restrictions are the same as for set
  keys need to be objects with a unique _key property

  keys are expected to have the same prototype

  values may be anything (but note that the values array might have additional nulls)
*/

/** @constructor */
function h$Map() {
    this._pairsKeys   = [];
    this._pairsValues = [];
    this._keys        = [];
    this._size        = 0;
}

h$Map.prototype.size = function() {
    return this._size;
}

h$Map.prototype.put = function(k,v) {
#ifdef GHCJS_STRUCTS_ASSERTS
    if((typeof k !== 'object' && typeof k !== 'function') || typeof k._key !== 'number') throw ("h$Map.add: invalid key: " + k);
    if(this._size > 0) {
        if(this._storedProto !== k.prototype) throw ("h$Map.add: unexpected key prototype: " + k)
    } else {
        this._storedProto = k.prototype;
    }
    if(this._keys[k._key] !== undefined && this._pairsKeys[this._keys[k._key]] !== k) throw ("h$Map.add: duplicate key: " + k);
#endif
    var ks = this._keys, pk = this._pairsKeys, pv = this._pairsValues, x = ks[k._key];
    if(x === undefined) {
        var n = this._size++;
        ks[k._key] = n;
        pk[n] = k;
        pv[n] = v;
    } else {
        pv[x] = v;
    }
}

h$Map.prototype.remove = function(k) {
    var kk = k._key, ks = this._keys, pk = this._pairsKeys, pv = this._pairsValues, x = ks[kk];
    if(x !== undefined) {
        delete ks[kk];
        var ss = --this._size;
        if(ss !== x) {
            var pks      = pk[ss];
            pk[x]        = pks;
            pv[x]        = pv[ss];
            ks[pks._key] = x;
        }
        pv[ss] = undefined;
        pk[ss] = undefined;

        if(pk.length > 10 && 2 * pk.length > 3 * this._size) {
            this._pairsKeys   = pk.slice(0,ss);
            this._pairsValues = pv.slice(0,ss);
        }
    }
}

h$Map.prototype.has = function(k) {
    return this._keys[k._key] !== undefined;
}

h$Map.prototype.get = function(k) {
    var n = this._keys[k._key];
    if(n !== undefined) {
        return this._pairsValues[n];
    } else {
        return null;
    }
}

h$Map.prototype.iter = function() {
    return new h$MapIter(this);
}

// returned array might have some trailing nulls
h$Map.prototype.keys = function () {
    return this._pairsKeys;
}

// returned array might have some trailing nulls
h$Map.prototype.values = function() {
    return this._pairsValues;
}

/** @constructor */
function h$MapIter(m) {
    this._n = 0;
    this._m = m;
}

h$MapIter.prototype.next = function() {
    return this._n < this._m._size ? this._m._pairsKeys[this._n++] : null;
}

h$MapIter.prototype.nextVal = function() {
    return this._n < this._m._size ? this._m._pairsValues[this._n++] : null;
}

h$MapIter.prototype.peek = function() {
    return this._n < this._m._size ? this._m._pairsKeys[this._n] : null;
}

h$MapIter.prototype.peekVal = function() {
    return this._n < this._m._size ? this._m._pairsValues[this._n] : null;
}

/*
  simple queue, returns null when empty
  it's safe to enqueue new items while iterating, not safe to dequeue
  (new items will not be iterated over)
*/
#ifndef GHCJS_QUEUE_BLOCK_SIZE
#define GHCJS_QUEUE_BLOCK_SIZE 1000
#endif

/** @constructor */
function h$Queue() {
    var b = { b: [], n: null };
    this._blocks = 1;
    this._first  = b;
    this._fp     = 0;
    this._last   = b;
    this._lp     = 0;
}

h$Queue.prototype.length = function() {
    return GHCJS_QUEUE_BLOCK_SIZE * (this._blocks - 1) + this._lp - this._fp;
}

h$Queue.prototype.isEmpty = function() {
    return this._blocks === 1 && this._lp >= this._fp;
}

h$Queue.prototype.enqueue = function(o) {
    if(this._lp === GHCJS_QUEUE_BLOCK_SIZE) {
        var newBlock = { b: [o], n: null };
        this._blocks++;
        this._last.n = newBlock;
        this._last = newBlock;
        this._lp = 1;
    } else {
        this._last.b[this._lp++] = o;
    }
}

h$Queue.prototype.dequeue = function() {
    if(this._blocks === 1 && this._fp >= this._lp) {
        return null;
    } else {
        var qfb = this._first.b, r = qfb[this._fp];
        qfb[this._fp] = null;
        if(++this._fp === GHCJS_QUEUE_BLOCK_SIZE) {
            if(this._blocks === 1) {
                this._lp = 0;
            } else {
                this._blocks--;
                this._first = this._first.n;
            }
            this._fp = 0;
        } else if(this._blocks === 1 && this._fp >= this._lp) {
            this._lp = this._fp = 0;
        }
        return r;
    }
}

h$Queue.prototype.peek = function() {
    if(this._blocks === 0 || (this._blocks === 1 && this._fp >= this._lp)) {
        return null;
    } else {
        return this._first.b[this._fp];
    }
}

h$Queue.prototype.iter = function() {
    var b = this._first, bp = this._fp, lb = this._last, lp = this._lp;
    return function() {
        if(b === null || (b === lb && bp >= lp)) {
            return null;
        } else {
            var r = b.b[bp];
            if(++bp === GHCJS_QUEUE_BLOCK_SIZE) {
                b = b.n;
                bp = 0;
                if(b === null) lb = null;
            }
            return r;
        }
    }
}

/*
   binary min-heap / set
   - iteration is not in order of priority
   - values can be removed, need to have the ._key property
*/

/** @constructor */
function h$HeapSet() {
    this._keys  = [];
    this._prios = [];
    this._vals  = [];
    this._size = 0;
}

h$HeapSet.prototype.size = function() {
    return this._size;
}

// add a node, if it already exists, it's moved to the new priority
h$HeapSet.prototype.add = function(op,o) {
#ifdef GHCJS_STRUCTS_ASSERTS
    if((typeof o !== 'object' && typeof o !== 'function') || typeof o._key !== 'number') throw ("h$HeapSet.add: invalid element: " + o);
    if(this._size > 0) {
        if(this._storedProto !== o.prototype) throw ("h$HeapSet.add: unexpected element prototype: " + o)
    } else {
        this._storedProto = o.prototype;
    }
    if(this._keys[o._key] !== undefined && this._vals[this._keys[o._key]] !== o) throw ("h$Set.add: duplicate key: " + o);
#endif
    var p = this._prios, k = this._keys, v = this._vals, x = k[o._key];
    if(x !== undefined) { // adjust node
        var oop = p[x];
        if(oop !== op) {
            p[x] = op;
            if(op < oop) {
                this._upHeap(x);
            } else {
                this._downHeap(x, this._size);
            }
        }
    } else { // new node
        var s = this._size++;
        k[o._key] = s;
        p[s]      = op;
        v[s]      = o;
        this._upHeap(s);
    }
}

h$HeapSet.prototype.has = function(o) {
    return this._keys[o._key] !== undefined;
}

h$HeapSet.prototype.prio = function(o) {
    var x = this._keys[o._key];
    if(x !== undefined) {
        return this._prios[x];
    } else {
        return null;
    }
}

h$HeapSet.prototype.peekPrio = function() {
    return this._size > 0 ? this._prios[0] : null;
}

h$HeapSet.prototype.peek = function() {
    return this._size > 0 ? this._vals[0] : null;
}

h$HeapSet.prototype.pop = function() {
    if(this._size > 0) {
        var v = this._vals[0];
        this._removeNode(0);
        return v;
    } else {
        return null;
    }
}

h$HeapSet.prototype.remove = function(o) {
    var x = this._keys[o._key];
    if(x !== undefined) this._removeNode(x);
}

h$HeapSet.prototype.iter = function() {
    var n = 0, v = this._vals, s = this._size;
    return function() {
        return n < s ? v[n++] : null;
    }
}

// may be longer than this.size(), remainder is filled with nulls
h$HeapSet.prototype.values = function() {
    return this._vals;
}

h$HeapSet.prototype._removeNode = function(i) {
    var p = this._prios, v = this._vals, s = --this._size, k = this._keys;
    delete k[v[i]._key];
    if(i !== s) {
        v[i]         = v[s];
        p[i]         = p[s];
        k[v[i]._key] = i;
    }
    v[s]         = null;
    p[s]         = null;
    this._downHeap(i,s);
}

h$HeapSet.prototype._downHeap = function(i,s) {
    var p = this._prios, v = this._vals, k = this._keys;
    var j,l,r,ti,tj;
    while(true) {
        j = i, r = 2*(i+1), l = r-1;
        if(l < s && p[l] < p[i]) i = l;
        if(r < s && p[r] < p[i]) i = r;
        if(i !== j) {
            ti         = v[i];
            tj         = v[j];
            v[j]       = ti;
            v[i]       = tj;
            k[ti._key] = j;
            k[tj._key] = i;
            ti         = p[i];
            p[i]       = p[j];
            p[j]       = ti;
        } else {
            break;
        }
    }
}

h$HeapSet.prototype._upHeap = function(i) {
    var ti, tj, j, p = this._prios, v = this._vals, k = this._keys;
    while(i !== 0) {
        j = (i-1) >> 1;
        if(p[i] < p[j]) {
            ti         = v[i];
            tj         = v[j];
            v[j]       = ti;
            v[i]       = tj;
            k[ti._key] = j;
            k[tj._key] = i;
            ti         = p[i];
            p[i]       = p[j];
            p[j]       = ti;
            i          = j;
        } else {
            break;
        }
    }
}
