"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDirInode = exports.isFileInode = exports.DirInode = exports.FileInode = exports.FileIndex = void 0;
var path = require("path");
var node_fs_stats_1 = require("../core/node_fs_stats");
/**
 * A simple class for storing a filesystem index. Assumes that all paths passed
 * to it are *absolute* paths.
 *
 * Can be used as a partial or a full index, although care must be taken if used
 * for the former purpose, especially when directories are concerned.
 */
var FileIndex = /** @class */ (function () {
    /**
     * Constructs a new FileIndex.
     */
    function FileIndex() {
        // _index is a single-level key,value store that maps *directory* paths to
        // DirInodes. File information is only contained in DirInodes themselves.
        this._index = {};
        // Create the root directory.
        this.addPath('/', new DirInode());
    }
    /**
     * Static method for constructing indices from a JSON listing.
     * @param listing Directory listing generated by tools/XHRIndexer.coffee
     * @return A new FileIndex object.
     */
    FileIndex.fromListing = function (listing) {
        var idx = new FileIndex();
        // Add a root DirNode.
        var rootInode = new DirInode();
        idx._index['/'] = rootInode;
        var queue = [['', listing, rootInode]];
        while (queue.length > 0) {
            var inode = void 0;
            var next = queue.pop();
            var pwd = next[0];
            var tree = next[1];
            var parent_1 = next[2];
            for (var node in tree) {
                if (tree.hasOwnProperty(node)) {
                    var children = tree[node];
                    var name_1 = "".concat(pwd, "/").concat(node);
                    if (children) {
                        idx._index[name_1] = inode = new DirInode();
                        queue.push([name_1, children, inode]);
                    }
                    else {
                        // This inode doesn't have correct size information, noted with -1.
                        inode = new FileInode(new node_fs_stats_1.default(node_fs_stats_1.FileType.FILE, -1, 0x16D));
                    }
                    if (parent_1) {
                        parent_1._ls[node] = inode;
                    }
                }
            }
        }
        return idx;
    };
    FileIndex.fromUnpkg = function (listing) {
        var idx = new FileIndex();
        function handleDir(dirPath, entry) {
            var dirInode = new DirInode();
            entry.files.forEach(function (child) {
                var inode;
                if (child.type === 'file') {
                    inode = new FileInode(new node_fs_stats_1.default(node_fs_stats_1.FileType.FILE, child.size));
                    // @ts-ignore
                    dirInode._ls[path.basename(child.path)] = inode;
                }
                else {
                    idx._index[child.path] = inode = handleDir(child.path, child);
                }
            });
            return dirInode;
        }
        idx._index['/'] = handleDir('/', listing);
        return idx;
    };
    FileIndex.fromJSDelivr = function (listing) {
        var idx = new FileIndex();
        listing.files.forEach(function (file) {
            var inode = new FileInode(new node_fs_stats_1.default(node_fs_stats_1.FileType.FILE, file.size));
            idx.addPathFast(file.name, inode);
        });
        return idx;
    };
    /**
     * Runs the given function over all files in the index.
     */
    FileIndex.prototype.fileIterator = function (cb) {
        for (var path_1 in this._index) {
            if (this._index.hasOwnProperty(path_1)) {
                var dir = this._index[path_1];
                var files = dir.getListing();
                for (var _i = 0, files_1 = files; _i < files_1.length; _i++) {
                    var file = files_1[_i];
                    var item = dir.getItem(file);
                    if (isFileInode(item)) {
                        cb(item.getData(), path_1 + '/' + file);
                    }
                }
            }
        }
    };
    /**
     * Adds the given absolute path to the index if it is not already in the index.
     * Creates any needed parent directories.
     * @param path The path to add to the index.
     * @param inode The inode for the
     *   path to add.
     * @return 'True' if it was added or already exists, 'false' if there
     *   was an issue adding it (e.g. item in path is a file, item exists but is
     *   different).
     * @todo If adding fails and implicitly creates directories, we do not clean up
     *   the new empty directories.
     */
    FileIndex.prototype.addPath = function (path, inode) {
        if (!inode) {
            throw new Error('Inode must be specified');
        }
        if (path[0] !== '/') {
            throw new Error('Path must be absolute, got: ' + path);
        }
        // Check if it already exists.
        if (this._index.hasOwnProperty(path)) {
            return this._index[path] === inode;
        }
        var splitPath = this._split_path(path);
        var dirpath = splitPath[0];
        var itemname = splitPath[1];
        // Try to add to its parent directory first.
        var parent = this._index[dirpath];
        if (parent === undefined && path !== '/') {
            // Create parent.
            parent = new DirInode();
            if (!this.addPath(dirpath, parent)) {
                return false;
            }
        }
        // Add myself to my parent.
        if (path !== '/') {
            if (!parent.addItem(itemname, inode)) {
                return false;
            }
        }
        // If I'm a directory, add myself to the index.
        if (isDirInode(inode)) {
            this._index[path] = inode;
        }
        return true;
    };
    /**
     * Adds the given absolute path to the index if it is not already in the index.
     * The path is added without special treatment (no joining of adjacent separators, etc).
     * Creates any needed parent directories.
     * @param path The path to add to the index.
     * @param inode The inode for the
     *   path to add.
     * @return 'True' if it was added or already exists, 'false' if there
     *   was an issue adding it (e.g. item in path is a file, item exists but is
     *   different).
     * @todo If adding fails and implicitly creates directories, we do not clean up
     *   the new empty directories.
     */
    FileIndex.prototype.addPathFast = function (path, inode) {
        var itemNameMark = path.lastIndexOf('/');
        var parentPath = itemNameMark === 0 ? "/" : path.substring(0, itemNameMark);
        var itemName = path.substring(itemNameMark + 1);
        // Try to add to its parent directory first.
        var parent = this._index[parentPath];
        if (parent === undefined) {
            // Create parent.
            parent = new DirInode();
            this.addPathFast(parentPath, parent);
        }
        if (!parent.addItem(itemName, inode)) {
            return false;
        }
        // If adding a directory, add to the index as well.
        if (inode.isDir()) {
            this._index[path] = inode;
        }
        return true;
    };
    /**
     * Removes the given path. Can be a file or a directory.
     * @return The removed item,
     *   or null if it did not exist.
     */
    FileIndex.prototype.removePath = function (path) {
        var splitPath = this._split_path(path);
        var dirpath = splitPath[0];
        var itemname = splitPath[1];
        // Try to remove it from its parent directory first.
        var parent = this._index[dirpath];
        if (parent === undefined) {
            return null;
        }
        // Remove myself from my parent.
        var inode = parent.remItem(itemname);
        if (inode === null) {
            return null;
        }
        // If I'm a directory, remove myself from the index, and remove my children.
        if (isDirInode(inode)) {
            var children = inode.getListing();
            for (var _i = 0, children_1 = children; _i < children_1.length; _i++) {
                var child = children_1[_i];
                this.removePath(path + '/' + child);
            }
            // Remove the directory from the index, unless it's the root.
            if (path !== '/') {
                delete this._index[path];
            }
        }
        return inode;
    };
    /**
     * Retrieves the directory listing of the given path.
     * @return An array of files in the given path, or 'null' if it does not exist.
     */
    FileIndex.prototype.ls = function (path) {
        var item = this._index[path];
        if (item === undefined) {
            return null;
        }
        return item.getListing();
    };
    /**
     * Returns the inode of the given item.
     * @return Returns null if the item does not exist.
     */
    FileIndex.prototype.getInode = function (path) {
        var splitPath = this._split_path(path);
        var dirpath = splitPath[0];
        var itemname = splitPath[1];
        // Retrieve from its parent directory.
        var parent = this._index[dirpath];
        if (parent === undefined) {
            return null;
        }
        // Root case
        if (dirpath === path) {
            return parent;
        }
        return parent.getItem(itemname);
    };
    /**
     * Split into a (directory path, item name) pair
     */
    FileIndex.prototype._split_path = function (p) {
        var dirpath = path.dirname(p);
        var itemname = p.substr(dirpath.length + (dirpath === "/" ? 0 : 1));
        return [dirpath, itemname];
    };
    return FileIndex;
}());
exports.FileIndex = FileIndex;
/**
 * Inode for a file. Stores an arbitrary (filesystem-specific) data payload.
 */
