/**
 * @license AngularJS
 * (c) 2010-2012 AngularJS http://angularjs.org
 * License: MIT
 */

/**
 * Backward compatibility module for AngularJS
 * @author Vojta Jina <vojta.jina@gmail.com>
 *
 * Load this module to enable old-style controllers, where controller and scope are mixed together.
 *
 * This module decorates Angular's $controller service:
 * - if given controller does not ask for $scope, it instantiates it in old-way
 * - if given controller does ask for $scope, instantiation is delegated to default $controller
 *   service.
 *
 * This also allows migrating apps step by step.
 */

angular.module('ngScopeController', ['ng'], ['$provide', function($provide) {
  $provide.decorator('$controller', ['$injector', '$delegate', '$parse', '$window',
      function($injector, $delegate, $parse, $window) {

    var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m;
    var FN_ARG_SPLIT = /,/;
    var FN_ARG = /^\s*(.+?)\s*$/;
    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    /**
     * Return a list of arguments to inject
     * If no $inject property specified it parse the argument names.
     * @param fn
     */
    function inferInjectionArgs(fn) {
      // assert fn is a function
      if (!angular.isFunction(fn)) {
        var error = new Error("Controller must be a function, got " +
            (typeof fn === 'object' ? fn.constructor.name || 'Object' : typeof fn));
        throw error;
      }

      if (fn.$inject) return fn.$inject;

      // guess from argument names
      var args = [];
      var fnText = fn.toString().replace(STRIP_COMMENTS, '');
      var argDecl = fnText.match(FN_ARGS);
      angular.forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) {
        arg.replace(FN_ARG, function(all, name) {
          args.push(name);
        });
      });
      return args;
    }


    /**
     * $controller service
     *
     * @param {Function|string} Class Constructor function of the controller or string id.
     * @param {Object} locals Locals for injecting, must contain a $scope.
     * @return {Object} Instance of the controller.
     */
    return function(Class, locals) {
      var scope = locals.$scope;

      // given string id, find ctrl on window or current scope
      if (angular.isString(Class)) {
        var getter = $parse(Class);
        Class = getter(scope) || getter($window);
      }

      var injectArgs = inferInjectionArgs(Class);

      // IE8 and below does not support Array.indexOf
      function indexOf(array, obj) {
        if (array.indexOf) return array.indexOf(obj);

        for ( var i = 0; i < array.length; i++) {
          if (obj === array[i]) return i;
        }
        return -1;
      }

      // asking for scope - delegate to original service
      if (indexOf(injectArgs, '$scope') !== -1) {
        return $delegate(Class, locals);
      }

      // not asking for scope - BC hack
      var classPrototype = Class.prototype;
      for(var key in classPrototype) {
        scope[key] = angular.bind(scope, classPrototype[key]);
      }
      $injector.invoke(Class, scope, locals);

      return scope;
    };
  }]);
}]);