"use strict";
/*
 * Copyright 2020 Unbounded Systems, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@adpt/utils");
const image_ref_parse_1 = require("./image-ref-parse");
const imageRefDataKeys = utils_1.tuple("digest", "dockerHost", "domain", "id", "path", "tag");
const imageRefKeys = utils_1.tuple("digest", "dockerHost", "domain", "familiar", "id", "name", "nameTag", "path", "pathTag", "ref", "registry", "registryRef", "registryDigest", "registryTag", "tag", "type");
/**
 * Type guard for determining whether a given object has a non-null `id`
 * property.
 * @public
 */
function hasId(o) {
    return hasProperty(o, "id");
}
exports.hasId = hasId;
/**
 * A mutable reference to a container image that can be used to parse,
 * construct, and modify an image reference, as well as to hold information
 * about the image and how to access it.
 *
 * @remarks
 * This class can be used to parse strings that contain complete or partial
 * container image references. It can also be used to construct an image
 * reference from scratch, from individual component parts, or from another
 * reference, then to correctly format the reference or its components for
 * use with various tools.
 *
 * To parse and normalize a "familiar" image reference, like is typically
 * seen in the Docker UI:
 * ```ts
 * const ref = mutableImageRef("redis", true);
 * console.log(ref.tag);         // => "latest"
 * console.log(ref.registryRef); // => "docker.io/library/redis:latest"
 * ```
 *
 * To construct a reference to an image present on the local Docker daemon
 * that has an image ID:
 * ```ts
 * const ref = mutableImageRef({
 *     dockerHost: "default",
 *     path: "myimage",
 *     tag: "just-built",
 *     id: "sha256:04696b491e0cc3c58a75bace8941c14c924b9f313b03ce5029ebbc040ed9dcd9",
 * });
 * ```
 *
 * To convert a `MutableImageRef` to an immutable plain object `ImageRef`,
 * use the `freeze` method.
 * @public
 */
