"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.logVector = exports.logUserdata = exports.logUseFlags = exports.logTearFlags = exports.logTableShallow = exports.logTableKeys = exports.logTableDifferences = exports.logTable = exports.logSounds = exports.logSet = exports.logSeedEffects = exports.logRoom = exports.logProjectileFlags = exports.logPlayerHealth = exports.logPlayerEffects = exports.logMusic = exports.logMap = exports.logLevelStateFlags = exports.logKColor = exports.logItemPoolTypes = exports.logGameStateFlags = exports.logFlags = exports.logEntityID = exports.logEntityFlags = exports.logDisplayFlags = exports.logDamageFlags = exports.logColor = exports.logCollectibleTypes = exports.logArray = void 0;
const isaac_typescript_definitions_1 = require("isaac-typescript-definitions");
const cachedClasses_1 = require("../core/cachedClasses");
const ReadonlySet_1 = require("../types/ReadonlySet");
const array_1 = require("./array");
const bosses_1 = require("./bosses");
const collectibles_1 = require("./collectibles");
const entities_1 = require("./entities");
const enums_1 = require("./enums");
const flag_1 = require("./flag");
const isaacAPIClass_1 = require("./isaacAPIClass");
const itemPool_1 = require("./itemPool");
const log_1 = require("./log");
const playerEffects_1 = require("./playerEffects");
const playerHealth_1 = require("./playerHealth");
const players_1 = require("./players");
const roomData_1 = require("./roomData");
const set_1 = require("./set");
const sort_1 = require("./sort");
const table_1 = require("./table");
const trinkets_1 = require("./trinkets");
const tstlClass_1 = require("./tstlClass");
const types_1 = require("./types");
const vector_1 = require("./vector");
/**
 * Helper function to log all of the values in an array.
 *
 * @param array The array to log.
 * @param name Optional. The name of the array, which will be logged before the elements.
 */
function logArray(array, name) {
    // We do not assume the given array has contiguous values in order to be more permissive about the
    // kinds of arrays that will successfully log without a run-time error.
    if (!(0, array_1.isArray)(array, false)) {
        (0, log_1.log)("Tried to log an array, but the given object was not an array.");
        return;
    }
    const arrayString = (0, array_1.arrayToString)(array);
    if (name === undefined) {
        name = "array";
    }
    (0, log_1.log)(`Logging ${name}: ${arrayString}`);
}
exports.logArray = logArray;
/**
 * Helper function to log the names of a collectible type array.
 *
 * @param collectibleTypes The collectible types to log.
 * @param name Optional. The name of the array, which will be logged before the elements.
 */
function logCollectibleTypes(collectibleTypes, name) {
    if (name === undefined) {
        name = "collectibles";
    }
    (0, log_1.log)(`Logging ${name}:`);
    let i = 1;
    for (const collectibleType of collectibleTypes) {
        const collectibleName = (0, collectibles_1.getCollectibleName)(collectibleType);
        (0, log_1.log)(`${i}) ${collectibleName} (${collectibleType})`);
        i++;
    }
}
exports.logCollectibleTypes = logCollectibleTypes;
/**
 * Helper function to log a `Color` object.
 *
 * @param color The `Color` object to log.
 * @param name Optional. The name of the object, which will be logged before the properties.
 */
