"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
 * @license
 * Copyright Google Inc. All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.io/license
 */
const core_1 = require("@angular-devkit/core");
const exception_1 = require("../exception/exception");
const action_1 = require("./action");
const entry_1 = require("./entry");
const interface_1 = require("./interface");
const recorder_1 = require("./recorder");
/**
 * The root class of most trees.
 */
class VirtualTree {
    constructor() {
        this._root = new Map();
        this._actions = new action_1.ActionList();
        this._cacheMap = new Map();
    }
    /**
     * Normalize the path. Made available to subclasses to overload.
     * @param path The path to normalize.
     * @returns {string} A path that is resolved and normalized.
     */
    _normalizePath(path) {
        return core_1.normalize('/' + path);
    }
    /**
     * A list of file names contained by this Tree.
     * @returns {[string]} File paths.
     */
    get files() {
        return [...new Set([...this._root.keys(), ...this._cacheMap.keys()]).values()];
    }
    get root() {
        return new Map(this._root);
    }
    get staging() {
        return new Map(this._cacheMap);
    }
    get(path) {
        const normalizedPath = this._normalizePath(path);
        return this._cacheMap.get(normalizedPath) || this._root.get(normalizedPath) || null;
    }
    has(path) {
        return this.get(path) != null;
    }
    set(entry) {
        return this._cacheMap.set(entry.path, entry);
    }
    exists(path) {
        return this.has(path);
    }
    read(path) {
        const entry = this.get(path);
        return entry ? entry.content : null;
    }
    beginUpdate(path) {
        const entry = this.get(path);
        if (!entry) {
            throw new exception_1.FileDoesNotExistException(path);
        }
        return new recorder_1.UpdateRecorderBase(entry);
    }
    commitUpdate(record) {
        if (record instanceof recorder_1.UpdateRecorderBase) {
            const path = record.path;
            const entry = this.get(path);
            if (!entry) {
                throw new exception_1.ContentHasMutatedException(path);
            }
            else {
                const newContent = record.apply(entry.content);
                this.overwrite(path, newContent);
            }
        }
        else {
            throw new exception_1.InvalidUpdateRecordException();
        }
    }
    overwrite(path, content) {
        const normalizedTo = this._normalizePath(path);
        if (typeof content == 'string') {
            content = new Buffer(content, 'utf-8');
        }
        const maybeEntry = this.get(normalizedTo);
        if (maybeEntry && maybeEntry.content.equals(content)) {
            return;
        }
        this._overwrite(normalizedTo, content);
    }
    create(path, content) {
        const normalizedTo = this._normalizePath(path);
        if (typeof content == 'string') {
            content = new Buffer(content);
        }
        this._create(normalizedTo, content);
    }
    rename(path, to) {
        const normalizedPath = this._normalizePath(path);
        const normalizedTo = this._normalizePath(to);
        if (normalizedPath === normalizedTo) {
            // Nothing to do.
            return;
        }
        this._rename(normalizedPath, normalizedTo);
    }
    delete(path) {
        this._delete(this._normalizePath(path));
    }
    _overwrite(path, content, action) {
        if (!this.has(path)) {
            throw new exception_1.FileDoesNotExistException(path);
        }
        // Update the action buffer.
        if (action) {
            this._actions.push(action);
        }
        else {
            this._actions.overwrite(path, content);
        }
        this.set(new entry_1.SimpleFileEntry(path, content));
    }
    _create(path, content, action) {
        if (this._cacheMap.has(path)) {
            throw new exception_1.FileAlreadyExistException(path);
        }
        if (action) {
            this._actions.push(action);
        }
        else {
            this._actions.create(path, content);
        }
        this.set(new entry_1.SimpleFileEntry(path, content));
    }
    _rename(path, to, action, force = false) {
        const entry = this.get(path);
        if (!entry) {
            throw new exception_1.FileDoesNotExistException(path);
        }
        if (this._cacheMap.has(to) && !force) {
            throw new exception_1.FileAlreadyExistException(to);
        }
        if (action) {
            this._actions.push(action);
        }
        else {
            this._actions.rename(path, to);
        }
        this.set(new entry_1.SimpleFileEntry(to, entry.content));
        this._cacheMap.delete(path);
    }
    _delete(path, action) {
        if (!this.has(path)) {
            throw new exception_1.FileDoesNotExistException(path);
        }
        if (action) {
            this._actions.push(action);
        }
        else {
            this._actions.delete(path);
        }
        this._cacheMap.delete(path);
    }
    apply(action, strategy) {
        if (this._actions.has(action)) {
            return;
        }
        switch (action.kind) {
            case 'o':
                // Update the action buffer.
                this._overwrite(action.path, action.content, action);
                break;
            case 'c':
                if (this._cacheMap.has(action.path)) {
                    switch (strategy) {
                        case interface_1.MergeStrategy.Error: throw new exception_1.MergeConflictException(action.path);
                        case interface_1.MergeStrategy.Overwrite:
                            this._overwrite(action.path, action.content, action);
                            break;
                    }
                }
                else {
                    this._create(action.path, action.content, action);
                }
                break;
            case 'r':
                const force = (strategy & interface_1.MergeStrategy.AllowOverwriteConflict) != 0;
                this._rename(action.path, action.to, action, force);
                break;
            case 'd':
                this._delete(action.path, action);
                break;
            default: throw new action_1.UnknownActionException(action);
        }
    }
    // Returns an ordered list of Action to get this host.
    get actions() {
        return [...this._actions];
    }
    /**
     * Allow subclasses to copy to a tree their own properties.
     * @return {Tree}
     * @private
     */
    _copyTo(tree) {
        tree._root = new Map(this._root);
        this._actions.forEach(action => tree._actions.push(action));
        [...this._cacheMap.entries()].forEach(([path, entry]) => {
            tree._cacheMap.set(path, entry);
        });
    }
    branch() {
        const newTree = new VirtualTree();
        this._copyTo(newTree);
        return newTree;
    }
    // Creates a new host from 2 hosts.
    merge(other, strategy = interface_1.MergeStrategy.Default) {
        other.actions.forEach(action => this.apply(action, strategy));
    }
    optimize() {
        // This destroys the history. Hope you know what you're doing.
        this._actions.optimize();
    }
    static branch(tree) {
        return tree.branch();
    }
    static merge(tree, other, strategy = interface_1.MergeStrategy.Default) {
        const newTree = tree.branch();
        newTree.merge(other, strategy);
        return newTree;
    }
    static optimize(tree) {
        const newTree = tree.branch();
        newTree.optimize();
        return newTree;
    }
}
exports.VirtualTree = VirtualTree;
//# sourceMappingURL=data:application/json;base64,[encoded data, 20704 characters]