class MutableImageRef {
    constructor(info, normalize = false) {
        if (!info)
            return;
        if (typeof info === "string") {
            const ref = normalize ? image_ref_parse_1.parseFamiliar(info) : image_ref_parse_1.parseReference(info);
            this.name = ref.name;
            if (ref.tag)
                this._tag = ref.tag;
            if (ref.digest)
                this._digest = ref.digest;
            return;
        }
        if (info.dockerHost && info.domain) {
            throw new Error(`Cannot specify both 'dockerHost' and 'domain' when creating an image reference`);
        }
        if (info.digest)
            this.digest = info.digest;
        if (info.dockerHost)
            this.dockerHost = info.dockerHost;
        if (info.domain)
            this.domain = info.domain;
        if (info.id)
            this.id = info.id;
        if (info.path)
            this.path = info.path;
        if (info.tag)
            this.tag = info.tag;
    }
    freeze() {
        const ret = {};
        for (const k of imageRefKeys) {
            if (this[k] !== undefined)
                ret[k] = this[k];
        }
        return Object.freeze(ret);
    }
    toData() {
        const ret = {};
        for (const k of imageRefDataKeys) {
            if (this[k] !== undefined)
                ret[k] = this[k];
        }
        return ret;
    }
    toJSON() {
        return this.toData();
    }
    /**
     * The digest string for this image in its associated registry and repo
     * in the form `algorithm:hex`.
     */
    get digest() {
        return this._digest;
    }
    set digest(digest) {
        if (digest == null)
            delete this._digest;
        else
            this._digest = image_ref_parse_1.validate("digest", digest);
    }
    get dockerHost() {
        return this._dockerHost;
    }
    set dockerHost(dockerHost) {
        if (!dockerHost)
            delete this._dockerHost;
        else
            this._dockerHost = dockerHost;
    }
    /**
     * The hostname and optional port of the image registry for this image.
     */
    get domain() {
        return this._domain;
    }
    set domain(domain) {
        if (domain == null)
            delete this._domain;
        else
            this._domain = image_ref_parse_1.validate("domain", domain);
    }
    /**
     * The complete string reference for this image in familiar form, which
     * leaves out certain fields from the reference when they are set to
     * default values.
     * @remarks
     * This reference form is the form typically used in the Docker UI.
     * If the ImageRef is not complete, familiar will return `undefined`, as
     * the correct familiar representation cannot be determined.
     */
    get familiar() {
        if (this.registryDigest)
            return this.registryDigest;
        if (this.type === "incomplete")
            return undefined;
        let path = this._path;
        if (!path)
            return undefined;
        const domain = !this._domain || this._domain === image_ref_parse_1.defaultDomain ? "" : `${this._domain}/`;
        if (this._domain === image_ref_parse_1.defaultDomain && path.startsWith(`${image_ref_parse_1.defaultOfficialRepo}/`)) {
            path = path.slice(`${image_ref_parse_1.defaultOfficialRepo}/`.length);
        }
        const tag = !this._tag || this._tag === image_ref_parse_1.defaultTag ? "" : `:${this._tag}`;
        const digest = this._digest && !tag ? `@${this._digest}` : "";
        return domain + path + tag + digest;
    }
    /**
     * The content ID of the image in `algorithm:hex` format.
     */
    get id() {
        return this._id;
    }
    set id(id) {
        if (id == null)
            delete this._id;
        else
            this._id = image_ref_parse_1.validate("id", id);
    }
    /**
     * The image name, which is comprised of the optional domain and
     * the (non-optional) path.
     */
    get name() {
        if (!this._path)
            return undefined;
        return this._domain ? `${this._domain}/${this._path}` : this._path;
    }
    set name(name) {
        if (name == null) {
            delete this._domain;
            delete this._path;
        }
        else {
            const parsed = image_ref_parse_1.parseName(name);
            if (parsed.domain)
                this._domain = parsed.domain;
            this._path = parsed.path;
        }
    }
    /**
     * The image name (including any registry) and image tag. Returns undefined
     * if either path or tag are not set.
     * If nameTag is set to a string that does not contain a tag (i.e. does
     * not include a ":"), the default tag of `latest` will be set.
     * To set nameTag without defaulting to `latest`, call the class method
     * `setNameTag` with the parameter `useDefaultTag` set to `false`.
     */
    get nameTag() {
        if (!this.name || !this.tag)
            return undefined;
        return `${this.name}:${this.tag}`;
    }
    set nameTag(nt) {
        this.setNameTag(nt);
    }
    /**
     * Method to set the image name and image tag which also allows choice
     * of default behavior when `nameTag` does not contain a tag string.
     */
    setNameTag(nameTag, useDefaultTag = true) {
        if (nameTag == null) {
            this.name = undefined;
            this.tag = undefined;
        }
        else {
            const ref = image_ref_parse_1.parseReference(nameTag);
            if (ref.digest) {
                throw new Error(`Invalid container image nameTag '${nameTag}'`);
            }
            this.name = ref.name;
            let tag = ref.tag;
            if (!tag && useDefaultTag)
                tag = image_ref_parse_1.defaultTag;
            this._tag = tag;
        }
    }
    /**
     * The image repo path.
     */
    get path() {
        return this._path;
    }
    set path(path) {
        if (path == null)
            delete this._path;
        else
            this._path = image_ref_parse_1.validate("path", path);
    }
    /**
     * The image path (not including any registry) and image tag. Returns
     * undefined if either path or tag are not set.
     * If pathTag is set to a string that does not contain a tag (i.e. does
     * not include a ":"), the default tag of `latest` will be set.
     * To set pathTag without defaulting to `latest`, call the class method
     * `setPathTag` with the parameter `useDefaultTag` set to `false`.
     */
    get pathTag() {
        if (!this.path || !this.tag)
            return undefined;
        return `${this.path}:${this.tag}`;
    }
    set pathTag(pathTag) {
        this.setPathTag(pathTag);
    }
    /**
     * Method to set the image path and image tag which also allows choice
     * of default behavior when `pathTag` does not contain a tag string.
     */
    setPathTag(pathTag, useDefaultTag = true) {
        if (pathTag == null) {
            this.path = undefined;
            this.tag = undefined;
        }
        else {
            const pt = image_ref_parse_1.parsePathTag(pathTag);
            this.path = pt.path;
            let tag = pt.tag;
            if (!tag && useDefaultTag)
                tag = image_ref_parse_1.defaultTag;
            this._tag = tag;
        }
    }
    get ref() {
        return this.registryRef || this.nameTag || this.name || this.path || this.id || "INVALIDREF";
    }
    /**
     * Alias for domain
     */
    get registry() {
        return this.domain;
    }
    /**
     * The best remote reference available for this image in either
     * `domain/path@digest` format or `domain/path:tag` format.
     * @remarks
     * Returns `registryDigest` if it is set, otherwise returns `registryTag`.
     * If no remote reference is available, returns undefined.
     */
    get registryRef() {
        return this.registryDigest || this.registryTag;
    }
    /**
     * The remote digest reference in `domain/path@digest` form.
     * @remarks
     * Undefined if any of `domain`, `path`, or `digest` are unset.
     */
    get registryDigest() {
        if (!this._domain || !this._path || !this._digest)
            return undefined;
        return `${this.name}@${this._digest}`;
    }
    set registryDigest(rd) {
        if (rd == null) {
            this.name = undefined; // use setter
            delete this._digest;
        }
        else {
            const ref = image_ref_parse_1.parseReference(rd);
            if (ref.tag) {
                throw new Error(`Invalid container image registryDigest '${rd}': must not contain tag`);
            }
            this.name = ref.name;
            this._digest = ref.digest;
        }
    }
    /**
     * The remote tag reference in `domain/path:tag` form.
     * @remarks
     * Undefined if any of `domain`, `path`, or `tag` are unset.
     */
    get registryTag() {
        if (!this.domain)
            return undefined;
        return this.nameTag;
    }
    set registryTag(rt) {
        if (rt == null) {
            this.name = undefined;
            this.tag = undefined;
        }
        else {
            const ref = image_ref_parse_1.parseReference(rt);
            if (ref.digest) {
                throw new Error(`Invalid container image registryTag '${rt}': must not contain digest`);
            }
            this.name = ref.name;
            this.tag = ref.tag;
        }
    }
    /**
     * The image tag.
     */
    get tag() {
        return this._tag;
    }
    set tag(tag) {
        if (tag == null)
            delete this._tag;
        else
            this._tag = image_ref_parse_1.validate("tag", tag);
    }
    /**
     * The type of an image reference indicates how the image can be
     * accessed, either through a Docker host (daemon) or directly to a
     * container registry.
     *
     * @remarks
     * A reference is a valid `dockerhost` type ref when it contains a valid
     * dockerHost string and either:
     * - A valid image id or
     * - A valid path and either a valid tag or digest.
     *
     * A reference is a valid `registry` type ref when it contains a valid
     * domain, path, and either a tag or a digest.
     *
     * If not enough information is present to access and uniquely identify a
     * container image, the type is `incomplete`.
     */
    get type() {
        if (this._dockerHost) {
            return this._id || (this._path && (this._tag || this._digest)) ? "dockerhost" : "incomplete";
        }
        return this._domain && this._path && (this._tag || this._digest) ? "registry" : "incomplete";
    }
}
exports.MutableImageRef = MutableImageRef;
function mutableImageRef(info, normalize = false) {
    return new MutableImageRef(info, normalize);
}
exports.mutableImageRef = mutableImageRef;
function imageRef(info, normalize = false) {
    const mutable = new MutableImageRef(info, normalize);
    return mutable.freeze();
}
exports.imageRef = imageRef;
/**
 * A type guard for determining whether a {@link docker.ImageRef} is a
 * {@link docker.ImageRefRegistry}.
 * @public
 */
