"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = __importDefault(require("./Base/index"));
const Vector_1 = __importDefault(require("../SequentialContainer/Vector"));
const OrderedSet_1 = __importDefault(require("../TreeContainer/OrderedSet"));
class HashSet extends index_1.default {
    constructor(container = [], initBucketNum, hashFunc) {
        super(initBucketNum, hashFunc);
        this.hashTable = [];
        container.forEach(element => this.insert(element));
    }
    reAllocate() {
        if (this.bucketNum >= index_1.default.maxBucketNum)
            return;
        const newHashTable = [];
        const originalBucketNum = this.bucketNum;
        this.bucketNum <<= 1;
        const keys = Object.keys(this.hashTable);
        const keyNums = keys.length;
        for (let i = 0; i < keyNums; ++i) {
            const index = parseInt(keys[i]);
            const container = this.hashTable[index];
            const size = container.size();
            if (size === 0)
                continue;
            if (size === 1) {
                const element = container.front();
                newHashTable[this.hashFunc(element) & (this.bucketNum - 1)] = new Vector_1.default([element], false);
                continue;
            }
            const lowList = [];
            const highList = [];
            container.forEach(element => {
                const hashCode = this.hashFunc(element);
                if ((hashCode & originalBucketNum) === 0) {
                    lowList.push(element);
                }
                else
                    highList.push(element);
            });
            if (container instanceof OrderedSet_1.default) {
                if (lowList.length > index_1.default.untreeifyThreshold) {
                    newHashTable[index] = new OrderedSet_1.default(lowList);
                }
                else if (lowList.length) {
                    newHashTable[index] = new Vector_1.default(lowList, false);
                }
                if (highList.length > index_1.default.untreeifyThreshold) {
                    newHashTable[index + originalBucketNum] = new OrderedSet_1.default(highList);
                }
                else if (highList.length) {
                    newHashTable[index + originalBucketNum] = new Vector_1.default(highList, false);
                }
            }
            else {
                if (lowList.length >= index_1.default.treeifyThreshold) {
                    newHashTable[index] = new OrderedSet_1.default(lowList);
                }
                else if (lowList.length) {
                    newHashTable[index] = new Vector_1.default(lowList, false);
                }
                if (highList.length >= index_1.default.treeifyThreshold) {
                    newHashTable[index + originalBucketNum] = new OrderedSet_1.default(highList);
                }
                else if (highList.length) {
                    newHashTable[index + originalBucketNum] = new Vector_1.default(highList, false);
                }
            }
        }
        this.hashTable = newHashTable;
    }
    forEach(callback) {
        const containers = Object.values(this.hashTable);
        const containersNum = containers.length;
        let index = 0;
        for (let i = 0; i < containersNum; ++i) {
            containers[i].forEach(element => callback(element, index++));
        }
    }
    /**
     * @description Insert element to hash set.
     * @param element The element you want to insert.
     */
    insert(element) {
        const index = this.hashFunc(element) & (this.bucketNum - 1);
        const container = this.hashTable[index];
        if (!container) {
            this.hashTable[index] = new Vector_1.default([element], false);
            this.length += 1;
        }
        else {
            const preSize = container.size();
            if (container instanceof Vector_1.default) {
                if (!container.find(element)
                    .equals(container.end()))
                    return;
                container.pushBack(element);
                if (preSize + 1 >= index_1.default.treeifyThreshold) {
                    if (this.bucketNum <= index_1.default.minTreeifySize) {
                        this.length += 1;
                        this.reAllocate();
                        return;
                    }
                    this.hashTable[index] = new OrderedSet_1.default(container);
                }
                this.length += 1;
            }
            else {
                container.insert(element);
                const curSize = container.size();
                this.length += curSize - preSize;
            }
        }
        if (this.length > this.bucketNum * index_1.default.sigma) {
            this.reAllocate();
        }
    }
    eraseElementByKey(key) {
        const index = this.hashFunc(key) & (this.bucketNum - 1);
        const container = this.hashTable[index];
        if (!container)
            return;
        const preSize = container.size();
        if (preSize === 0)
            return;
        if (container instanceof Vector_1.default) {
            container.eraseElementByValue(key);
            const curSize = container.size();
            this.length += curSize - preSize;
        }
        else {
            container.eraseElementByKey(key);
            const curSize = container.size();
            this.length += curSize - preSize;
            if (curSize <= index_1.default.untreeifyThreshold) {
                this.hashTable[index] = new Vector_1.default(container);
            }
        }
    }
    find(element) {
        const index = this.hashFunc(element) & (this.bucketNum - 1);
        const container = this.hashTable[index];
        if (!container)
            return false;
        return !container.find(element)
            .equals(container.end());
    }
    [Symbol.iterator]() {
        return function* () {
            const containers = Object.values(this.hashTable);
            const containersNum = containers.length;
            for (let i = 0; i < containersNum; ++i) {
                const container = containers[i];
                for (const element of container) {
                    yield element;
                }
            }
        }.bind(this)();
    }
}
exports.default = HashSet;