"use strict";
/*
 * Copyright © 2020 Atomist, Inc.
 *
 * 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 });
exports.AbstractSoftwareDeliveryMachine = void 0;
const string_1 = require("@atomist/automation-client/lib/internal/util/string");
const logger_1 = require("@atomist/automation-client/lib/util/logger");
const _ = require("lodash");
const goalContribution_1 = require("../../api/dsl/goalContribution");
const ReportProgress_1 = require("../../api/goal/progress/ReportProgress");
const ConfigurationValues_1 = require("../../api/machine/ConfigurationValues");
const Registerable_1 = require("../../api/machine/Registerable");
const commonPushTests_1 = require("../../api/mapping/support/commonPushTests");
const PushRules_1 = require("../../api/mapping/support/PushRules");
const goalApprovalRequestVote_1 = require("../../api/registration/goalApprovalRequestVote");
const DefaultGoalImplementationMapper_1 = require("../goal/DefaultGoalImplementationMapper");
const goalSetListener_1 = require("../listener/goalSetListener");
const logInterpreters_1 = require("../log/logInterpreters");
const HandlerRegistrationManagerSupport_1 = require("./HandlerRegistrationManagerSupport");
const ListenerRegistrationManagerSupport_1 = require("./ListenerRegistrationManagerSupport");
/**
 * Abstract support class for implementing a SoftwareDeliveryMachine.
 */
