/*****************************************************************************
 * Open MCT, Copyright (c) 2014-2018, United States Government
 * as represented by the Administrator of the National Aeronautics and Space
 * Administration. All rights reserved.
 *
 * Open MCT 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 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.
 *****************************************************************************/

/**
 * Module defining CustomRegistrars. Created by vwoeltje on 11/3/14.
 */
define(
    ['../Constants', './ServiceCompositor'],
    function (Constants, ServiceCompositor) {
        /*jshint validthis:true */

        /**
         * Handles registration of a few specific extension types that are
         * understood natively by Angular. This includes services and
         * directives.
         * @memberof platform/framework
         * @constructor
         */
        function CustomRegistrars(app, $log) {
            this.app = app;
            this.$log = $log;
            this.registered = {}; // Track registered keys by extension
        }

        // Utility; bind a function to a "this" pointer
        function bind(fn, thisArg) {
            return function () {
                return fn.apply(thisArg, arguments);
            };
        }

        // Used to create custom registration functions which map to
        // named methods on Angular modules, which follow the normal
        // app.method(key, [ deps..., function ]) pattern.
        function customRegistrar(angularFunction) {
            return function (extension, index) {
                var app = this.app,
                    $log = this.$log,
                    key = extension.key,
                    dependencies = extension.depends || [],
                    registered = this.registered[angularFunction] || {};

                this.registered[angularFunction] = registered;

                if (!key) {
                    $log.warn([
                        "Cannot register ",
                        angularFunction,
                        " ",
                        index,
                        ", no key specified. ",
                        JSON.stringify(extension)
                    ].join(""));
                } else if (registered[key]) {
                    $log.debug([
                        "Already registered ",
                        angularFunction,
                        " with key ",
                        key,
                        "; skipping."
                    ].join(""));
                } else {
                    $log.info([
                        "Registering ",
                        angularFunction,
                        ": ",
                        key
                    ].join(""));
                    registered[key] = true;
                    app[angularFunction](
                        key,
                        dependencies.concat([extension])
                    );
                }
            };
        }

        function registerConstant(extension) {
            var app = this.app,
                $log = this.$log,
                key = extension.key,
                value = extension.value;

            if (typeof key === "string" && value !== undefined) {
                $log.info([
                    "Registering constant: ",
                    key,
                    " with value ",
                    value
                ].join(""));
                app.constant(key, value);
            } else {
                $log.warn([
                    "Cannot register constant ",
                    key,
                    " with value ",
                    value
                ].join(""));
            }

        }

        // Custom registration function for extensions of category "runs"
        function registerRun(extension) {
            var app = this.app,
                $log = this.$log;

            if (typeof extension === 'function') {
                // Prepend dependencies, and schedule to run
                app.run((extension.depends || []).concat([extension]));
            } else {
                // If it's not a function, no implementation was given
                $log.warn([
                    "Cannot register run extension from ",
                    (extension.bundle || {}).path,
                    "; no implementation."
                ].join(""));
            }
        }

        // Custom registration function for extensions of category "route"
        function registerRoute(extension) {
            var app = this.app,
                $log = this.$log,
                route = Object.create(extension);

            // Adjust path for bundle
            if (route.templateUrl) {
                route.templateUrl = [
                    route.bundle.path,
                    route.bundle.resources,
                    route.templateUrl
                ].join(Constants.SEPARATOR);
            }

            // Log the registration
            $log.info("Registering route: " + (route.key || route.when));

            // Register the route with Angular
            app.config(['$routeProvider', function ($routeProvider) {
                if (route.when) {
                    $routeProvider.when(route.when, route);
                } else {
                    $routeProvider.otherwise(route);
                }
            }]);
        }

        // Handle service compositing
        function registerComponents(components) {
            var app = this.app,
                $log = this.$log;
            return new ServiceCompositor(app, $log)
                .registerCompositeServices(components);
        }

        // Utility; create a function which converts another function
        // (which acts on single objects) to one which acts upon arrays.
        function mapUpon(func) {
            return function (array) {
                return array.map(bind(func, this));
            };
        }

        // More like key-value pairs than methods; key is the
        // name of the extension category to be handled, and the value
        // is the function which handles it.

        /**
         * Register constant values.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.constants =
            mapUpon(registerConstant);

        /**
         * Register Angular routes.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.routes =
            mapUpon(registerRoute);

        /**
         * Register Angular directives.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.directives =
            mapUpon(customRegistrar("directive"));

        /**
         * Register Angular controllers.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.controllers =
            mapUpon(customRegistrar("controller"));

        /**
         * Register Angular services.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.services =
            mapUpon(customRegistrar("service"));

        /**
         * Register Angular filters.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.filters =
            mapUpon(customRegistrar("filter"));

        /**
         * Register functions which will run after bootstrapping.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.runs =
            mapUpon(registerRun);

        /**
         * Register components of composite services.
         * @param {Array} extensions the resolved extensions
         */
        CustomRegistrars.prototype.components =
            registerComponents;

        return CustomRegistrars;
    }
);
