import _ from 'lodash';
import '../../assert/js/assert.js';
import '../../globals.js';
// Copyright 2013-2023, University of Colorado Boulder
/**
 * An immutable permutation that can permute an array
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
import dot from './dot.js';
import Utils from './Utils.js';
class Permutation {
    /**
     * Creates a permutation that will rearrange a list so that newList[i] = oldList[permutation[i]]
     */
    constructor(indices) {
        this.indices = indices;
    }
    size() {
        return this.indices.length;
    }
    /**
     * Applies the permutation, returning either a new array or number (whatever was provided).
     */
    apply(arrayOrInt) {
        if (typeof arrayOrInt === 'number') {
            // @ts-expect-error
            return this.indices[arrayOrInt];
        }
        else {
            if (arrayOrInt.length !== this.size()) {
                throw new Error(`Permutation length ${this.size()} not equal to list length ${arrayOrInt.length}`);
            }
            // permute it as an array
            const result = new Array(arrayOrInt.length);
            for (let i = 0; i < arrayOrInt.length; i++) {
                result[i] = arrayOrInt[this.indices[i]];
            }
            // @ts-expect-error
            return result;
        }
    }
    /**
     * Creates a new permutation that is the inverse of this.
     */
    inverted() {
        const newPermutation = new Array(this.size());
        for (let i = 0; i < this.size(); i++) {
            newPermutation[this.indices[i]] = i;
        }
        return new Permutation(newPermutation);
    }
    withIndicesPermuted(indices) {
        const result = [];
        Permutation.forEachPermutation(indices, integers => {
            const oldIndices = this.indices;
            const newPermutation = oldIndices.slice(0);
            for (let i = 0; i < indices.length; i++) {
                newPermutation[indices[i]] = oldIndices[integers[i]];
            }
            result.push(new Permutation(newPermutation));
        });
        return result;
    }
    toString() {
        return `P[${this.indices.join(', ')}]`;
    }
    equals(permutation) {
        return this.indices.length === permutation.indices.length && _.isEqual(this.indices, permutation.indices);
    }
    /**
     * Creates an identity permutation of a given size.
     */
    static identity(size) {
        assert && assert(size >= 0);
        const indices = new Array(size);
        for (let i = 0; i < size; i++) {
            indices[i] = i;
        }
        return new Permutation(indices);
    }
    /**
     * Lists all permutations that have a given size
     */
    static permutations(size) {
        const result = [];
        Permutation.forEachPermutation(Utils.rangeInclusive(0, size - 1), integers => {
            result.push(new Permutation(integers.slice()));
        });
        return result;
    }
    /**
     * Calls a callback on every single possible permutation of the given Array
     *
     * @param array
     * @param callback - Called on each permuted version of the array possible
     */
    static forEachPermutation(array, callback) {
        recursiveForEachPermutation(array, [], callback);
    }
    static permutationsOf(array) {
        const results = [];
        Permutation.forEachPermutation(array, result => {
            results.push(result.slice());
        });
        return results;
    }
}
dot.register('Permutation', Permutation);
/**
 * Call our function with each permutation of the provided list PREFIXED by prefix, in lexicographic order
 *
 * @param array   List to generate permutations of
 * @param prefix   Elements that should be inserted at the front of each list before each call
 * @param callback Function to call
 */
function recursiveForEachPermutation(array, prefix, callback) {
    if (array.length === 0) {
        callback(prefix);
    }
    else {
        for (let i = 0; i < array.length; i++) {
            const element = array[i];
            // remove the element from the array
            const nextArray = array.slice(0);
            nextArray.splice(i, 1);
            // add it into the prefix
            const nextPrefix = prefix.slice(0);
            nextPrefix.push(element);
            recursiveForEachPermutation(nextArray, nextPrefix, callback);
        }
    }
}
export default Permutation;
//# sourceMappingURL=Permutation.js.map