function logColor(color, name) {
    if (name === undefined) {
        name = "color";
    }
    (0, log_1.log)(`Logging ${name}: R${color.R}, G${color.G}, B${color.B}, A${color.A}, RO${color.RO}, BO${color.BO}, GO${color.GO}`);
}
exports.logColor = logColor;
/** Helper function to log every damage flag that is turned on. Useful when debugging. */
function logDamageFlags(damageFlags) {
    logFlags(damageFlags, isaac_typescript_definitions_1.DamageFlag, "damage");
}
exports.logDamageFlags = logDamageFlags;
/** Helper function to log every display flag that is turned on. Useful when debugging. */
function logDisplayFlags(displayFlags) {
    logFlags(displayFlags, isaac_typescript_definitions_1.DisplayFlag, "display");
}
exports.logDisplayFlags = logDisplayFlags;
/** Helper function to log every entity flag that is turned on. Useful when debugging. */
function logEntityFlags(entityFlags) {
    logFlags(entityFlags, isaac_typescript_definitions_1.EntityFlag, "entity");
}
exports.logEntityFlags = logEntityFlags;
function logEntityID(entity) {
    const entityID = (0, entities_1.getEntityID)(entity);
    (0, log_1.log)(`Logging entity: ${entityID}`);
}
exports.logEntityID = logEntityID;
/** Helper function for logging every flag that is turned on. Useful when debugging. */
function logFlags(flags, flagEnum, description = "") {
    if (description !== "") {
        description = "flag";
    }
    // eslint-disable-next-line @typescript-eslint/no-base-to-string
    (0, log_1.log)(`Logging ${description} values for: ${flags}`);
    let hasNoFlags = true;
    const entries = (0, enums_1.getEnumEntries)(flagEnum);
    for (const [key, value] of entries) {
        if ((0, flag_1.hasFlag)(flags, value)) {
            // eslint-disable-next-line @typescript-eslint/no-base-to-string
            (0, log_1.log)(`  Has flag: ${key} (${value})`);
            hasNoFlags = false;
        }
    }
    if (hasNoFlags) {
        (0, log_1.log)("  n/a (no flags)");
    }
}
exports.logFlags = logFlags;
/** Helper function for logging every game state flag that is turned on. Useful when debugging. */
function logGameStateFlags() {
    (0, log_1.log)("Logging game state flags:");
    const gameStateFlagEntries = (0, enums_1.getEnumEntries)(isaac_typescript_definitions_1.GameStateFlag);
    let hasNoFlags = true;
    for (const [key, gameStateFlag] of gameStateFlagEntries) {
        const flagValue = cachedClasses_1.game.GetStateFlag(gameStateFlag);
        if (flagValue) {
            (0, log_1.log)(`  Has flag: ${key} (${gameStateFlag})`);
            hasNoFlags = false;
        }
    }
    if (hasNoFlags) {
        (0, log_1.log)("  n/a (no flags)");
    }
}
exports.logGameStateFlags = logGameStateFlags;
/**
 * Helper function to log the names of a item pool type array.
 *
 * @param itemPoolTypes The item pool types to log.
 * @param name Optional. The name of the array, which will be logged before the elements.
 */
function logItemPoolTypes(itemPoolTypes, name) {
    if (name === undefined) {
        name = "item pool types";
    }
    (0, log_1.log)(`Logging ${name}:`);
    let i = 1;
    for (const itemPoolType of itemPoolTypes) {
        const itemPoolName = (0, itemPool_1.getItemPoolName)(itemPoolType);
        (0, log_1.log)(`${i}) ${itemPoolName} (${itemPoolType})`);
        i++;
    }
}
exports.logItemPoolTypes = logItemPoolTypes;
/**
 * Helper function to log a `KColor` object.
 *
 * @param kColor The `KColor` object to log.
 * @param name Optional. The name of the object, which will be logged before the properties.
 */
function logKColor(kColor, name) {
    if (name === undefined) {
        name = "KColor";
    }
    (0, log_1.log)(`Logging ${name}: R${kColor.Red}, G${kColor.Green}, B${kColor.Blue}, A${kColor.Alpha}`);
}
exports.logKColor = logKColor;
/** Helper function for logging every level state flag that is turned on. Useful when debugging. */
function logLevelStateFlags() {
    const level = cachedClasses_1.game.GetLevel();
    const levelStateFlagEntries = (0, enums_1.getEnumEntries)(isaac_typescript_definitions_1.LevelStateFlag);
    (0, log_1.log)("Logging level state flags:");
    let hasNoFlags = true;
    for (const [key, levelStateFlag] of levelStateFlagEntries) {
        const flagValue = level.GetStateFlag(levelStateFlag);
        if (flagValue) {
            (0, log_1.log)(`  Has flag: ${key} (${levelStateFlag})`);
            hasNoFlags = false;
        }
    }
    if (hasNoFlags) {
        (0, log_1.log)("  n/a (no flags)");
    }
}
exports.logLevelStateFlags = logLevelStateFlags;
/**
 * Helper function to log a TSTL `Map`.
 *
 * @param map The TSTL `Map` to log.
 * @param name Optional. The name of the map, which will be logged before the elements.
 */
