/*!
 * @author electricessence / https://github.com/electricessence/
 * Licensing: MIT https://github.com/electricessence/TypeScript.NET/blob/master/LICENSE.md
 */
import Type from "../Types";
import LinkedNodeList from "./LinkedNodeList";
import ArgumentException from "../Exceptions/ArgumentException";
import ArgumentNullException from "../Exceptions/ArgumentNullException";
import forEach from "./Enumeration/forEach";
import { empty as emptyEnumerator } from "./Enumeration/Enumerator";
import { using } from "../Disposable/Utility";
const OTHER = 'other';
export default class Set {
    constructor(source) {
        this._count = 0;
        if (source)
            this.unionWith(source);
    }
    _getSet() {
        var s = this._set;
        if (!s)
            this._set = s = new LinkedNodeList();
        return s;
    }
    get count() {
        return this._count;
    }
    get isReadOnly() { return true; }
    exceptWith(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        forEach(other, v => {
            this.remove(v);
        });
    }
    intersectWith(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        if (other instanceof Set) {
            let s = this._set;
            if (s)
                s.forEach(n => {
                    if (!other.contains(n.value))
                        this.remove(n.value);
                });
        }
        else {
            using(new Set(other), o => this.intersectWith(o));
        }
    }
    isProperSubsetOf(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        return other instanceof Set
            ? other.isProperSupersetOf(this)
            : using(new Set(other), o => o.isProperSupersetOf(this));
    }
    isProperSupersetOf(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        var result = true, count;
        if (other instanceof Set) {
            result = this.isSupersetOf(other);
            count = other._count;
        }
        else {
            using(new Set(), o => {
                forEach(other, v => {
                    o.add(v);
                    return result = this.contains(v);
                });
                count = o._count;
            });
        }
        return result && this._count > count;
    }
    isSubsetOf(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        return other instanceof Set
            ? other.isSupersetOf(this)
            : using(new Set(other), o => o.isSupersetOf(this));
    }
    isSupersetOf(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        var result = true;
        forEach(other, v => {
            return result = this.contains(v);
        });
        return result;
    }
    overlaps(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        var result = false;
        forEach(other, v => !(result = this.contains(v)));
        return result;
    }
    setEquals(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        return this._count == (other instanceof Set
            ? other._count
            : using(new Set(other), o => o._count))
            && this.isSubsetOf(other);
    }
    symmetricExceptWith(other) {
        if (!other)
            throw new ArgumentNullException(OTHER);
        if (other instanceof Set) {
            forEach(other, v => {
                if (this.contains(v))
                    this.remove(v);
                else
                    this.add(v);
            });
        }
        else {
            using(new Set(other), o => this.symmetricExceptWith(o));
        }
    }
    unionWith(other) {
        forEach(other, v => {
            this.add(v);
        });
    }
    add(item) {
        if (!this.contains(item)) {
            var type = typeof item;
            if (!Type.isPrimitive(type))
                throw new ArgumentException("item", "A Set can only index primitives.  Complex objects require a HashSet.");
            var r = this._registry;
            var t = r && r[type];
            if (!r)
                this._registry = r = {};
            if (!t)
                r[type] = t = {};
            var node = { value: item };
            this._getSet().addNode(node);
            t[item] = node;
            ++this._count;
        }
    }
    clear() {
        var _ = this;
        _._count = 0;
        wipe(_._registry, 2);
        var s = _._set;
        return s ? s.clear() : 0;
    }
    dispose() {
        this.clear();
        this._set = null;
        this._registry = null;
    }
    _getNode(item) {
        var r = this._registry, t = r && r[typeof item];
        return t && t[item];
    }
    contains(item) {
        return !(!this._count || !this._getNode(item));
    }
    copyTo(array, index = 0) {
        if (!array)
            throw new ArgumentNullException('array');
        var s = this._set, c = this._count;
        if (!s || !c)
            return array;
        var minLength = index + c;
        if (array.length < minLength)
            array.length = minLength;
        return LinkedNodeList.copyValues(s, array, index);
    }
    toArray() {
        var s = this._set;
        return s ? s.map(n => n.value) : [];
    }
    remove(item) {
        var r = this._registry, t = r && r[typeof item], node = t && t[item];
        if (node) {
            delete t[item];
            var s = this._set;
            if (s && s.removeNode(node)) {
                --this._count;
                return 1;
            }
        }
        return 0;
    }
    getEnumerator() {
        var s = this._set;
        return s && this._count
            ? LinkedNodeList.valueEnumeratorFrom(s)
            : emptyEnumerator;
    }
}
function wipe(map, depth = 1) {
    if (map && depth) {
        for (var key of Object.keys(map)) {
            var v = map[key];
            delete map[key];
            wipe(v, depth - 1);
        }
    }
}
//# sourceMappingURL=Set.js.map