"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Log_1 = require("../core/Log");
const Utilities_1 = require("../core/Utilities");
const LevelDb_1 = require("../minecraft/LevelDb");
const MCWorld_1 = require("../minecraft/MCWorld");
const WorldLevelDat_1 = require("../minecraft/WorldLevelDat");
const StorageUtilities_1 = require("../storage/StorageUtilities");
const IProjectData_1 = require("./IProjectData");
const IProjectItemData_1 = require("./IProjectItemData");
const minecraftIndicatorFolderNames = [
    "development_behavior_packs",
    "development_resource_packs",
    "development_resource_packs",
    "behavior_packs",
    "resource_packs",
    "behavior_pack",
    "resource_pack",
    "world_templates",
    "com.mojang",
    "minecraftworlds",
    "worlds",
    "world",
];
class ProjectIntegrator {
    candidateFiles = {};
    worlds = [];
    isInErrorState;
    errorMessages;
    _pushError(message, contextIn) {
        this.isInErrorState = true;
        if (this.errorMessages === undefined) {
            this.errorMessages = [];
        }
        message = message + contextIn ? " " + contextIn : "";
        Log_1.default.error(message);
        this.errorMessages.push({
            message: message,
            context: contextIn,
        });
        return message;
    }
    static async createProjectFromFolder(carto, newProjectName, folder, operationDescriptor, log) {
        let newProject = await carto.createNewProject(newProjectName, undefined, undefined, undefined, IProjectData_1.ProjectFocus.general, true, IProjectData_1.ProjectScriptLanguage.typeScript);
        await this.extendProjectFromCollectionFolder(carto, newProject, folder, operationDescriptor, log);
        await carto.save();
        return newProject;
    }
    static async extendProjectFromCollectionFolder(carto, project, folder, operationDescriptor, log) {
        // assume everything else is "loose files" that need to be reconstructed
        const pi = new ProjectIntegrator();
        await pi.addFromFolder(project, folder, true, log);
        await pi.processResults(project, operationDescriptor, log);
        await project.inferProjectItemsFromFiles(true);
    }
    async attachWorldDataToWorlds() {
        for (const world of this.worlds) {
            if (world.currentManifest) {
                if (world.manifestFiles.length > 0) {
                    await this.addToWorldByFileIds(world);
                }
            }
        }
        for (const world of this.worlds) {
            if (world.currentManifest) {
                if (world.manifestFiles.length > 0) {
                    await this.findLevelDat(world);
                }
            }
        }
    }
    async processResults(project, operationDescriptor, log) {
        for (const world of this.worlds) {
            // find our MANIFEST
            if (world.currentManifest) {
                for (const fileName in this.candidateFiles) {
                    const tokenToLookFor = world.currentManifest.substring(2).trim();
                    const file = this.candidateFiles[fileName];
                    if (file &&
                        !file.isUsed &&
                        file.type === IProjectItemData_1.ProjectItemType.levelDbManifest &&
                        fileName.indexOf(tokenToLookFor) >= 0) {
                        file.isUsed = true;
                        world.manifestFiles.push(file);
                        break;
                    }
                }
                if (world.manifestFiles.length > 0) {
                    await this.addToWorldByManifestData(world);
                }
            }
        }
        await this.attachWorldDataToWorlds();
        // now that we've attached as many LDBs/LOGs to the "good" manifests connected to CURRENTs,
        // let's see if we can make more worlds out of 'leftover' MANIFEST files (those without CURRENTs)
        // in some cases their CURRENT might be missing, or in other cases they are older versions of "good" manifests
        for (const fileName in this.candidateFiles) {
            const file = this.candidateFiles[fileName];
            if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbManifest) {
                file.isUsed = true;
                const world = {
                    manifestFiles: [file],
                    ldbFiles: [],
                    logFiles: [],
                };
                if (world.manifestFiles.length > 0) {
                    await this.addToWorldByManifestData(world);
                }
                this.worlds.push(world);
            }
        }
        await this.attachWorldDataToWorlds();
        const leftoverWorld = {
            manifestFiles: [],
            ldbFiles: [],
            logFiles: [],
        };
        for (const fileName in this.candidateFiles) {
            const file = this.candidateFiles[fileName];
            if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbLdb) {
                file.isUsed = true;
                leftoverWorld.ldbFiles.push(file);
            }
            if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDbLog) {
                file.isUsed = true;
                leftoverWorld.logFiles.push(file);
            }
            if (file && !file.isUsed && file.type === IProjectItemData_1.ProjectItemType.levelDat && leftoverWorld.levelDatFile === undefined) {
                file.isUsed = true;
                leftoverWorld.levelDatFile = file;
            }
        }
        if (leftoverWorld.ldbFiles.length > 0 || leftoverWorld.logFiles.length > 0) {
            this.worlds.push(leftoverWorld);
        }
        await project.ensureLoadedProjectFolder();
        if (project.projectFolder) {
            let worldsFolder = await project.ensureWorldContainer();
            if (worldsFolder) {
                let index = 0;
                for (const world of this.worlds) {
                    await this.copyFilesToWorld(world, project, worldsFolder, index);
                    index++;
                }
            }
        }
        project.appendErrors(this, operationDescriptor);
    }
    async addToWorldByManifestData(world) {
        let keySets = [];
        let logIndices = [];
        let ldbIndices = [];
        for (const manifestFile of world.manifestFiles) {
            if (manifestFile.levelDbSubset &&
                manifestFile.levelDbSubset.newFileNumber &&
                manifestFile.levelDbSubset.newFileSmallest &&
                manifestFile.levelDbSubset.newFileLargest) {
                for (let i = 0; i < manifestFile.levelDbSubset.newFileSmallest.length; i++) {
                    const smallest = manifestFile.levelDbSubset.newFileSmallest[i];
                    const largest = manifestFile.levelDbSubset.newFileLargest[i];
                    if (smallest && largest) {
                        keySets.push({ smallest: smallest, largest: largest });
                    }
                }
                for (let i = 0; i < manifestFile.levelDbSubset.newFileNumber.length; i++) {
                    const index = manifestFile.levelDbSubset.newFileNumber[i];
                    if (!manifestFile.levelDbSubset.deletedFileNumber ||
                        !manifestFile.levelDbSubset.deletedFileNumber.includes(index)) {
                        ldbIndices.push(index);
                    }
                }
                if (manifestFile.levelDbSubset.logNumber !== undefined) {
                    logIndices.push(manifestFile.levelDbSubset.logNumber);
                }
            }
        }
        for (const fileName in this.candidateFiles) {
            const file = this.candidateFiles[fileName];
            if (!file.isUsed &&
                (file.type === IProjectItemData_1.ProjectItemType.levelDbLdb || file.type === IProjectItemData_1.ProjectItemType.levelDbLog) &&
                file.levelDbSubset) {
                for (const keySet of keySets) {
                    if (file.levelDbSubset.keys[keySet.largest] !== undefined &&
                        file.levelDbSubset.keys[keySet.smallest] !== undefined) {
                        if (file.type === IProjectItemData_1.ProjectItemType.levelDbLog) {
                            file.isUsed = true;
                            world.logFiles.push(file);
                            break;
                        }
                        else if (file.type === IProjectItemData_1.ProjectItemType.levelDbLdb) {
                            file.isUsed = true;
                            world.ldbFiles.push(file);
                            break;
                        }
                    }
                }
            }
        }
    }
    async addToWorldByFileIds(world) {
        let logIndices = [];
        let ldbIndices = [];
        for (const manifestFile of world.manifestFiles) {
            if (manifestFile.levelDbSubset && manifestFile.levelDbSubset.newFileNumber) {
                for (let i = 0; i < manifestFile.levelDbSubset.newFileNumber.length; i++) {
                    const index = manifestFile.levelDbSubset.newFileNumber[i];
                    if (!manifestFile.levelDbSubset.deletedFileNumber ||
                        !manifestFile.levelDbSubset.deletedFileNumber.includes(index)) {
                        ldbIndices.push(index);
                    }
                }
                if (manifestFile.levelDbSubset.logNumber !== undefined) {
                    logIndices.push(manifestFile.levelDbSubset.logNumber);
                }
            }
        }
        for (const fileName in this.candidateFiles) {
            const file = this.candidateFiles[fileName];
            if (!file.isUsed &&
                file.type === IProjectItemData_1.ProjectItemType.levelDbLdb &&
                file.levelDbSubset &&
                file.index !== undefined &&
                ldbIndices.includes(file.index)) {
                file.isUsed = true;
                world.ldbFiles.push(file);
            }
            else if (!file.isUsed &&
                file.type === IProjectItemData_1.ProjectItemType.levelDbLog &&
                file.levelDbSubset &&
                file.index !== undefined &&
                logIndices.includes(file.index)) {
                file.isUsed = true;
                world.logFiles.push(file);
            }
        }
    }
    async findLevelDat(world) {
        if ((world.ldbFiles.length > 0 || world.logFiles.length > 0) && world.manifestFiles.length > 0) {
            for (const file of world.ldbFiles) {
                if (file && file.levelDbSubset) {
                    const mcworld = new MCWorld_1.default();
                    await mcworld.loadFromLevelDb(file.levelDbSubset);
                    if (mcworld.generationSeed) {
                        world.seedFromLevelDb = mcworld.generationSeed;
                        break;
                    }
                }
            }
            if (!world.seedFromLevelDb) {
                for (const file of world.logFiles) {
                    if (file && file.levelDbSubset) {
                        const mcworld = new MCWorld_1.default();
                        await mcworld.loadFromLevelDb(file.levelDbSubset);
                        if (mcworld.generationSeed) {
                            world.seedFromLevelDb = mcworld.generationSeed;
                            break;
                        }
                    }
                }
            }
            if (world.seedFromLevelDb) {
                for (const fileName in this.candidateFiles) {
                    const file = this.candidateFiles[fileName];
                    if (!file.isUsed &&
                        file.type === IProjectItemData_1.ProjectItemType.levelDat &&
                        file.levelDat &&
                        file.levelDat.randomSeed === world.seedFromLevelDb) {
                        file.isUsed = true;
                        world.levelDatFile = file;
                    }
                }
            }
        }
    }
    async copyFilesToWorld(world, project, worldsFolder, index) {
        if (world.ldbFiles.length > 0 || world.logFiles.length > 0) {
            // && world.manifestFiles.length > 0) {
            let name = "world" + index;
            if (world.levelDatFile && world.levelDatFile.levelDat?.levelName) {
                name = world.levelDatFile.levelDat?.levelName;
                let worldIndex = 0;
                // ensure folder name is unique
                while (worldsFolder.folders[name] !== undefined) {
                    worldIndex++;
                    name = world.levelDatFile.levelDat?.levelName + " " + worldIndex;
                }
            }
            const worldFolder = worldsFolder.ensureFolder(name);
            const newLevelDataFile = worldFolder.ensureFile("level.dat");
            const newLevelDat = new WorldLevelDat_1.default();
            newLevelDat.ensureDefaults();
            newLevelDat.levelName = name;
            if (world.levelDatFile &&
                world.levelDatFile.file.content &&
                world.levelDatFile.file.content instanceof Uint8Array) {
                // we could probably just newLevelDat = world.levelDatFile.levelDat
                // but we're going to modify it so use the new copy.
                newLevelDat.loadFromNbtBytes(world.levelDatFile.file.content);
            }
            // note that we are re-writing level.dat rather than just copying it.
            // add experiments flag
            newLevelDat.experimentsEverUsed = true;
            newLevelDat.savedWithToggledExperiments = true;
            newLevelDat.persist();
            const levelDatBytes = newLevelDat.getBytes();
            if (levelDatBytes !== undefined) {
                newLevelDataFile.setContent(levelDatBytes);
            }
            else if (world.levelDatFile &&
                world.levelDatFile.file.content &&
                world.levelDatFile.file.content instanceof Uint8Array) {
                newLevelDataFile.setContent(world.levelDatFile.file.content);
            }
            const levelNameFile = worldFolder.ensureFile("levelname.txt");
            levelNameFile.setContent(name);
            let dbFolder = worldFolder.ensureFolder("db");
            for (const file of world.ldbFiles) {
                if (file && file.index && file.file.content) {
                    const newFileName = Utilities_1.default.frontPadToLength(file.index, 6, "0");
                    const newLdbFile = dbFolder.ensureFile(newFileName + ".ldb");
                    newLdbFile.setContent(file.file.content);
                }
            }
            for (const file of world.logFiles) {
                if (file && file.index && file.file.content) {
                    const newFileName = Utilities_1.default.frontPadToLength(file.index, 6, "0");
                    const newLogFile = dbFolder.ensureFile(newFileName + ".log");
                    newLogFile.setContent(file.file.content);
                }
            }
            let manifestFileName = world.currentManifest;
            for (const file of world.manifestFiles) {
                if (file && file.index && file.file.content) {
                    const newFileName = "MANIFEST-" + Utilities_1.default.frontPadToLength(file.index, 6, "0");
                    if (!manifestFileName) {
                        manifestFileName = newFileName;
                    }
                    const newManifestFile = dbFolder.ensureFile(newFileName);
                    newManifestFile.setContent(file.file.content);
                }
            }
            const currentFile = dbFolder.ensureFile("CURRENT");
            currentFile.setContent(manifestFileName + "\r\n");
        }
    }
    async addFromFolder(project, sourceFolder, processSubFolders, log) {
        await sourceFolder.load();
        const files = [];
        for (const filePath in sourceFolder.files) {
            const file = sourceFolder.files[filePath];
            if (file) {
                files.push(file);
            }
        }
        await this.addLooseFiles(project, files, log);
        if (processSubFolders) {
            // first pass: see if the root folder contains any structured 'known' Minecraft Folders
            for (const folderName in sourceFolder.folders) {
                const folderNameToLower = folderName.toLowerCase().trim();
                const childFolder = sourceFolder.folders[folderName];
                if (childFolder) {
                    if (minecraftIndicatorFolderNames.includes(folderNameToLower)) {
                        // handle what looks like special minecraft folders in some way
                    }
                }
            }
            for (const folderName in sourceFolder.folders) {
                const folderNameToLower = folderName.toLowerCase().trim();
                const childFolder = sourceFolder.folders[folderName];
                if (childFolder) {
                    if (!minecraftIndicatorFolderNames.includes(folderNameToLower)) {
                        // treat non-Minecraft indicated folders as loose files
                        if (childFolder) {
                            await this.addFromFolder(project, childFolder, true);
                        }
                    }
                }
            }
        }
    }
    async addLooseFiles(project, files, log) {
        for (const file of files) {
            const extension = StorageUtilities_1.default.getTypeFromName(file.name);
            if (file.name.indexOf("evel") >= 0 && extension === "dat") {
                await file.loadContent();
                if (file.content && file.content instanceof Uint8Array) {
                    const levelDat = new WorldLevelDat_1.default();
                    try {
                        levelDat.loadFromNbtBytes(file.content);
                        if (!levelDat.isInErrorState) {
                            this.candidateFiles[file.storageRelativePath] = {
                                file: file,
                                levelDat: levelDat,
                                type: IProjectItemData_1.ProjectItemType.levelDat,
                            };
                        }
                    }
                    catch (e) {
                        if (log) {
                            log("Error processing " + file.name + " (" + e.toString() + ")");
                        }
                    }
                }
            }
            else if (file.name.indexOf("RRENT") >= 0) {
                await this.considerCURRENTFile(file, log);
            }
            else if (file.name.indexOf("NIFEST") >= 0) {
                await this.considerMANIFESTFile(file, log);
            }
            else {
                switch (extension) {
                    case "ldb":
                        await file.loadContent();
                        if (file.isBinary) {
                            const levelDb = new LevelDb_1.default([file], [], [], file.name);
                            try {
                                await levelDb.init(log);
                                Utilities_1.default.appendErrors(this, levelDb);
                                if (!levelDb.isInErrorState) {
                                    this.candidateFiles[file.storageRelativePath] = {
                                        file: file,
                                        index: ProjectIntegrator.getIndexFromString(file.name),
                                        levelDbSubset: levelDb,
                                        type: IProjectItemData_1.ProjectItemType.levelDbLdb,
                                    };
                                }
                            }
                            catch (e) {
                                const error = this._pushError("Error processing ldb file " + file.name + " (" + e.toString() + ")");
                                if (log) {
                                    log(error);
                                }
                            }
                        }
                        break;
                    case "log":
                        await file.loadContent(false, StorageUtilities_1.EncodingType.ByteBuffer);
                        if (file.isBinary) {
                            const levelDb = new LevelDb_1.default([], [file], [], file.name);
                            try {
                                await levelDb.init(log);
                                Utilities_1.default.appendErrors(this, levelDb);
                                if (!levelDb.isInErrorState) {
                                    this.candidateFiles[file.storageRelativePath] = {
                                        file: file,
                                        index: ProjectIntegrator.getIndexFromString(file.name),
                                        levelDbSubset: levelDb,
                                        type: IProjectItemData_1.ProjectItemType.levelDbLog,
                                    };
                                }
                            }
                            catch (e) {
                                const error = this._pushError("Error processing log file " + file.name + " (" + e.toString() + ")");
                                if (log) {
                                    log(error);
                                }
                            }
                        }
                        break;
                }
            }
        }
    }
    async considerCURRENTFile(file, log) {
        await file.loadContent(false, StorageUtilities_1.EncodingType.Utf8String);
        if (file.content && typeof file.content === "string") {
            if (file.content.startsWith("MANIFEST")) {
                this.worlds.push({
                    currentFile: file,
                    currentManifest: file.content.trim(),
                    manifestFiles: [],
                    ldbFiles: [],
                    logFiles: [],
                });
            }
        }
    }
    static getIndexFromString(fileName) {
        fileName = StorageUtilities_1.default.getBaseFromName(fileName.trim());
        const lastDash = fileName.lastIndexOf("-");
        if (lastDash >= 0) {
            fileName = fileName.substring(lastDash + 1);
        }
        let zeroStart = -1;
        let adjustStr = "";
        let index = 0;
        for (const char of fileName) {
            if (char >= "1" && char <= "9") {
                if (zeroStart >= 0) {
                    adjustStr += char;
                }
            }
            else if (char === "0") {
                zeroStart = index;
                adjustStr += char;
            }
            else if (zeroStart >= 0) {
                break;
            }
            index++;
        }
        if (adjustStr.length > 4) {
            fileName = adjustStr;
        }
        try {
            const num = parseInt(fileName);
            if (isNaN(num)) {
                return undefined;
            }
            return num;
        }
        catch (e) { }
        return undefined;
    }
    async considerMANIFESTFile(file, log) {
        await file.loadContent(false, StorageUtilities_1.EncodingType.ByteBuffer);
        if (file.isBinary) {
            const levelDb = new LevelDb_1.default([], [], [file], file.name);
            try {
                await levelDb.init(log);
                Utilities_1.default.appendErrors(this, levelDb);
                if (!levelDb.isInErrorState) {
                    this.candidateFiles[file.storageRelativePath] = {
                        file: file,
                        index: ProjectIntegrator.getIndexFromString(file.name),
                        levelDbSubset: levelDb,
                        type: IProjectItemData_1.ProjectItemType.levelDbManifest,
                    };
                }
            }
            catch (e) {
                const error = this._pushError("Error processing MANIFEST file " + file.name + " (" + e.toString() + ")");
                if (log) {
                    log(error);
                }
            }
        }
    }
}
exports.default = ProjectIntegrator;

//# sourceMappingURL=../maps/app/ProjectIntegrator.js.map
