import { fromEquals } from './Eq';
import { identity } from './function';
import { not } from './Predicate';
import { separated } from './Separated';
/**
 * @category conversions
 * @since 2.5.0
 */
export var fromSet = function (s) { return new Set(s); };
// -------------------------------------------------------------------------------------
// constructors
// -------------------------------------------------------------------------------------
/**
 * Create a set with one element
 *
 * @category constructors
 * @since 2.5.0
 */
export var singleton = function (a) { return new Set([a]); };
/**
 * Create a `ReadonlySet` from a `ReadonlyArray`
 *
 * @category conversions
 * @since 2.10.0
 */
export var fromReadonlyArray = function (E) {
    return function (as) {
        var len = as.length;
        var out = new Set();
        var has = elem(E);
        for (var i = 0; i < len; i++) {
            var a = as[i];
            if (!has(a, out)) {
                out.add(a);
            }
        }
        return out;
    };
};
/**
 * @category conversions
 * @since 2.5.0
 */
export function toSet(s) {
    return new Set(s);
}
/**
 * Projects a Set through a function
 *
 * @since 2.5.0
 */
export function map(E) {
    var elemE = elem(E);
    return function (f) { return function (set) {
        var r = new Set();
        set.forEach(function (e) {
            var v = f(e);
            if (!elemE(v, r)) {
                r.add(v);
            }
        });
        return r;
    }; };
}
/**
 * @since 2.5.0
 */
export function chain(E) {
    var elemE = elem(E);
    return function (f) { return function (set) {
        var r = new Set();
        set.forEach(function (e) {
            f(e).forEach(function (e) {
                if (!elemE(e, r)) {
                    r.add(e);
                }
            });
        });
        return r;
    }; };
}
export function filter(predicate) {
    return function (set) {
        var values = set.values();
        var e;
        var r = new Set();
        while (!(e = values.next()).done) {
            var a = e.value;
            if (predicate(a)) {
                r.add(a);
            }
        }
        return r;
    };
}
export function partition(predicate) {
    return function (set) {
        var values = set.values();
        var e;
        var right = new Set();
        var left = new Set();
        while (!(e = values.next()).done) {
            var a = e.value;
            if (predicate(a)) {
                right.add(a);
            }
            else {
                left.add(a);
            }
        }
        return separated(left, right);
    };
}
export function union(E) {
    var elemE = elem(E);
    return function (me, that) {
        if (that === undefined) {
            var unionE_1 = union(E);
            return function (that) { return unionE_1(me, that); };
        }
        if (isEmpty(me)) {
            return that;
        }
        if (isEmpty(that)) {
            return me;
        }
        var r = new Set(me);
        that.forEach(function (e) {
            if (!elemE(e, r)) {
                r.add(e);
            }
        });
        return r;
    };
}
export function intersection(E) {
    var elemE = elem(E);
    return function (me, that) {
        if (that === undefined) {
            var intersectionE_1 = intersection(E);
            return function (that) { return intersectionE_1(that, me); };
        }
        if (isEmpty(me) || isEmpty(that)) {
            return empty;
        }
        var r = new Set();
        me.forEach(function (e) {
            if (elemE(e, that)) {
                r.add(e);
            }
        });
        return r;
    };
}
/**
 * @since 2.5.0
 */
export function partitionMap(EB, EC) {
    return function (f) {
        return function (set) {
            var values = set.values();
            var e;
            var left = new Set();
            var right = new Set();
            var hasB = elem(EB);
            var hasC = elem(EC);
            while (!(e = values.next()).done) {
                var v = f(e.value);
                switch (v._tag) {
                    case 'Left':
                        if (!hasB(v.left, left)) {
                            left.add(v.left);
                        }
                        break;
                    case 'Right':
                        if (!hasC(v.right, right)) {
                            right.add(v.right);
                        }
                        break;
                }
            }
            return separated(left, right);
        };
    };
}
export function difference(E) {
    var elemE = elem(E);
    return function (me, that) {
        if (that === undefined) {
            var differenceE_1 = difference(E);
            return function (that) { return differenceE_1(that, me); };
        }
        return filter(function (a) { return !elemE(a, that); })(me);
    };
}
/**
 * @since 2.5.0
 */
export function reduce(O) {
    var toReadonlyArrayO = toReadonlyArray(O);
    return function (b, f) { return function (fa) { return toReadonlyArrayO(fa).reduce(f, b); }; };
}
/**
 * @since 2.5.0
 */
export function foldMap(O, M) {
    var toReadonlyArrayO = toReadonlyArray(O);
    return function (f) { return function (fa) { return toReadonlyArrayO(fa).reduce(function (b, a) { return M.concat(b, f(a)); }, M.empty); }; };
}
/**
 * @category folding
 * @since 2.11.0
 */
export var reduceRight = function (O) {
    var toReadonlyArrayO = toReadonlyArray(O);
    return function (b, f) { return function (fa) { return toReadonlyArrayO(fa).reduceRight(function (b, a) { return f(a, b); }, b); }; };
};
/**
 * Insert a value into a set
 *
 * @since 2.5.0
 */