function logMap(map, name) {
    if (!(0, tstlClass_1.isTSTLMap)(map) && !(0, tstlClass_1.isDefaultMap)(map)) {
        (0, log_1.log)("Tried to log a TSTL map, but the given object was not a TSTL map.");
        return;
    }
    const suffix = name === undefined ? "" : ` "${name}"`;
    (0, log_1.log)(`Logging a TSTL map${suffix}:`);
    const mapKeys = [...map.keys()];
    mapKeys.sort(sort_1.sortNormal);
    for (const key of mapKeys) {
        const value = map.get(key);
        (0, log_1.log)(`  ${key} --> ${value}`);
    }
    (0, log_1.log)(`  The size of the map was: ${map.size}`);
}
exports.logMap = logMap;
function logMusic() {
    const currentMusic = cachedClasses_1.musicManager.GetCurrentMusicID();
    (0, log_1.log)(`Currently playing music track: ${isaac_typescript_definitions_1.Music[currentMusic]} (${currentMusic})`);
}
exports.logMusic = logMusic;
function logPlayerEffects(player) {
    const effects = (0, playerEffects_1.getEffectsList)(player);
    (0, log_1.log)("Logging player effects:");
    if (effects.length === 0) {
        (0, log_1.log)("  n/a (no effects)");
        return;
    }
    for (const [i, effect] of effects.entries()) {
        let effectDescription;
        if (effect.Item.IsCollectible()) {
            const collectibleName = (0, collectibles_1.getCollectibleName)(effect.Item.ID);
            effectDescription = `Collectible: ${collectibleName}`;
        }
        else if (effect.Item.IsTrinket()) {
            const trinketName = (0, trinkets_1.getTrinketName)(effect.Item.ID);
            effectDescription = `Trinket: ${trinketName}`;
        }
        else if (effect.Item.IsNull()) {
            effectDescription = `Null item: ${effect.Item.ID}`;
        }
        else {
            effectDescription = `Unknown type of effect: ${effect.Item.ID}`;
        }
        (0, log_1.log)(`  ${i + 1}) ${effectDescription} (x${effect.Count})`);
    }
}
exports.logPlayerEffects = logPlayerEffects;
function logPlayerHealth(player) {
    const playerName = (0, players_1.getPlayerName)(player);
    const playerHealth = (0, playerHealth_1.getPlayerHealth)(player);
    (0, log_1.log)(`Player health for ${playerName}:`);
    (0, log_1.log)(`  Max hearts: ${playerHealth.maxHearts}`);
    (0, log_1.log)(`  Hearts: ${playerHealth.hearts}`);
    (0, log_1.log)(`  Eternal hearts: ${playerHealth.eternalHearts}`);
    (0, log_1.log)(`  Soul hearts: ${playerHealth.soulHearts}`);
    (0, log_1.log)(`  Bone hearts: ${playerHealth.boneHearts}`);
    (0, log_1.log)(`  Golden hearts: ${playerHealth.goldenHearts}`);
    (0, log_1.log)(`  Rotten hearts: ${playerHealth.rottenHearts}`);
    (0, log_1.log)(`  Broken hearts: ${playerHealth.brokenHearts}`);
    (0, log_1.log)(`  Soul charges: ${playerHealth.soulCharges}`);
    (0, log_1.log)(`  Blood charges: ${playerHealth.bloodCharges}`);
    (0, log_1.log)("  Soul heart types: [");
    for (const soulHeartType of playerHealth.soulHeartTypes) {
        (0, log_1.log)(`    HeartSubType.${isaac_typescript_definitions_1.HeartSubType[soulHeartType]}`);
    }
    (0, log_1.log)("  ]");
}
exports.logPlayerHealth = logPlayerHealth;
/** Helper function for logging every projectile flag that is turned on. Useful when debugging. */
function logProjectileFlags(projectileFlags) {
    logFlags(projectileFlags, isaac_typescript_definitions_1.ProjectileFlag, "projectile");
}
exports.logProjectileFlags = logProjectileFlags;
/** Helper function for logging information about the current room. */
function logRoom() {
    const bossID = (0, bosses_1.getBossID)();
    const roomGridIndex = (0, roomData_1.getRoomGridIndex)();
    const roomListIndex = (0, roomData_1.getRoomListIndex)();
    const roomData = (0, roomData_1.getRoomData)();
    (0, log_1.log)("Logging room information:");
    (0, log_1.log)(`- Room stage ID: ${isaac_typescript_definitions_1.StageID[roomData.StageID]} (roomData.StageID)`);
    (0, log_1.log)(`- Room type: ${isaac_typescript_definitions_1.RoomType[roomData.Type]} (${roomData.Type})`);
    (0, log_1.log)(`- Variant: ${roomData.Variant}`);
    (0, log_1.log)(`- Sub-type: ${roomData.Subtype}`);
    (0, log_1.log)(`- Name: ${roomData.Name}`);
    const roomGridIndexName = isaac_typescript_definitions_1.GridRoom[roomGridIndex];
    if (roomGridIndexName === undefined) {
        (0, log_1.log)(`- Grid index: ${roomGridIndex}`);
    }
    else {
        (0, log_1.log)(`- Grid index: ${roomGridIndexName} (${roomGridIndex})`);
    }
    (0, log_1.log)(`- List index: ${roomListIndex}`);
    if (bossID === undefined) {
        (0, log_1.log)("- Boss ID: undefined");
    }
    else {
        (0, log_1.log)(`- Boss ID: ${isaac_typescript_definitions_1.BossID[bossID]} (${bossID})`);
    }
}
exports.logRoom = logRoom;
/**
 * Helper function for logging every seed effect (i.e. Easter Egg) that is turned on for the
 * particular run.
 */
