/*
 * Copyright © 2019 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.
 */

import { logger } from "@atomist/automation-client/lib/util/logger";
import { PushListenerInvocation } from "../../listener/PushListener";
import {
    GoalSettingCompositionStyle,
    GoalSettingStructure,
} from "../GoalSetter";
import { PushMapping } from "../PushMapping";

/**
 * Use to execute a rule set for any push to resolve to an object.
 * The value from the first matching rule will be used.
 */
export class PushRules<V> implements PushMapping<V>, GoalSettingStructure<PushListenerInvocation, V> {

    public choices: Array<PushMapping<V>> = [];

    get structure(): { components: Array<PushMapping<V>>, compositionStyle: GoalSettingCompositionStyle } {
        return {
            components: this.rules,
            compositionStyle: GoalSettingCompositionStyle.FirstMatch,
        };
    }

    /**
     * Return all possible values
     * @param {string} name
     * @param {Array<PushMapping<V>>} rules Array of rules.
     * Passing an empty array will result in an instance that always maps to undefined,
     * and is not an error.
     */
    constructor(public readonly name: string, private readonly rules: Array<PushMapping<V>> = []) {
        if (!name) {
            throw new Error("PushRule name must be specified");
        }
        this.add(rules);
    }

    /**
     * Return a PushRules with a subset of the rules of this one
     * @param {(p: PushMapping<V>) => boolean} predicate
     * @return {PushRules<V>}
     */
    public filter(predicate: (p: PushMapping<V>) => boolean): PushRules<V> {
        return new PushRules("name-", this.choices.filter(predicate));
    }

    public add(rules: Array<PushMapping<V>>): void {
        this.choices = this.choices.concat(rules);
    }

    public async mapping(pi: PushListenerInvocation): Promise<V> {
        for (const pc of this.choices) {
            const found = await pc.mapping(pi);
            logger.debug("Eligible PushRule named %s returned choice %j", pc.name, found);
            if (found) {
                logger.debug("PushRules '%s': Value for push on %j is %j", this.name, pi.id, found);
                return found;
            } else if (found === null) {
                return undefined;
            }
        }
        return undefined;
    }
}