export function insert(E) {
    var elemE = elem(E);
    return function (a) { return function (set) {
        if (!elemE(a)(set)) {
            var r = new Set(set);
            r.add(a);
            return r;
        }
        else {
            return set;
        }
    }; };
}
/**
 * Delete a value from a set
 *
 * @since 2.5.0
 */
export var remove = function (E) {
    return function (a) {
        return function (set) {
            return filter(function (ax) { return !E.equals(a, ax); })(set);
        };
    };
};
/**
 * Checks an element is a member of a set;
 * If yes, removes the value from the set
 * If no, inserts the value to the set
 *
 * @since 2.10.0
 */
export var toggle = function (E) {
    var elemE = elem(E);
    var removeE = remove(E);
    var insertE = insert(E);
    return function (a) { return function (set) { return (elemE(a, set) ? removeE : insertE)(a)(set); }; };
};
/**
 * @since 2.5.0
 */
export var compact = function (E) { return filterMap(E)(identity); };
/**
 * @since 2.5.0
 */
export function separate(EE, EA) {
    return function (fa) {
        var elemEE = elem(EE);
        var elemEA = elem(EA);
        var left = new Set();
        var right = new Set();
        fa.forEach(function (e) {
            switch (e._tag) {
                case 'Left':
                    if (!elemEE(e.left, left)) {
                        left.add(e.left);
                    }
                    break;
                case 'Right':
                    if (!elemEA(e.right, right)) {
                        right.add(e.right);
                    }
                    break;
            }
        });
        return separated(left, right);
    };
}
/**
 * @since 2.5.0
 */
export function filterMap(E) {
    var elemE = elem(E);
    return function (f) { return function (fa) {
        var r = new Set();
        fa.forEach(function (a) {
            var ob = f(a);
            if (ob._tag === 'Some' && !elemE(ob.value, r)) {
                r.add(ob.value);
            }
        });
        return r;
    }; };
}
// -------------------------------------------------------------------------------------
// utils
// -------------------------------------------------------------------------------------
/**
 * @since 2.5.0
 */
export var empty = new Set();
/**
 * Test whether a `ReadonlySet` is empty.
 *
 * @since 2.10.0
 */
export var isEmpty = function (set) { return set.size === 0; };
/**
 * Calculate the number of elements in a `ReadonlySet`.
 *
 * @since 2.10.0
 */
export var size = function (set) { return set.size; };
/**
 * @since 2.5.0
 */
export var some = function (predicate) {
    return function (set) {
        var values = set.values();
        var e;
        var found = false;
        while (!found && !(e = values.next()).done) {
            found = predicate(e.value);
        }
        return found;
    };
};
export function every(predicate) {
    return not(some(not(predicate)));
}
export function isSubset(E) {
    var elemE = elem(E);
    return function (me, that) {
        if (that === undefined) {
            var isSubsetE_1 = isSubset(E);
            return function (that) { return isSubsetE_1(that, me); };
        }
        return every(function (a) { return elemE(a, that); })(me);
    };
}
export function elem(E) {
    return function (a, set) {
        if (set === undefined) {
            var elemE_1 = elem(E);
            return function (set) { return elemE_1(a, set); };
        }
        var values = set.values();
        var e;
        var found = false;
        while (!found && !(e = values.next()).done) {
            found = E.equals(a, e.value);
        }
        return found;
    };
}
/**
 * Get a sorted `ReadonlyArray` of the values contained in a `ReadonlySet`.
 *
 * @category conversions
 * @since 2.5.0
 */
export var toReadonlyArray = function (O) {
    return function (set) {
        var out = [];
        set.forEach(function (e) { return out.push(e); });
        return out.sort(O.compare);
    };
};
/**
 * @category type lambdas
 * @since 2.11.0
 */
export var URI = 'ReadonlySet';
/**
 * @category instances
 * @since 2.5.0
 */
export function getShow(S) {
    return {
        show: function (s) {
            var entries = [];
            s.forEach(function (a) {
                entries.push(S.show(a));
            });
            return "new Set([".concat(entries.sort().join(', '), "])");
        }
    };
}
/**
 * @category instances
 * @since 2.5.0
 */
export function getEq(E) {
    var subsetE = isSubset(E);
    return fromEquals(function (x, y) { return subsetE(x, y) && subsetE(y, x); });
}
/**
 * @category instances
 * @since 2.11.0
 */
export var getUnionSemigroup = function (E) { return ({
    concat: union(E)
}); };
/**
 * @category instances
 * @since 2.5.0
 */
export var getUnionMonoid = function (E) { return ({
    concat: getUnionSemigroup(E).concat,
    empty: empty
}); };
/**
 * @category instances
 * @since 2.5.0
 */
export var getIntersectionSemigroup = function (E) { return ({
    concat: intersection(E)
}); };
/**
 * @category instances
 * @since 2.11.0
 */
export var getDifferenceMagma = function (E) { return ({
    concat: difference(E)
}); };
// -------------------------------------------------------------------------------------
// deprecated
// -------------------------------------------------------------------------------------
/**
 * Use [`fromReadonlyArray`](#fromreadonlyarray) instead.
 *
 * @category zone of death
 * @since 2.5.0
 * @deprecated
 */
export var fromArray = fromReadonlyArray;