function isImageRefRegistry(ref) {
    return ref.type === "registry";
}
exports.isImageRefRegistry = isImageRefRegistry;
/**
 * A type guard for determining whether a {@link docker.ImageRef} is a
 * {@link docker.ImageRefRegistry} that has the `id` property set.
 * @public
 */
function isImageRefRegistryWithId(ref) {
    return ref.type === "registry" && typeof ref.id === "string";
}
exports.isImageRefRegistryWithId = isImageRefRegistryWithId;
/**
 * A type guard for determining whether a {@link docker.ImageRef} is a
 * {@link docker.ImageRefDockerHost}.
 * @public
 */
function isImageRefDockerhost(ref) {
    return ref.type === "dockerhost";
}
exports.isImageRefDockerhost = isImageRefDockerhost;
/**
 * A type guard for determining whether a {@link docker.ImageRef} is a
 * {@link docker.ImageRefDockerHost} that has the `id` property set.
 * @public
 */
function isImageRefDockerhostWithId(ref) {
    return ref.type === "dockerhost" && typeof ref.id === "string";
}
exports.isImageRefDockerhostWithId = isImageRefDockerhostWithId;
/**
 * A type guard for asserting whether an object has a property that is not
 * undefined.
 * @public
 */
function hasProperty(o, k) {
    return o[k] !== undefined;
}
//# sourceMappingURL=image-ref.js.map