"use strict";
/*
 * Copyright 2018-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 tslib_1 = require("tslib");
const core_1 = tslib_1.__importStar(require("@adpt/core"));
const utils_1 = require("@adpt/utils");
const Container_1 = require("../Container");
const env_1 = require("../env");
const toK8sEnv = env_1.mergeEnvPairs;
const defaultProtocol = "tcp";
class PortInfo {
    constructor() {
        this.portMap = new Map();
    }
    get containerPorts() {
        if (this.portMap.size === 0)
            return undefined;
        return utils_1.mapMap(this.portMap, (_, p) => p);
    }
    addPortMapping(ctrPort, hostPort) {
        ctrPort = Number(ctrPort);
        if (isNaN(ctrPort)) {
            throw new Error(`Non-numeric port description not implemented`);
        }
        const e = this.entry(ctrPort);
        if (hostPort !== undefined)
            e.hostPort = hostPort;
    }
    entry(containerPort) {
        const key = this.makeKey(containerPort);
        let e = this.portMap.get(key);
        if (e === undefined) {
            e = { containerPort };
            this.portMap.set(key, e);
        }
        return e;
    }
    makeKey(ctrPort, protocol = defaultProtocol) {
        return `${protocol}/${ctrPort}`;
    }
}
function toK8sPorts(abstractProps) {
    const { ports, portBindings } = abstractProps;
    const pInfo = new PortInfo();
    if (ports != null)
        ports.forEach((p) => pInfo.addPortMapping(p));
    if (portBindings != null) {
        Object.keys(portBindings).forEach((ctrPort) => pInfo.addPortMapping(ctrPort, portBindings[ctrPort]));
    }
    return pInfo.containerPorts;
}
/**
 * Low level utility function to translate from the abstract {@link Container}
 * component props ({@link ContainerProps}) to {@link k8s.K8sContainerProps}
 * to be used in a {@link k8s.K8sContainer}.
 * @remarks
 * Note: The `image` property in the passed in {@link ContainerProps} must
 * be a `string`, not a `Handle`.
 * In most cases, it is preferable to use the {@link k8s.Container} component
 * instead, which is designed specifically to deal with this issue.
 *
 * @param abstractProps - The abstract {@link ContainerProps} to translate from.
 * @param k8sProps - Props that are specific to the {@link k8s.K8sContainer}
 *     component that should be merged into the resulting returned
 *     {@link k8s.K8sContainerProps} object.
 * @public
 */
function k8sContainerProps(abstractProps, k8sProps) {
    const { command, entrypoint, environment, tty, workingDir } = abstractProps;
    const ret = Object.assign({ key: abstractProps.key, name: abstractProps.name, image: abstractProps.image }, (k8sProps || {}));
    if (entrypoint != null) {
        ret.args = Array.isArray(entrypoint) ? entrypoint : [entrypoint];
    }
    if (command != null) {
        ret.command = Array.isArray(command) ? command : [command];
    }
    ret.env = toK8sEnv(environment);
    ret.ports = toK8sPorts(abstractProps);
    if (tty != null)
        ret.tty = tty;
    if (workingDir != null)
        ret.workingDir = workingDir;
    return ret;
}
exports.k8sContainerProps = k8sContainerProps;
/**
 * Tests whether an element is a {@link k8s.K8sContainer} element
 * @param x - element to test
 * @returns `true` if element is a {@link k8s.K8sContainer}, `false` otherwise
 *
 * @remarks
 * Acts as a TypeScript type assertion that will assert that `x` is `AdaptElement<K8sContainerProps>`
 *
 * @public
 */
function isK8sContainerElement(x) {
    return x.componentType === K8sContainer;
}
exports.isK8sContainerElement = isK8sContainerElement;
/**
 * Kubernetes-specific container.
 * @public
 */
class K8sContainer extends core_1.PrimitiveComponent {
    validate() {
        if (this.props.image == null || this.props.image === "") {
            throw new Error("K8sContainer: image is a required value");
        }
        return undefined;
        //FIXME(manishv) check if name is legal in k8s
        //FIXME(manishv) check if image string is valid URL
        //FIXME(manishv) check if workDir is valid path
    }
}
K8sContainer.defaultProps = {
    imagePullPolicy: "IfNotPresent"
};
exports.K8sContainer = K8sContainer;
/**
 * Component that implements the abstract {@link Container} interface and
 * translates to a Kubernetes-specific {@link k8s.K8sContainer}.
 * @public
 */
function Container(props) {
    const _a = props, { image: imgOrHandle, k8sContainerProps: addlProps } = _a, rest = tslib_1.__rest(_a, ["image", "k8sContainerProps"]);
    const image = Container_1.useLatestImageFrom(imgOrHandle);
    core_1.useDeployedWhen((gs) => {
        if (gs === core_1.GoalStatus.Destroyed || image)
            return true;
        return core_1.waiting("Waiting for Docker image");
    });
    if (!image)
        return null;
    const kProps = k8sContainerProps(Object.assign({}, rest, { image }), addlProps);
    return core_1.default.createElement(K8sContainer, Object.assign({}, kProps));
}
exports.Container = Container;
// TODO: The "as any" is a workaround for an api-extractor bug. See issue #185.
Container.displayName = "k8s.Container";
Container.defaultProps = Container_1.Container.defaultProps;
//# sourceMappingURL=Container.js.map