/* file : utils.ts
MIT License

Copyright (c) 2017-2020 Thomas Minier & Arnaud Grall

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
'use strict';
var __values = (this && this.__values) || function(o) {
    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
    if (m) return m.call(o);
    if (o && typeof o.length === "number") return {
        next: function () {
            if (o && i >= o.length) o = void 0;
            return { value: o && o[i++], done: !o };
        }
    };
    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var xxhashjs_1 = __importDefault(require("xxhashjs"));
/**
 * Create a new array fill with a base value
 * @param size - The size of the array
 * @param defaultValue - The default value used to fill the array. If it's a function, it will be invoked to get the default value.
 * @return A newly allocated array
 * @memberof Utils
 */
function allocateArray(size, defaultValue) {
    var array = new Array(size);
    var getDefault = (typeof defaultValue === 'function') ? defaultValue : function () { return defaultValue; };
    for (var ind = 0; ind < size; ind++) {
        array[ind] = getDefault();
    }
    return array;
}
exports.allocateArray = allocateArray;
/**
 * (64-bits only) Hash a value into two values (in hex or integer format)
 * @param  value - The value to hash
 * @param  asInt - (optional) If True, the values will be returned as an integer. Otherwise, as hexadecimal values.
 * @param seed the seed used for hashing
 * @return The results of the hash functions applied to the value (in hex or integer)
 * @memberof Utils
 * @author Arnaud Grall & Thomas Minier
 */
function hashTwice(value, asInt, seed) {
    if (asInt === undefined) {
        asInt = false;
    }
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    var f = xxhashjs_1.default.h64(value, seed + 1);
    var l = xxhashjs_1.default.h64(value, seed + 2);
    if (asInt) {
        return {
            first: f.toNumber(),
            second: l.toNumber()
        };
    }
    else {
        var one = f.toString(16);
        if (one.length < 16) {
            one = '0'.repeat(16 - one.length) + one;
        }
        var two = l.toString(16);
        if (two.length < 16) {
            two = '0'.repeat(16 - two.length) + two;
        }
        return {
            first: Number(one),
            second: Number(two)
        };
    }
}
exports.hashTwice = hashTwice;
function hashTwiceAsString(value, seed) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    var f = xxhashjs_1.default.h64(value, seed + 1);
    var l = xxhashjs_1.default.h64(value, seed + 2);
    var one = f.toString(16);
    if (one.length < 16)
        one = '0'.repeat(16 - one.length) + one;
    var two = l.toString(16);
    if (two.length < 16)
        two = '0'.repeat(16 - two.length) + two;
    return {
        first: one,
        second: two
    };
}
exports.hashTwiceAsString = hashTwiceAsString;
/**
 * (64-bits only) Same as hashTwice but return Numbers and String equivalent
 * @param  val the value to hash
 * @param  seed the seed to change when hashing
 * @return A object of shape {int: {first: <number>, second: <number>}, string: {first: <hex-string>, second: <hex-string>}
 * @author Arnaud Grall
 */
function allInOneHashTwice(val, seed) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    var one = xxhashjs_1.default.h64(val, seed + 1);
    var two = xxhashjs_1.default.h64(val, seed + 2);
    var stringOne = one.toString(16);
    if (stringOne.length < 16)
        stringOne = '0'.repeat(16 - stringOne.length) + stringOne;
    var stringTwo = two.toString(16);
    if (stringTwo.length < 16)
        stringTwo = '0'.repeat(16 - stringTwo.length) + stringTwo;
    return {
        int: {
            first: one.toNumber(),
            second: two.toNumber()
        },
        string: {
            first: stringOne,
            second: stringTwo
        }
    };
}
exports.allInOneHashTwice = allInOneHashTwice;
/**
 * Apply Double Hashing to produce a n-hash
 *
 * This implementation used directly the value produced by the two hash functions instead of the functions themselves.
 * @see {@link http://citeseer.ist.psu.edu/viewdoc/download;jsessionid=4060353E67A356EF9528D2C57C064F5A?doi=10.1.1.152.579&rep=rep1&type=pdf} for more details about double hashing.
 * @param  n - The indice of the hash function we want to produce
 * @param  hashA - The result of the first hash function applied to a value.
 * @param  hashB - The result of the second hash function applied to a value.
 * @param  size - The size of the datastructures associated to the hash context (ex: the size of a Bloom Filter)
 * @return The result of hash_n applied to a value.
 * @memberof Utils
 * @author Thomas Minier
 */
