"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const uniqueid_1 = require("../util/uniqueid");
const PATH_SEP = '/';
/**
 * Renders a hashed ID for a resource.
 *
 * In order to make sure logical IDs are unique and stable, we hash the resource
 * construct tree path (i.e. toplevel/secondlevel/.../myresource) and add it as
 * a suffix to the path components joined without a separator (CloudFormation
 * IDs only allow alphanumeric characters).
 *
 * The result will be:
 *
 *     <path.join('')><md5(path.join('/')>
 *         "human"          "hash"
 *
 * If the "human" part of the ID exceeds 240 characters, we simply trim it so
 * the total ID doesn't exceed CloudFormation's 255 character limit.
 *
 * We only take 8 characters from the md5 hash (0.000005 chance of collision).
 *
 * Special cases:
 *
 * - If the path only contains a single component (i.e. it's a top-level
 *   resource), we won't add the hash to it. The hash is not needed for
 *   disamiguation and also, it allows for a more straightforward migration an
 *   existing CloudFormation template to a CDK stack without logical ID changes
 *   (or renames).
 * - For aesthetic reasons, if the last components of the path are the same
 *   (i.e. `L1/L2/Pipeline/Pipeline`), they will be de-duplicated to make the
 *   resulting human portion of the ID more pleasing: `L1L2Pipeline<HASH>`
 *   instead of `L1L2PipelinePipeline<HASH>`
 * - If a component is named "Default" it will be omitted from the path. This
 *   allows refactoring higher level abstractions around constructs without affecting
 *   the IDs of already deployed resources.
 * - If a component is named "Resource" it will be omitted from the user-visible
 *   path, but included in the hash. This reduces visual noise in the human readable
 *   part of the identifier.
 */
class HashedAddressingScheme {
    allocateAddress(addressComponents) {
        return uniqueid_1.makeUniqueId(addressComponents);
    }
}
exports.HashedAddressingScheme = HashedAddressingScheme;
/**
 * Class that keeps track of the logical IDs that are assigned to resources
 *
 * Supports renaming the generated IDs.
 */
class LogicalIDs {
    constructor(namingScheme) {
        this.namingScheme = namingScheme;
        /**
         * The rename table (old to new)
         */
        this.renames = {};
        /**
         * All assigned names (new to old, may be identical)
         *
         * This is used to ensure that:
         *
         * - No 2 resources end up with the same final logical ID, unless they were the same to begin with.
         * - All renames have been used at the end of renaming.
         */
        this.reverse = {};
    }
    /**
     * Rename a logical ID from an old ID to a new ID
     */
    renameLogical(oldId, newId) {
        if (oldId in this.renames) {
            throw new Error(`A rename has already been registered for '${oldId}'`);
        }
        this.renames[oldId] = newId;
    }
    /**
     * Return the logical ID for the given stack element
     */
    getLogicalId(stackElement) {
        const path = stackElement.stackPath.split(PATH_SEP);
        const generatedId = this.namingScheme.allocateAddress(path);
        const finalId = this.applyRename(generatedId);
        validateLogicalId(finalId);
        return finalId;
    }
    /**
     * Throw an error if not all renames have been used
     *
     * This is to assure that users didn't make typoes when registering renames.
     */
    assertAllRenamesApplied() {
        const keys = new Set();
        Object.keys(this.renames).forEach(keys.add.bind(keys));
        Object.keys(this.reverse).map(newId => {
            keys.delete(this.reverse[newId]);
        });
        if (keys.size !== 0) {
            const unusedRenames = Array.from(keys.values());
            throw new Error(`The following Logical IDs were attempted to be renamed, but not found: ${unusedRenames.join(', ')}`);
        }
    }
    /**
     * Return the renamed version of an ID, if applicable
     */
    applyRename(oldId) {
        let newId = oldId;
        if (oldId in this.renames) {
            newId = this.renames[oldId];
        }
        // If this newId has already been used, it must have been with the same oldId
        if (newId in this.reverse && this.reverse[newId] !== oldId) {
            // tslint:disable-next-line:max-line-length
            throw new Error(`Two objects have been assigned the same Logical ID: '${this.reverse[newId]}' and '${oldId}' are now both named '${newId}'.`);
        }
        this.reverse[newId] = oldId;
        return newId;
    }
}
exports.LogicalIDs = LogicalIDs;
const VALID_LOGICALID_REGEX = /^[A-Za-z][A-Za-z0-9]{1,254}$/;
/**
 * Validate logical ID is valid for CloudFormation
 */
