(function(baselib) {
    var exports = {};

    baselib.hashes = exports;


    
    var _eqHashCodeCounter = 0;
    var makeEqHashCode = function() {
	_eqHashCodeCounter++;
	return _eqHashCodeCounter;
    };


    // getHashCode: any -> (or fixnum string)
    // Given a value, produces a hashcode appropriate for eq.
    var getEqHashCode = function(x) {
	if (typeof(x) === 'string') {
	    return x;
	}
	if (typeof(x) === 'number') {
	    return String(x);
	}
	if (x && !x._eqHashCode) {
	    x._eqHashCode = makeEqHashCode();
	}
	if (x && x._eqHashCode) {
	    return x._eqHashCode;
	}
	return 0;
    };


    // Creates a low-level hashtable, following the interface of 
    // http://www.timdown.co.uk/jshashtable/
    //
    // Defined to use the getEqHashCode defined in baselib_hash.js.
    var makeLowLevelEqHash = function() {
	return new Hashtable(function(x) { return getEqHashCode(x); },
			     function(x, y) { return x === y; });
    };










    //////////////////////////////////////////////////////////////////////
    // Eq Hashtables
    var EqHashTable = function(inputHash) {
        this.hash = makeLowLevelEqHash();
        this.mutable = true;

    };

    EqHashTable.prototype.toWrittenString = function(cache) {
        var keys = this.hash.keys();
        var ret = [];
        for (var i = 0; i < keys.length; i++) {
	    var keyStr = toWrittenString(keys[i], cache);
	    var valStr = toWrittenString(this.hash.get(keys[i]), cache);
	    ret.push('(' + keyStr + ' . ' + valStr + ')');
        }
        return ('#hasheq(' + ret.join(' ') + ')');
    };
    
    EqHashTable.prototype.toDisplayedString = function(cache) {
        var keys = this.hash.keys();
        var ret = [];
        for (var i = 0; i < keys.length; i++) {
	    var keyStr = toDisplayedString(keys[i], cache);
	    var valStr = toDisplayedString(this.hash.get(keys[i]), cache);
	    ret.push('(' + keyStr + ' . ' + valStr + ')');
        }
        return ('#hasheq(' + ret.join(' ') + ')');
    };

    EqHashTable.prototype.equals = function(other, aUnionFind) {
        if ( !(other instanceof EqHashTable) ) {
	    return false; 
        }

        if (this.hash.keys().length != other.hash.keys().length) { 
	    return false;
        }

        var keys = this.hash.keys();
        for (var i = 0; i < keys.length; i++){
	    if ( !(other.hash.containsKey(keys[i]) &&
	           plt.baselib.equality.equals(this.hash.get(keys[i]),
		                               other.hash.get(keys[i]),
		                               aUnionFind)) ) {
		return false;
	    }
        }
        return true;
    };



    //////////////////////////////////////////////////////////////////////
    // Equal hash tables
    var EqualHashTable = function(inputHash) {
	this.hash = new _Hashtable(
            function(x) {
	        return plt.baselib.format.toWrittenString(x); 
	    },
	    function(x, y) {
		return plt.baselib.equality.equals(x, y, new plt.baselib.UnionFind()); 
	    });
	this.mutable = true;
    };

    EqualHashTable.prototype.toWrittenString = function(cache) {
        var keys = this.hash.keys();
        var ret = [];
        for (var i = 0; i < keys.length; i++) {
	    var keyStr = plt.baselib.format.toWrittenString(keys[i], cache);
	    var valStr = plt.baselib.format.toWrittenString(this.hash.get(keys[i]), cache);
	    ret.push('(' + keyStr + ' . ' + valStr + ')');
        }
        return ('#hash(' + ret.join(' ') + ')');
    };
    EqualHashTable.prototype.toDisplayedString = function(cache) {
        var keys = this.hash.keys();
        var ret = [];
        for (var i = 0; i < keys.length; i++) {
	    var keyStr = plt.baselib.format.toDisplayedString(keys[i], cache);
	    var valStr = plt.baselib.format.toDisplayedString(this.hash.get(keys[i]), cache);
	    ret.push('(' + keyStr + ' . ' + valStr + ')');
        }
        return ('#hash(' + ret.join(' ') + ')');
    };

    EqualHashTable.prototype.equals = function(other, aUnionFind) {
        if ( !(other instanceof EqualHashTable) ) {
	    return false; 
        }

        if (this.hash.keys().length != other.hash.keys().length) { 
	    return false;
        }

        var keys = this.hash.keys();
        for (var i = 0; i < keys.length; i++){
	    if (! (other.hash.containsKey(keys[i]) &&
	           plt.baselib.equality.equals(this.hash.get(keys[i]),
		                               other.hash.get(keys[i]),
		                               aUnionFind))) {
	        return false;
	    }
        }
        return true;
    };




    var isHash = function(x) { 
        return (x instanceof EqHashTable ||
		x instanceof EqualHashTable); 
    };

















    //////////////////////////////////////////////////////////////////////

    exports.getEqHashCode = getEqHashCode;
    exports.makeEqHashCode = makeEqHashCode;
    exports.makeLowLevelEqHash = makeLowLevelEqHash;


    exports.EqualHashTable = EqualHashTable;
    exports.EqHashTable = EqHashTable;
    exports.isHash = isHash;


})(this['plt'].baselib);