var FileInode = /** @class */ (function () {
    function FileInode(data) {
        this.data = data;
    }
    FileInode.prototype.isFile = function () { return true; };
    FileInode.prototype.isDir = function () { return false; };
    FileInode.prototype.getData = function () { return this.data; };
    FileInode.prototype.setData = function (data) { this.data = data; };
    return FileInode;
}());
exports.FileInode = FileInode;
/**
 * Inode for a directory. Currently only contains the directory listing.
 */
var DirInode = /** @class */ (function () {
    /**
     * Constructs an inode for a directory.
     */
    function DirInode(data) {
        if (data === void 0) { data = null; }
        this.data = data;
        this._ls = {};
    }
    DirInode.prototype.isFile = function () {
        return false;
    };
    DirInode.prototype.isDir = function () {
        return true;
    };
    DirInode.prototype.getData = function () { return this.data; };
    /**
     * Return a Stats object for this inode.
     * @todo Should probably remove this at some point. This isn't the
     *       responsibility of the FileIndex.
     */
    DirInode.prototype.getStats = function () {
        return new node_fs_stats_1.default(node_fs_stats_1.FileType.DIRECTORY, 4096, 0x16D);
    };
    /**
     * Returns the directory listing for this directory. Paths in the directory are
     * relative to the directory's path.
     * @return The directory listing for this directory.
     */
    DirInode.prototype.getListing = function () {
        return Object.keys(this._ls);
    };
    /**
     * Returns the inode for the indicated item, or null if it does not exist.
     * @param p Name of item in this directory.
     */
    DirInode.prototype.getItem = function (p) {
        var item = this._ls[p];
        return item && this._ls.hasOwnProperty(p) ? item : null;
    };
    /**
     * Add the given item to the directory listing. Note that the given inode is
     * not copied, and will be mutated by the DirInode if it is a DirInode.
     * @param p Item name to add to the directory listing.
     * @param inode The inode for the
     *   item to add to the directory inode.
     * @return True if it was added, false if it already existed.
     */
    DirInode.prototype.addItem = function (p, inode) {
        if (p in this._ls) {
            return false;
        }
        this._ls[p] = inode;
        return true;
    };
    /**
     * Removes the given item from the directory listing.
     * @param p Name of item to remove from the directory listing.
     * @return Returns the item
     *   removed, or null if the item did not exist.
     */
    DirInode.prototype.remItem = function (p) {
        var item = this._ls[p];
        if (item === undefined) {
            return null;
        }
        delete this._ls[p];
        return item;
    };
    return DirInode;
}());
exports.DirInode = DirInode;
/**
 * @hidden
 */
function isFileInode(inode) {
    return !!inode && inode.isFile();
}
exports.isFileInode = isFileInode;
/**
 * @hidden
 */
function isDirInode(inode) {
    return !!inode && inode.isDir();
}
exports.isDirInode = isDirInode;
//# sourceMappingURL=file_index.js.map