function logSeedEffects() {
    const seeds = cachedClasses_1.game.GetSeeds();
    const seedEffectEntries = (0, enums_1.getEnumEntries)(isaac_typescript_definitions_1.SeedEffect);
    (0, log_1.log)("Logging seed effects:");
    let hasNoSeedEffects = true;
    for (const [key, seedEffect] of seedEffectEntries) {
        if (seeds.HasSeedEffect(seedEffect)) {
            (0, log_1.log)(`  ${key} (${seedEffect})`);
            hasNoSeedEffects = false;
        }
    }
    if (hasNoSeedEffects) {
        (0, log_1.log)("  n/a (no seed effects)");
    }
}
exports.logSeedEffects = logSeedEffects;
/**
 * Helper function to log a TSTL `Set`.
 *
 * @param set The TSTL `Set` to log.
 * @param name Optional. The name of the set, which will be logged before the elements.
 */
function logSet(set, name) {
    if (!(0, tstlClass_1.isTSTLSet)(set)) {
        (0, log_1.log)("Tried to log a TSTL set, but the given object was not a TSTL set.");
        return;
    }
    const suffix = name === undefined ? "" : ` "${name}"`;
    (0, log_1.log)(`Logging a TSTL set${suffix}:`);
    const setValues = (0, set_1.getSortedSetValues)(set);
    for (const value of setValues) {
        (0, log_1.log)(`  Value: ${value}`);
    }
    (0, log_1.log)(`  The size of the set was: ${set.size}`);
}
exports.logSet = logSet;
/** Helper function for logging every sound effect that is currently playing. */
function logSounds() {
    const soundEffects = (0, enums_1.getEnumEntries)(isaac_typescript_definitions_1.SoundEffect);
    for (const [key, soundEffect] of soundEffects) {
        if (cachedClasses_1.sfxManager.IsPlaying(soundEffect)) {
            (0, log_1.log)(`Currently playing sound effect: ${key} (${soundEffect})`);
        }
    }
}
exports.logSounds = logSounds;
/**
 * Helper function for logging every key and value of a Lua table. This is a deep log; the function
 * will recursively call itself if it encounters a table within a table.
 *
 * This function will only work on tables that have string keys (because it logs the keys in order,
 * instead of randomly). It will throw a run-time error if it encounters a non-string key.
 *
 * In order to prevent infinite recursion, this function will not log a sub-table when there are 10
 * or more parent tables.
 */
function logTable(luaTable, parentTables = 0) {
    if (parentTables === 0) {
        (0, log_1.log)("Logging a Lua table:", false);
    }
    else if (parentTables > 10) {
        return;
    }
    const numSpaces = (parentTables + 1) * 2; // 2, 4, 6, etc.
    const indentation = " ".repeat(numSpaces);
    if (!(0, types_1.isTable)(luaTable)) {
        (0, log_1.log)(`${indentation}n/a (encountered a variable of type "${typeof luaTable}" instead of a table)`, false);
        return;
    }
    let numElements = 0;
    (0, table_1.iterateTableInOrder)(luaTable, (key, value) => {
        // eslint-disable-next-line @typescript-eslint/no-base-to-string
        (0, log_1.log)(`${indentation}${key} --> ${value}`, false);
        if ((0, types_1.isTable)(value)) {
            if (key === "__class") {
                (0, log_1.log)(`${indentation}  (skipping enumerating this key to avoid infinite recursion)`, false);
            }
            else {
                logTable(value, parentTables + 1);
            }
        }
        numElements++;
    });
    (0, log_1.log)(`${indentation}The size of the table was: ${numElements}`, false);
}
exports.logTable = logTable;
/**
 * Helper function to log the differences between the entries of two tables. Note that this will
 * only do a shallow comparison.
 */
