/*****************************************************************************
 * Open MCT Web, Copyright (c) 2014-2015, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT Web is 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.
 *
 * Open MCT Web includes source code licensed under additional open source
 * licenses. See the Open Source Licenses file (LICENSES.md) included with
 * this source code distribution or the Licensing information page available
 * at runtime from the About dialog for additional information.
 *****************************************************************************/
/*global define*/

/**
 * This bundle implements the policy service.
 * @namespace platform/policy
 */
define(
    [],
    function () {
        "use strict";

        /**
         * A policy is a participant in decision-making policies. Policies
         * are divided into categories (identified symbolically by strings);
         * within a given category, every given policy-driven decision will
         * occur by consulting all available policies and requiring their
         * collective consent (that is, every individual policy has the
         * power to reject the decision entirely.)
         *
         * @interface Policy
         * @template C, X
         */

        /**
         * Check if this policy allows the described decision. The types
         * of the arguments expected here vary depending on policy category.
         *
         * @method Policy#allow
         * @template C, X
         * @param {C} candidate the thing to allow or disallow
         * @param {X} context the context in which the decision occurs
         * @returns {boolean} false if disallowed; otherwise, true
         */


        /**
         * The `policyService` handles decisions about what things
         * are and are not allowed in certain contexts.
         * @interface PolicyService
         */

        /**
         * Check whether or not a certain decision is allowed by
         * policy.
         * @param {string} category a machine-readable identifier
         *        for the kind of decision being made
         * @param candidate the object about which the decision is
         *        being made
         * @param context the context in which the decision occurs
         * @param {Function} [callback] callback to invoke with a
         *        string message describing the reason a decision
         *        was disallowed (if its disallowed)
         * @returns {boolean} true if the decision is allowed,
         *          otherwise false.
         * @method PolicyService#allow
         */

        /**
         * Provides an implementation of `policyService` which consults
         * various policy extensions to determine whether or not a specific
         * decision should be allowed.
         * @memberof platform/policy
         * @constructor
         * @implements {PolicyService}
         * @param {Policy[]} policies the policies to enforce
         */
        function PolicyProvider(policies) {
            var policyMap = {};

            // Instantiate a policy. Mostly just a constructor call, but
            // we also track the message (which was provided as metadata
            // along with the constructor) so that we can expose this later.
            function instantiate(Policy) {
                var policy = Object.create(new Policy());
                policy.message = Policy.message;
                return policy;
            }

            // Add a specific policy to the map for later lookup,
            // according to its category. Note that policy extensions are
            // provided as constructors, so they are instantiated here.
            function addToMap(Policy) {
                var category = (Policy || {}).category;
                if (category) {
                    // Create a new list for that category if needed...
                    policyMap[category] = policyMap[category] || [];
                    // ...and put an instance of this policy in that list.
                    policyMap[category].push(instantiate(Policy));
                }
            }

            // Populate the map for subsequent lookup
            policies.forEach(addToMap);
            this.policyMap = policyMap;
        }

        PolicyProvider.prototype.allow = function (category, candidate, context, callback) {
            var policyList = this.policyMap[category] || [],
                i;

            // Iterate through policies. We do this instead of map or
            // forEach so that we can return immediately if a policy
            // chooses to disallow this decision.
            for (i = 0; i < policyList.length; i += 1) {
                // Consult the policy...
                if (!policyList[i].allow(candidate, context)) {
                    // ...it disallowed, so pass its message to
                    // the callback (if any)
                    if (callback) {
                        callback(policyList[i].message);
                    }
                    // And return the failed result.
                    return false;
                }
            }

            // No policy disallowed this decision.
            return true;
        };

        return PolicyProvider;
    }
);