class AbstractSoftwareDeliveryMachine extends ListenerRegistrationManagerSupport_1.ListenerRegistrationManagerSupport {
    /**
     * Construct a new software delivery machine, with zero or
     * more goal setters.
     * @param {string} name
     * @param configuration automation client configuration we're running in
     * @param {GoalSetter} goalSetters tell me what to do on a push. Hint: start with "whenPushSatisfies(...)"
     */
    constructor(name, configuration, goalSetters) {
        super();
        this.name = name;
        this.configuration = configuration;
        this.extensionPacks = [];
        this.registrationManager = new HandlerRegistrationManagerSupport_1.HandlerRegistrationManagerSupport(this);
        this.disposalGoalSetters = [];
        this.goalApprovalRequestVoters = [];
        this.goalApprovalRequestVoteDecisionManager = goalApprovalRequestVote_1.UnanimousGoalApprovalRequestVoteDecisionManager;
        Registerable_1.registrableManager().register(this);
        // If we didn't get any goal setters don't register a mapping
        if (goalSetters.length > 0) {
            this.pushMap = new PushRules_1.PushRules("Goal setters", _.flatten(goalSetters));
        }
        // Register the goal completion listener to update goal set state
        this.addGoalCompletionListener(goalSetListener_1.GoalSetGoalCompletionListener);
    }
    get goalFulfillmentMapper() {
        if (!this.fulfillmentMapper) {
            this.fulfillmentMapper = new DefaultGoalImplementationMapper_1.DefaultGoalImplementationMapper();
        }
        return this.fulfillmentMapper;
    }
    /**
     * Return the PushMapping that will be used on pushes.
     * Useful in testing goal setting.
     * @return {PushMapping<Goals>}
     */
    get pushMapping() {
        return this.pushMap;
    }
    /**
     * Provide the implementation for a goal.
     * The SDM will run it as soon as the goal is ready (all preconditions are met).
     * If you provide a PushTest, then the SDM can assign different implementations
     * to the same goal based on the code in the project.
     * @param {string} implementationName
     * @param {Goal} goal
     * @param {ExecuteGoal} goalExecutor
     * @param options PushTest to narrow matching & InterpretLog that can handle
     * the log from the goalExecutor function
     * @return {this}
     */
    addGoalImplementation(implementationName, goal, goalExecutor, options) {
        const implementation = {
            implementationName, goal, goalExecutor,
            pushTest: _.get(options, "pushTest") || commonPushTests_1.AnyPush,
            logInterpreter: _.get(options, "logInterpreter") || logInterpreters_1.LogSuppressor,
            progressReporter: _.get(options, "progressReporter") || ReportProgress_1.NoProgressReport,
            projectListeners: _.get(options, "projectListeners") || [],
        };
        this.goalFulfillmentMapper.addImplementation(implementation);
        return this;
    }
    addGoalApprovalRequestVoter(vote) {
        this.goalApprovalRequestVoters.push(vote);
        return this;
    }
    setGoalApprovalRequestVoteDecisionManager(manager) {
        this.goalApprovalRequestVoteDecisionManager = manager;
        return this;
    }
    addCommand(cmd) {
        if (this.shouldRegister(cmd)) {
            this.registrationManager.addCommand(cmd);
        }
        return this;
    }
    addGeneratorCommand(gen) {
        if (this.shouldRegister(gen)) {
            this.registrationManager.addGeneratorCommand(gen);
        }
        return this;
    }
    addCodeTransformCommand(ct) {
        if (this.shouldRegister(ct)) {
            this.registrationManager.addCodeTransformCommand(ct);
        }
        return this;
    }
    addCodeInspectionCommand(cir) {
        if (this.shouldRegister(cir)) {
            this.registrationManager.addCodeInspectionCommand(cir);
        }
        return this;
    }
    addEvent(e) {
        this.registrationManager.addEvent(e);
        return this;
    }
    addIngester(i) {
        if (typeof i === "string") {
            this.registrationManager.addIngester({
                ingester: i,
            });
        }
        else {
            this.registrationManager.addIngester(i);
        }
        return this;
    }
    /**
     * Declare that a goal will become successful based on something outside.
     * For instance, ArtifactGoal succeeds because of an ImageLink event.
     * This tells the SDM that it does not need to run anything when this
     * goal becomes ready.
     */
    addGoalSideEffect(goal, sideEffectName, registration, pushTest = commonPushTests_1.AnyPush) {
        this.goalFulfillmentMapper.addSideEffect({
            goal,
            sideEffectName,
            pushTest,
            registration,
        });
        return this;
    }
    addGoalContributions(goalContributions) {
        if (!this.pushMap) {
            this.pushMap = goalContributions;
        }
        else {
            this.pushMap = goalContribution_1.enrichGoalSetters(this.pushMap, goalContributions);
        }
        return this;
    }
    withPushRules(contributor, ...contributors) {
        return this.addGoalContributions(goalContribution_1.goalContributors(contributor, ...contributors));
    }
    addExtensionPacks(...packs) {
        for (const pack of packs) {
            const found = this.extensionPacks.find(existing => existing.name === pack.name && existing.vendor === pack.vendor);
            if (!!found) {
                pack.name = `${pack.name}-${string_1.guid().slice(0, 7)}`;
            }
            this.addExtensionPack(pack);
            if (!!pack.goalContributions) {
                this.addGoalContributions(pack.goalContributions);
            }
        }
        return this;
    }
    addExtensionPack(pack) {
        logger_1.logger.debug("Adding extension pack '%s' version %s from %s", pack.name, pack.version, pack.vendor);
        ConfigurationValues_1.validateConfigurationValues(this.configuration, pack);
        pack.configure(this);
        this.extensionPacks.push(pack);
        return this;
    }
    /**
     * Invoke StartupListeners.
     */
    async notifyStartupListeners() {
        const i = {
            addressAdmin: this.configuration.sdm.adminAddressChannels || (async (msg) => {
                logger_1.logger.debug("startup: %j", msg);
            }),
            sdm: this,
        };
        // Register the startup listener to schedule the triggered listeners
        // It is important we schedule the triggers after the startup listeners are done!
        this.startupListeners.push(async () => this.scheduleTriggeredListeners());
        // Execute startupListeners in registration order
        await this.startupListeners.map(l => l(i)).reduce(p => p.then(), Promise.resolve());
    }
    /**
     * Schedule the triggered listeners
     */
    scheduleTriggeredListeners() {
        const i = {
            addressAdmin: this.configuration.sdm.adminAddressChannels || (async (msg) => {
                logger_1.logger.debug("trigger: %j", msg);
            }),
            sdm: this,
        };
        this.triggeredListeners.forEach(t => {
            if (t.trigger && t.trigger.cron) {
                const cj = require("cron");
                const cron = new cj.CronJob({
                    cronTime: t.trigger.cron,
                    onTick: () => t.listener(i),
                    unrefTimeout: true,
                });
                cron.start();
            }
            if (t.trigger && t.trigger.interval) {
                setInterval(() => t.listener(i), t.trigger.interval).unref();
            }
        });
    }
    shouldRegister(cm) {
        return !!cm.registerWhen ?
            cm.registerWhen(this.configuration) :
            true;
    }
}
exports.AbstractSoftwareDeliveryMachine = AbstractSoftwareDeliveryMachine;
//# sourceMappingURL=AbstractSoftwareDeliveryMachine.js.map