function validateLogicalId(logicalId) {
    if (!VALID_LOGICALID_REGEX.test(logicalId)) {
        throw new Error(`Logical ID must adhere to the regular expression: ${VALID_LOGICALID_REGEX.toString()}, got '${logicalId}'`);
    }
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibG9naWNhbC1pZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxvZ2ljYWwtaWQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFBQSwrQ0FBZ0Q7QUFHaEQsTUFBTSxRQUFRLEdBQUcsR0FBRyxDQUFDO0FBWXJCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1DRztBQUNILE1BQWEsc0JBQXNCO0lBQ3hCLGVBQWUsQ0FBQyxpQkFBMkI7UUFDOUMsT0FBTyx1QkFBWSxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFDM0MsQ0FBQztDQUNKO0FBSkQsd0RBSUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBYSxVQUFVO0lBZ0JuQixZQUE2QixZQUErQjtRQUEvQixpQkFBWSxHQUFaLFlBQVksQ0FBbUI7UUFmNUQ7O1dBRUc7UUFDYyxZQUFPLEdBQTRCLEVBQUUsQ0FBQztRQUV2RDs7Ozs7OztXQU9HO1FBQ2MsWUFBTyxHQUEyQixFQUFFLENBQUM7SUFHdEQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksYUFBYSxDQUFDLEtBQWEsRUFBRSxLQUFhO1FBQzdDLElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDdkIsTUFBTSxJQUFJLEtBQUssQ0FBQyw2Q0FBNkMsS0FBSyxHQUFHLENBQUMsQ0FBQztTQUMxRTtRQUNELElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsS0FBSyxDQUFDO0lBQ2hDLENBQUM7SUFFRDs7T0FFRztJQUNJLFlBQVksQ0FBQyxZQUEwQjtRQUMxQyxNQUFNLElBQUksR0FBRyxZQUFZLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUVwRCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1RCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQzlDLGlCQUFpQixDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzNCLE9BQU8sT0FBTyxDQUFDO0lBQ25CLENBQUM7SUFFRDs7OztPQUlHO0lBQ0ksdUJBQXVCO1FBQzFCLE1BQU0sSUFBSSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUM7UUFDL0IsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7UUFFdkQsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFO1lBQ2xDLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLENBQUMsQ0FBQyxDQUFDO1FBRUgsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLENBQUMsRUFBRTtZQUNqQixNQUFNLGFBQWEsR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1lBQ2hELE1BQU0sSUFBSSxLQUFLLENBQUMsMEVBQTBFLGFBQWEsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1NBQ3pIO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0ssV0FBVyxDQUFDLEtBQWE7UUFDN0IsSUFBSSxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ2xCLElBQUksS0FBSyxJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDdkIsS0FBSyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7U0FDL0I7UUFFRCw2RUFBNkU7UUFDN0UsSUFBSSxLQUFLLElBQUksSUFBSSxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxLQUFLLEtBQUssRUFBRTtZQUN4RCwyQ0FBMkM7WUFDM0MsTUFBTSxJQUFJLEtBQUssQ0FBQyx3REFBd0QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsVUFBVSxLQUFLLHlCQUF5QixLQUFLLElBQUksQ0FBQyxDQUFDO1NBQ2pKO1FBQ0QsSUFBSSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxLQUFLLENBQUM7UUFFNUIsT0FBTyxLQUFLLENBQUM7SUFDakIsQ0FBQztDQUNKO0FBOUVELGdDQThFQztBQUVELE1BQU0scUJBQXFCLEdBQUcsOEJBQThCLENBQUM7QUFFN0Q7O0dBRUc7QUFDSCxTQUFTLGlCQUFpQixDQUFDLFNBQWlCO0lBQ3hDLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUU7UUFDeEMsTUFBTSxJQUFJLEtBQUssQ0FBQyxxREFBcUQscUJBQXFCLENBQUMsUUFBUSxFQUFFLFVBQVUsU0FBUyxHQUFHLENBQUMsQ0FBQztLQUNoSTtBQUNMLENBQUMifQ==