"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSeed = exports.setAllRNGToStartSeed = exports.setAllRNGToSeed = exports.serializeRNG = exports.rngEquals = exports.newRNG = exports.isSerializedRNG = exports.isRNG = exports.getRandomSeed = exports.deserializeRNG = exports.copyRNG = void 0;
const cachedClasses_1 = require("../core/cachedClasses");
const SerializationBrand_1 = require("../enums/private/SerializationBrand");
const debugFunctions_1 = require("./debugFunctions");
const isaacAPIClass_1 = require("./isaacAPIClass");
const log_1 = require("./log");
const table_1 = require("./table");
const types_1 = require("./types");
const utils_1 = require("./utils");
/**
 * This is the ShiftIdx that Blade recommended after having reviewing the game's internal functions.
 * Any value between 0 and 80 should work equally well.
 * https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
 */
const RECOMMENDED_SHIFT_IDX = 35;
const OBJECT_NAME = "RNG";
const KEYS = ["seed"];
/** Helper function to copy an `RNG` Isaac API class. */
function copyRNG(rng) {
    if (!isRNG(rng)) {
        error(`Failed to copy a ${OBJECT_NAME} object since the provided object was not a userdata ${OBJECT_NAME} class.`);
    }
    const seed = rng.GetSeed();
    return newRNG(seed);
}
exports.copyRNG = copyRNG;
/**
 * Helper function to convert a `SerializedRNG` object to a normal `RNG` object. (This is used by
 * the save data manager when reading data from the "save#.dat" file.)
 */
function deserializeRNG(rng) {
    if (!(0, types_1.isTable)(rng)) {
        error(`Failed to deserialize a ${OBJECT_NAME} object since the provided object was not a Lua table.`);
    }
    const [seed] = (0, table_1.getNumbersFromTable)(rng, OBJECT_NAME, ...KEYS);
    (0, utils_1.assertDefined)(seed, `Failed to deserialize a ${OBJECT_NAME} object since the provided object did not have a value for: seed`);
    return newRNG(seed);
}
exports.deserializeRNG = deserializeRNG;
/**
 * Helper function to get a random `Seed` value to be used in spawning entities and so on. Use this
 * instead of calling the `Random` function directly since that can return a value of 0 and crash
 * the game.
 */
function getRandomSeed() {
    const randomNumber = Random(); // eslint-disable-line deprecation/deprecation
    const safeRandomNumber = randomNumber === 0 ? 1 : randomNumber;
    return safeRandomNumber;
}
exports.getRandomSeed = getRandomSeed;
/** Helper function to check if something is an instantiated `RNG` object. */
function isRNG(object) {
    return (0, isaacAPIClass_1.isIsaacAPIClassOfType)(object, OBJECT_NAME);
}
exports.isRNG = isRNG;
/**
 * Used to determine is the given table is a serialized `RNG` object created by the `deepCopy`
 * function.
 */
function isSerializedRNG(object) {
    if (!(0, types_1.isTable)(object)) {
        return false;
    }
    return (0, table_1.tableHasKeys)(object, ...KEYS) && object.has(SerializationBrand_1.SerializationBrand.RNG);
}
exports.isSerializedRNG = isSerializedRNG;
/**
 * Helper function to initialize a new RNG object using Blade's recommended shift index.
 *
 * @param seed Optional. The seed to initialize it with. Default is a random seed.
 */
function newRNG(seed = getRandomSeed()) {
    const rng = RNG();
    setSeed(rng, seed);
    return rng;
}
exports.newRNG = newRNG;
function rngEquals(rng1, rng2) {
    return (0, isaacAPIClass_1.isaacAPIClassEquals)(rng1, rng2, KEYS);
}
exports.rngEquals = rngEquals;
/**
 * Helper function to convert a `RNG` object to a `SerializedRNG` object. (This is used by the save
 * data manager when writing data from the "save#.dat" file.)
 */
function serializeRNG(rng) {
    if (!isRNG(rng)) {
        error(`Failed to serialize a ${OBJECT_NAME} object since the provided object was not a userdata ${OBJECT_NAME} class.`);
    }
    const seed = rng.GetSeed();
    const rngTable = new LuaMap();
    rngTable.set("seed", seed);
    rngTable.set(SerializationBrand_1.SerializationBrand.RNG, "");
    return rngTable;
}
exports.serializeRNG = serializeRNG;
/**
 * Helper function to iterate over the provided object and set the seed for all of the values that
 * are RNG objects equal to a particular seed.
 */
function setAllRNGToSeed(object, seed) {
    if (!(0, types_1.isTable)(object)) {
        error(`Failed to iterate over the object containing RNG objects since the type of the provided object was: ${typeof object}`);
    }
    let setAtLeastOneSeed = false;
    for (const [_key, value] of object) {
        if (isRNG(value)) {
            setSeed(value, seed);
            setAtLeastOneSeed = true;
        }
    }
    if (!setAtLeastOneSeed) {
        error(`Failed to set all RNG objects to seed ${seed} because the parent object did not contain any RNG objects.`);
    }
}
exports.setAllRNGToSeed = setAllRNGToSeed;
/**
 * Helper function to iterate over the provided object and set the seed for all of the values that
 * are RNG objects equal to the start seed for the current run.
 */
function setAllRNGToStartSeed(object) {
    const seeds = cachedClasses_1.game.GetSeeds();
    const startSeed = seeds.GetStartSeed();
    setAllRNGToSeed(object, startSeed);
}
exports.setAllRNGToStartSeed = setAllRNGToStartSeed;
/** Helper function to set a seed to an RNG object using Blade's recommended shift index. */
function setSeed(rng, seed) {
    if (seed === 0) {
        seed = getRandomSeed();
        (0, log_1.logError)("Failed to set a RNG object to a seed of 0. Using a random value instead.");
        (0, debugFunctions_1.traceback)();
    }
    // The game expects seeds in the range of 1 to 4294967295 (1^32 - 1).
    // eslint-disable-next-line deprecation/deprecation
    rng.SetSeed(seed, RECOMMENDED_SHIFT_IDX);
}
exports.setSeed = setSeed;