function doubleHashing(n, hashA, hashB, size) {
    return Math.abs((hashA + n * hashB) % size);
}
exports.doubleHashing = doubleHashing;
/**
 * Generate a set of distinct indexes on interval [0, size) using the double hashing technique
 * @param  element  - The element to hash
 * @param  size     - the range on which we can generate an index [0, size) = size
 * @param  number   - The number of indexes desired
 * @param  seed     - The seed used
 * @return A array of indexes
 * @author Arnaud Grall
 */
function getDistinctIndices(element, size, number, seed) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    var indexes = [];
    var n = 1;
    while (indexes.length < number) {
        var hashes = hashTwice(element, true, seed + size + n);
        var ind = doubleHashing(n, hashes.first, hashes.second, size);
        if (!indexes.includes(ind)) {
            indexes.push(ind);
        }
        n++;
    }
    return indexes;
}
exports.getDistinctIndices = getDistinctIndices;
/**
 * Generate hashCount indexes, one index per [0, size)
 * it uses the double hashing technique to generate the indexes
 * @param  element    - The element to hash
 * @param  size       - The range on which we can g-enerate the index, exclusive
 * @param  hashCount  - The number of indexes we want
 * @return An array of indexes
 */
function getIndices(element, size, hashCount, seed) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    var arr = [];
    for (var i = 1; i <= hashCount; i++) {
        var hashes = hashTwice(element, true, seed + size % i);
        arr.push(doubleHashing(i, hashes.first, hashes.second, size));
    }
    if (arr.length !== hashCount)
        throw new Error('report this, please, shouldnt be of different size');
    return arr;
}
exports.getIndices = getIndices;
/**
 * Generate a random int bewteen two bounds (included)
 * @param min - The lower bound
 * @param max - The upper bound
 * @param random - Function used to generate random floats
 * @return A random int bewteen lower and upper bound (included)
 * @memberof Utils
 * @author Thomas Minier
 */
function randomInt(min, max, random) {
    if (random === undefined) {
        random = Math.random;
    }
    min = Math.ceil(min);
    max = Math.floor(max);
    var rn = random();
    return Math.floor(rn * (max - min + 1)) + min;
}
exports.randomInt = randomInt;
/**
 * Return the non-destructive XOR of two buffers
 * @param a - The buffer to copy, then to xor with b
 * @param b - The buffer to xor with
 * @return The results of the XOR between the two buffers
 * @author Arnaud Grall
 */
function xorBuffer(a, b) {
    var length = Math.max(a.length, b.length);
    var buffer = Buffer.allocUnsafe(length).fill(0);
    for (var i = 0; i < length; ++i) {
        if (i < a.length && i < b.length) {
            buffer[length - i - 1] = a[a.length - i - 1] ^ b[b.length - i - 1];
        }
        else if (i < a.length && i >= b.length) {
            buffer[length - i - 1] ^= a[a.length - i - 1];
        }
        else if (i < b.length && i >= a.length) {
            buffer[length - i - 1] ^= b[b.length - i - 1];
        }
    }
    // now need to remove leading zeros in the buffer if any
    var start = 0;
    var it = buffer.values();
    var value = it.next();
    while (!value.done && value.value === 0) {
        start++;
        value = it.next();
    }
    var buf2 = buffer.slice(start);
    return buf2;
}
exports.xorBuffer = xorBuffer;
/**
 * Return true if the buffer is empty, i.e., all value are equals to 0.
 * @param  buffer - The buffer to inspect
 * @return True if the buffer only contains zero, False otherwise
 * @author Arnaud Grall
 */
