"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentLibrary = void 0;
const internal_1 = require("../internal");
const StorageUtils_1 = require("../Utilities/StorageUtils");
const concurrentSemaphore_1 = require("../Utilities/Concurrent/concurrentSemaphore");
/**
 * @internal
 * Synchronizes all dictionaries relating to the documents (and their statuses) in the corpus.
 */
class DocumentLibrary {
    constructor(corpus) {
        this.TAG = DocumentLibrary.name;
        this.allDocuments = [];
        this.concurrentReadLock = new concurrentSemaphore_1.ConcurrentSemaphore();
        this.corpus = corpus;
        this.docsCurrentlyIndexing = new Set();
        this.docsCurrentlyLoading = new Set();
        this.docsNotFound = new Set();
        this.pathLookup = new Map();
    }
    /**
     * @internal
     *
     * Adds a folder and document to the list of all documents in the corpus. Also adds the document path to the path lookup.
     * @param path The document path.
     * @param folder The folder.
     * @param doc The document
     */
    addDocumentPath(path, folder, doc) {
        if (!this.pathLookup.has(path)) {
            this.allDocuments.push([folder, doc]);
            this.pathLookup.set(path, [folder, doc]);
            folder.documentLookup.set(doc.name, doc);
        }
    }
    /**
     * @internal
     *
     * Removes a folder and document from the list of all documents in the corpus. Also removes the document path from the path lookup.
     * @param path The document path.
     * @param folder The folder.
     * @param doc The document.
     */
    removeDocumentPath(path, folder, doc) {
        if (this.pathLookup.has(path)) {
            this.pathLookup.delete(path);
            const index = this.allDocuments.indexOf([folder, doc]);
            this.allDocuments.splice(index, 1);
        }
    }
    /**
     * @internal
     *
     * Returns a list of all the documents that are not loaded.
     */
    listDocsNotIndexed(rootDoc, docsLoaded) {
        const docsNotIndexed = new Set();
        // gets all the documents that needs indexing and set the currentlyIndexing flag to true.
        docsLoaded.forEach(docPath => {
            const doc = this.fetchDocument(docPath);
            if (!doc) {
                return;
            }
            // The root document that started this indexing process is already masked for indexing, don't mark it again.
            if (doc != rootDoc) {
                if (this.markDocumentForIndexing(doc)) {
                    docsNotIndexed.add(doc);
                }
            }
            else {
                docsNotIndexed.add(doc);
            }
        });
        return docsNotIndexed;
    }
    /**
     * @internal
     *
     * Returns a list of all the documents in the corpus.
     */
    listAllDocuments() {
        const list = new Set();
        this.allDocuments.forEach((fd) => {
            list.add(fd[1]);
        });
        return list;
    }
    /**
     * @internal
     * Fetches a document from the path lookup.
     * @param path The document path.
     */
    fetchDocument(path) {
        if (!this.docsNotFound.has(path)) {
            const lookup = this.pathLookup.get(path);
            if (lookup) {
                const currentDoc = lookup[1];
                return currentDoc;
            }
        }
        return null;
    }
    /**
     * @internal
     *
     * Sets a document's status to loading if the document needs to be loaded.
     * @param docPath The document path.
     */
    needToLoadDocument(docPath, docsLoading) {
        const doc = this.pathLookup.has(docPath) ? this.pathLookup.get(docPath)[1] : undefined;
        // first check if the document was not found or is currently loading.
        // if the document was loaded previously, check if its imports were not indexed and it's not being indexed currently.
        const needToLoad = !this.docsNotFound.has(docPath) && !docsLoading.has(docPath) && (!doc || (!doc.importsIndexed && !doc.currentlyIndexing));
        if (needToLoad) {
            docsLoading.add(docPath);
        }
        return needToLoad;
    }
    /**
     * @internal
     *
     * Marks a document for indexing if it has loaded successfully, or adds it to the list of documents not found if it failed to load.
     * @param doc The document that was loaded.
     * @param docPath The document path.
     * @param docsNowLoaded The dictionary of documents that are now loaded.
     */
    markAsLoadedOrFailed(docPath, doc) {
        // Doc is no longer loading.
        this.docsCurrentlyLoading.delete(docPath);
        if (!doc) {
            // The doc failed to load, so set doc as not found.
            this.docsNotFound.add(docPath);
        }
    }
    /**
     * @internal
     *
     * Removes a document from the list of documents that are not indexed to mark it as indexed.
     * @param doc The document.
     */
    markDocumentAsIndexed(doc) {
        doc.currentlyIndexing = false;
        this.docsCurrentlyIndexing.delete(doc);
    }
    /**
     * @internal
     *
     * Adds a document to the list of documents that are not indexed to mark it for indexing.
     * @param doc The document.
     */
    markDocumentForIndexing(doc) {
        if (doc.needsIndexing && !doc.currentlyIndexing) {
            // If the document was not indexed before and it's not currently being indexed.
            this.docsCurrentlyIndexing.add(doc);
            doc.currentlyIndexing = true;
        }
        return doc.needsIndexing;
    }
    /**
     * @internal
     *
     * Whether a specific pair of folder-document exists in the list of all documents in the corpus.
     * @param fd The folder-document pair.
     */
    contains(fd) {
        for (let i = 0; i < this.allDocuments.length; i++) {
            if (this.allDocuments[i][0] === fd[0] && this.allDocuments[i][1] === fd[1]) {
                return true;
            }
        }
        return false;
    }
    /**
     * @internal
     */
    loadFolderOrDocument(objectPath, forceReload = false, resOpt = null) {
        return __awaiter(this, void 0, void 0, function* () {
            // If the document is already loaded and the user do not want to force a reload, return the document previously loaded.
            if (!forceReload && this.pathLookup.has(objectPath)) {
                const doc = this.pathLookup.get(objectPath)[1];
                return doc;
            }
            // Mark as loading.
            this.docsCurrentlyLoading.add(objectPath);
            // The document needs to be loaded. Create a task to load it and add to the list of documents currently loading.
            var task = () => __awaiter(this, void 0, void 0, function* () {
                var result = yield this._loadFolderOrDocument(objectPath, forceReload, resOpt);
                this.markAsLoadedOrFailed(objectPath, result);
                return result;
            });
            return yield task();
        });
    }
    /**
     * @internal
     */
    _loadFolderOrDocument(objectPath, forceReload = false, resOpt = null) {
        return __awaiter(this, void 0, void 0, function* () {
            // let bodyCode = () =>
            {
                if (!objectPath) {
                    return undefined;
                }
                // first check for namespace
                const pathTuple = StorageUtils_1.StorageUtils.splitNamespacePath(objectPath);
                if (!pathTuple) {
                    internal_1.Logger.error(this.corpus.ctx, this.TAG, this.loadFolderOrDocument.name, objectPath, internal_1.cdmLogCode.ErrPathNullObjectPath);
                    return undefined;
                }
                const namespace = pathTuple[0] || this.corpus.storage.defaultNamespace;
                objectPath = pathTuple[1];
                if (!objectPath.startsWith('/')) {
                    return undefined;
                }
                const namespaceFolder = this.corpus.storage.fetchRootFolder(namespace);
                const namespaceAdapter = this.corpus.storage.fetchAdapter(namespace);
                if (!namespaceFolder || !namespaceAdapter) {
                    internal_1.Logger.error(this.corpus.ctx, this.TAG, this.loadFolderOrDocument.name, objectPath, internal_1.cdmLogCode.ErrStorageNamespaceNotRegistered, namespace, objectPath);
                    return;
                }
                const lastFolder = namespaceFolder.fetchChildFolderFromPath(objectPath, false);
                // don't create new folders, just go as far as possible
                if (!lastFolder) {
                    return undefined;
                }
                // maybe the search is for a folder?
                const lastPath = lastFolder.folderPath;
                if (lastPath === objectPath) {
                    return lastFolder;
                }
                // remove path to folder and then look in the folder
                objectPath = objectPath.slice(lastPath.length);
                return lastFolder.fetchDocumentFromFolderPathAsync(objectPath, forceReload, resOpt);
            }
            // return p.measure(bodyCode);
        });
    }
}
exports.DocumentLibrary = DocumentLibrary;
//# sourceMappingURL=DocumentLibrary.js.map