function logTableDifferences(table1, table2) {
    (0, log_1.log)("Comparing two Lua tables:");
    const table1Keys = Object.keys(table1);
    const table1KeysSet = new ReadonlySet_1.ReadonlySet(table1Keys);
    const table2Keys = Object.keys(table2);
    const table2KeysSet = new ReadonlySet_1.ReadonlySet(table2Keys);
    const keysSet = (0, set_1.combineSets)(table1KeysSet, table2KeysSet);
    const keys = [...keysSet.values()];
    keys.sort(); // eslint-disable-line @typescript-eslint/require-array-sort-compare
    for (const key of keys) {
        const value1 = table1.get(key);
        const value2 = table2.get(key);
        if (value1 === undefined) {
            // eslint-disable-next-line @typescript-eslint/no-base-to-string
            (0, log_1.log)(`  Table 1 is missing key: ${key}`);
        }
        if (value2 === undefined) {
            // eslint-disable-next-line @typescript-eslint/no-base-to-string
            (0, log_1.log)(`  Table 2 is missing key: ${key}`);
        }
        if (value1 !== value2) {
            // eslint-disable-next-line @typescript-eslint/no-base-to-string
            (0, log_1.log)(`  ${key} --> "${value1}" versus "${value2}"`);
        }
    }
}
exports.logTableDifferences = logTableDifferences;
/**
 * Helper function to log the keys of a Lua table. This is not a deep log; only the keys of the
 * top-most table will be logged.
 *
 * This function is useful for tables that have recursive references.
 */
function logTableKeys(luaTable) {
    (0, log_1.log)("Logging the keys of a Lua table:");
    if (!(0, types_1.isTable)(luaTable)) {
        (0, log_1.log)(`  n/a (encountered a variable of type "${typeof luaTable}" instead of a table)`);
        return;
    }
    let numElements = 0;
    (0, table_1.iterateTableInOrder)(luaTable, (key) => {
        // eslint-disable-next-line @typescript-eslint/no-base-to-string
        (0, log_1.log)(`${key}`);
        numElements++;
    });
    (0, log_1.log)(`  The size of the table was: ${numElements}`);
}
exports.logTableKeys = logTableKeys;
/**
 * Helper function to log every table key and value. This is a shallow log; the function will not
 * recursively traverse sub-tables.
 *
 * This function will only work on tables that have string keys (because it logs the keys in order,
 * instead of randomly). It will throw a run-time error if it encounters a non-string key.
 */
function logTableShallow(luaTable) {
    (0, log_1.log)("Logging a Lua table (shallow):", false);
    if (!(0, types_1.isTable)(luaTable)) {
        (0, log_1.log)(`n/a (encountered a variable of type "${typeof luaTable}" instead of a table)`, false);
        return;
    }
    let numElements = 0;
    const indentation = "  ";
    (0, table_1.iterateTableInOrder)(luaTable, (key, value) => {
        // eslint-disable-next-line @typescript-eslint/no-base-to-string
        (0, log_1.log)(`${indentation}${key} --> ${value}`, false);
        numElements++;
    });
    (0, log_1.log)(`${indentation}The size of the table was: ${numElements}`, false);
}
exports.logTableShallow = logTableShallow;
/** Helper function for log every tear flag that is turned on. Useful when debugging. */
function logTearFlags(tearFlags) {
    logFlags(tearFlags, isaac_typescript_definitions_1.TearFlag, "tear");
}
exports.logTearFlags = logTearFlags;
/** Helper function for printing out every use flag that is turned on. Useful when debugging. */
function logUseFlags(useFlags) {
    logFlags(useFlags, isaac_typescript_definitions_1.UseFlag, "use");
}
exports.logUseFlags = logUseFlags;
/**
 * Helper function to enumerate all of the properties of a "userdata" object (i.e. an object from
 * the Isaac API).
 */
function logUserdata(userdata) {
    if (!(0, types_1.isUserdata)(userdata)) {
        (0, log_1.log)("Userdata: [not userdata]");
        return;
    }
    const metatable = getmetatable(userdata);
    if (metatable === undefined) {
        (0, log_1.log)("Userdata: [no metatable]");
        return;
    }
    const classType = (0, isaacAPIClass_1.getIsaacAPIClassName)(userdata);
    if (classType === undefined) {
        (0, log_1.log)("Userdata: [no class type]");
    }
    else {
        (0, log_1.log)(`Userdata: ${classType}`);
    }
    logTable(metatable);
}
exports.logUserdata = logUserdata;
/**
 * Helper function to log a `Vector` object.
 *
 * @param vector The `Vector` object to log.
 * @param name Optional. The name of the object, which will be logged before the properties.
 * @param round Optional. If true, will round the vector values to the nearest integer. Default is
 *              false.
 */
function logVector(vector, name, round = false) {
    if (name === undefined) {
        name = "vector";
    }
    const vectorString = (0, vector_1.vectorToString)(vector, round);
    (0, log_1.log)(`Logging ${name}: ${vectorString}`);
}
exports.logVector = logVector;