function isEmptyBuffer(buffer) {
    var e_1, _a;
    if (buffer === null || !buffer)
        return true;
    try {
        for (var buffer_1 = __values(buffer), buffer_1_1 = buffer_1.next(); !buffer_1_1.done; buffer_1_1 = buffer_1.next()) {
            var value = buffer_1_1.value;
            if (value !== 0) {
                return false;
            }
        }
    }
    catch (e_1_1) { e_1 = { error: e_1_1 }; }
    finally {
        try {
            if (buffer_1_1 && !buffer_1_1.done && (_a = buffer_1.return)) _a.call(buffer_1);
        }
        finally { if (e_1) throw e_1.error; }
    }
    return true;
    // const json = buffer.toJSON()
    // let i = 0
    // let found = false
    // while (!found && i < json.data.length) {
    //   if (json.data[i] !== 0) {
    //     found = true
    //   }
    //   i++
    // }
    // return !found
}
exports.isEmptyBuffer = isEmptyBuffer;
/**
 * Hash an item as an unsigned int
 * @param  elem - Element to hash
 * @param  seed - The hash seed. If its is UINT32 make sure to set the length to 32
 * @param  length - The length of hashes (defaults to 64 bits)
 * @return The hash value as an unsigned int
 * @author Arnaud Grall
 */
function hashAsInt(elem, seed, length) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    switch (length) {
        case 32:
            return xxhashjs_1.default.h32(elem, seed).toNumber();
        case 64:
            return xxhashjs_1.default.h64(elem, seed).toNumber();
        default:
            return xxhashjs_1.default.h64(elem, seed).toNumber();
    }
}
exports.hashAsInt = hashAsInt;
/**
 * Hash an item as a string
 * @param  elem - Element to hash
 * @param  seed - The hash seed. If its is UINT32 make sure to set the length to 32
 * @param  base - The base in which the string will be returned, default: 16
 * @param  length - The length of hashes (defaults to 64 bits)
 * @return The hashed value as a string
 * @author Arnaud Grall
 */
function hashAsString(elem, seed, base, length) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    if (base === undefined) {
        base = 16;
    }
    if (length === undefined) {
        length = 64;
    }
    var hash;
    switch (length) {
        case 32:
            hash = xxhashjs_1.default.h32(elem, seed);
            break;
        case 64:
            hash = xxhashjs_1.default.h64(elem, seed);
            break;
        default:
            hash = xxhashjs_1.default.h64(elem, seed);
            break;
    }
    var result = '';
    if (base === 16) {
        result = hash.toString(base);
        if (result.length < (length / 4)) {
            result = '0'.repeat((length / 4) - result.length) + result;
        }
    }
    else if (base === 2) {
        result = hex2bin(hash.toString(16));
        if (result.length < length) {
            result = '0'.repeat(length - result.length) + result;
        }
    }
    return result;
}
exports.hashAsString = hashAsString;
/**
 * Hash an item as a string
 * @param  elem - Element to hash
 * @param  seed - The hash seed. If its is UINT32 make sure to set the length to 32
 * @param  base - The base in which the string will be returned, default: 16
 * @param  length - The length of hashes (defaults to 64 bits)
 * @return The item hased as an int and a string
 * @author Arnaud Grall
 */
function hashIntAndString(elem, seed, base, length) {
    if (seed === undefined) {
        seed = getDefaultSeed();
    }
    if (base === undefined) {
        base = 16;
    }
    if (length === undefined) {
        length = 64;
    }
    var hash;
    switch (length) {
        case 32:
            hash = xxhashjs_1.default.h32(elem, seed);
            break;
        case 64:
            hash = xxhashjs_1.default.h64(elem, seed);
            break;
        default:
            hash = xxhashjs_1.default.h64(elem, seed);
            break;
    }
    var result = '';
    if (base === 16) {
        result = hash.toString(base);
        if (result.length < (length / 4)) {
            result = '0'.repeat((length / 4) - result.length) + result;
        }
    }
    else if (base === 2) {
        result = hex2bin(hash.toString(16));
        if (result.length < length) {
            result = '0'.repeat(length - result.length) + result;
        }
    }
    return { int: hash.toNumber(), string: result };
}
exports.hashIntAndString = hashIntAndString;
/**
 * Return the default seed used in the package
 * @return A ssed as a floating point number
 * @author Arnaud Grall
 */
function getDefaultSeed() {
    return 0x1234567890;
}
exports.getDefaultSeed = getDefaultSeed;
/**
 * Return the next power of 2 of x
 * @param  x - Value
 * @return The next power of 2 of x
 */
function power2(x) {
    return Math.ceil(Math.pow(2, Math.floor(Math.log(x) / Math.log(2))));
}
exports.power2 = power2;
/**
 * Convert an hex string into a binary string
 * @param  hex  - A base 16 string
 * @return A base 2 string
 */
function hex2bin(hex) {
    return parseInt(hex, 16).toString(2);
}
exports.hex2bin = hex2bin;
