var docsApp = {
  controller: {},
  directive: {},
  serviceFactory: {},
  filter: {}
};
docsApp.controller.DocsVersionsCtrl = ['$scope', '$rootScope', '$window', 'NG_VERSIONS', 'NG_VERSION', function($scope, $rootScope, $window, NG_VERSIONS, NG_VERSION) {
  $scope.docs_versions = NG_VERSIONS;
  $scope.docs_version  = NG_VERSIONS[0];
  $scope.jumpToDocsVersion = function(version) {
    var currentPagePath = '';
    // preserve URL path when switching between doc versions
    if (angular.isObject($rootScope.currentPage) && $rootScope.currentPage.section && $rootScope.currentPage.id) {
      currentPagePath = '/' + $rootScope.currentPage.section + '/' + $rootScope.currentPage.id;
    }
    $window.location = version.url + currentPagePath;
  };
}];
docsApp.controller.DocsNavigationCtrl = ['$scope', '$location', 'docsSearch', function($scope, $location, docsSearch) {
  function clearResults() {
    $scope.results = [];
    $scope.colClassName = null;
    $scope.hasResults = false;
  }
  $scope.search = function(q) {
    var MIN_SEARCH_LENGTH = 3;
    if(q.length >= MIN_SEARCH_LENGTH) {
      var results = docsSearch(q);
      var totalSections = 0;
      for(var i in results) {
        ++totalSections;
      }
      if(totalSections > 0) {
        $scope.colClassName = 'cols-' + totalSections;
      }
      $scope.hasResults = totalSections > 0;
      $scope.results = results;
    }
    else {
      clearResults();
    }
    if(!$scope.$$phase) $scope.$apply();
  };
  $scope.submit = function() {
    var result;
    for(var i in $scope.results) {
      result = $scope.results[i][0];
      if(result) {
        break;
      }
    }
    if(result) {
      $location.path(result.url);
      $scope.hideResults();
    }
  };
  $scope.hideResults = function() {
    clearResults();
    $scope.q = '';
  };
}];
docsApp.serviceFactory.lunrSearch = function() {
  return function(properties) {
    if (window.RUNNING_IN_NG_TEST_RUNNER) return null;
    var engine = lunr(properties);
    return {
      store : function(values) {
        engine.add(values);
      },
      search : function(q) {
        return engine.search(q);
      }
    };
  };
};
docsApp.serviceFactory.docsSearch = ['$rootScope','lunrSearch', 'NG_PAGES',
    function($rootScope, lunrSearch, NG_PAGES) {
  if (window.RUNNING_IN_NG_TEST_RUNNER) {
    return null;
  }
  var index = lunrSearch(function() {
    this.ref('id');
    this.field('title', {boost: 50});
    this.field('description', { boost : 20 });
  });
  angular.forEach(NG_PAGES, function(page, i) {
    var title = page.shortName;
    if(title.charAt(0) == 'n' && title.charAt(1) == 'g') {
      title = title + ' ' + title.charAt(2).toLowerCase() + title.substr(3);
    }
    index.store({
      id: i,
      title: title,
      description: page.keywords
    });
  });
  return function(q) {
    var results = {};
    angular.forEach(index.search(q), function(result) {
      var item = NG_PAGES[result.ref];
      var section = item.section;
      results[section] = results[section] || [];
      if(results[section].length < 15) {
        results[section].push(item);
      }
    });
    return results;
  };
}];
docsApp.directive.focused = function($timeout) {
  return function(scope, element, attrs) {
    element[0].focus();
    element.on('focus', function() {
      scope.$apply(attrs.focused + '=true');
    });
    element.on('blur', function() {
      // have to use $timeout, so that we close the drop-down after the user clicks,
      // otherwise when the user clicks we process the closing before we process the click.
      $timeout(function() {
        scope.$eval(attrs.focused + '=false');
      });
    });
    scope.$eval(attrs.focused + '=true');
  };
};
docsApp.directive.docsSearchInput = ['$document',function($document) {
  return function(scope, element, attrs) {
    var ESCAPE_KEY_KEYCODE = 27,
        FORWARD_SLASH_KEYCODE = 191;
    angular.element($document[0].body).bind('keydown', function(event) {
      if(event.keyCode == FORWARD_SLASH_KEYCODE && document.activeElement) {
        var activeElement = document.activeElement;
        var activeTagName = activeElement.nodeName.toLowerCase();
        var hasInputFocus = activeTagName == 'input'  || activeTagName == 'select' ||
                            activeTagName == 'option' || activeTagName == 'textarea' ||
                            activeElement.hasAttribute('contenteditable');
        if(!hasInputFocus) {
          event.stopPropagation();
          event.preventDefault();
          var input = element[0];
          input.focus();
        }
      }
    });
    element.bind('keydown', function(event) {
      if(event.keyCode == ESCAPE_KEY_KEYCODE) {
        event.stopPropagation();
        event.preventDefault();
        scope.$apply(function() {
          scope.hideResults();
        });
      }
    });
  };
}];
docsApp.directive.code = function() {
  return { restrict:'E', terminal: true };
};
docsApp.directive.sourceEdit = function(getEmbeddedTemplate) {
  return {
    template: '
',
    scope: true,
    controller: function($scope, $attrs, openJsFiddle, openPlunkr) {
      var sources = {
        module: $attrs.sourceEdit,
        deps: read($attrs.sourceEditDeps),
        html: read($attrs.sourceEditHtml),
        css: read($attrs.sourceEditCss),
        js: read($attrs.sourceEditJs),
        json: read($attrs.sourceEditJson),
        unit: read($attrs.sourceEditUnit),
        scenario: read($attrs.sourceEditScenario)
      };
      $scope.fiddle = function(e) {
        e.stopPropagation();
        openJsFiddle(sources);
      };
      $scope.plunkr = function(e) {
        e.stopPropagation();
        openPlunkr(sources);
      };
    }
  };
  function read(text) {
    var files = [];
    angular.forEach(text ? text.split(' ') : [], function(refId) {
      // refId is index.html-343, so we need to strip the unique ID when exporting the name
      files.push({name: refId.replace(/-\d+$/, ''), content: getEmbeddedTemplate(refId)});
    });
    return files;
  }
};
docsApp.directive.docModuleComponents = function() {
  return {
    template: '  ' +
              '    
Module Components
' +
              '    
' +
              '      
{{ section.title }}
' +
              '      
' +
              '        ' +
              '          | Name' +
              ' | Description' +
              ' | 
' +
              '        ' +
              '          | {{ component.shortName }}' +
              ' | {{ component.shortDescription }}' +
              ' | 
' +
              '      
' +
              '    
' +
              '  
\n' +
      '    
\n' +
      '      - Reset the workspace to step ' + step + '. ' +
      '- ' + command + ' 
\n' +
      '- Refresh your browser or check the app out on Angular\'s server. 
\n' +
      '
\n' +
      '  
\n' +
          tab('Git on Mac/Linux', 'git checkout -f step-' + step, 'gitUnix', step) +
          tab('Git on Windows', 'git checkout -f step-' + step, 'gitWin', step) +
        '
\n');
    }
  };
};
docsApp.filter.errorLink = ['$sanitize', function ($sanitize) {
  var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/g,
      MAILTO_REGEXP = /^mailto:/,
      STACK_TRACE_REGEXP = /:\d+:\d+$/;
  var truncate = function (text, nchars) {
    if (text.length > nchars) {
      return text.substr(0, nchars - 3) + '...';
    }
    return text;
  };
  return function (text, target) {
    var targetHtml = target ? ' target="' + target + '"' : '';
    if (!text) return text;
    return $sanitize(text.replace(LINKY_URL_REGEXP, function (url) {
      if (STACK_TRACE_REGEXP.test(url)) {
        return url;
      }
      // if we did not match ftp/http/mailto then assume mailto
      if (!/^((ftp|https?):\/\/|mailto:)/.test(url)) url = 'mailto:' + url;
      return '' +
                truncate(url.replace(MAILTO_REGEXP, ''), 60) +
              '';
    }));
  };
}];
docsApp.directive.errorDisplay = ['$location', 'errorLinkFilter', function ($location, errorLinkFilter) {
  var interpolate = function (formatString) {
    var formatArgs = arguments;
    return formatString.replace(/\{\d+\}/g, function (match) {
      // Drop the braces and use the unary plus to convert to an integer.
      // The index will be off by one because of the formatString.
      var index = +match.slice(1, -1);
      if (index + 1 >= formatArgs.length) {
        return match;
      }
      return formatArgs[index+1];
    });
  };
  return {
    link: function (scope, element, attrs) {
      var search = $location.search(),
        formatArgs = [attrs.errorDisplay],
        i;
      for (i = 0; angular.isDefined(search['p'+i]); i++) {
        formatArgs.push(search['p'+i]);
      }
      element.html(errorLinkFilter(interpolate.apply(null, formatArgs), '_blank'));
    }
  };
}];
/**
 * backToTop Directive
 * @param  {Function} $anchorScroll
 *
 * @description Ensure that the browser scrolls when the anchor is clicked
 */
docsApp.directive.backToTop = ['$anchorScroll', function($anchorScroll) {
  return function link(scope, element) {
    element.on('click', function(event) {
      scope.$apply($anchorScroll);
    });
  };
}];
docsApp.serviceFactory.angularUrls = function($document) {
  var urls = {};
  angular.forEach($document.find('script'), function(script) {
    var match = script.src.match(/^.*\/(angular[^\/]*\.js)$/);
    if (match) {
      urls[match[1].replace(/(\-\d.*)?(\.min)?\.js$/, '.js')] = match[0];
    }
  });
  return urls;
};
docsApp.serviceFactory.formPostData = function($document) {
  return function(url, fields) {
    var form = angular.element('');
    angular.forEach(fields, function(value, name) {
      var input = angular.element('');
      input.attr('value', value);
      form.append(input);
    });
    $document.find('body').append(form);
    form[0].submit();
    form.remove();
  };
};
docsApp.serviceFactory.prepareDefaultAppModule = function() {
  return function(content) {
    var deps = [];
    angular.forEach(content.deps, function(file) {
      if(file.name == 'angular-animate.js') {
        deps.push('ngAnimate');
      }
    });
    var moduleName = 'App';
    return {
      module : moduleName,
      script : "angular.module('" + moduleName + "', [" +
          (deps.length ? "'" + deps.join("','") + "'" : "") + "]);\n\n"
    };
  };
};
docsApp.serviceFactory.prepareEditorAssetTags = function(angularUrls) {
  return function(content, options) {
    options = options || {};
    var includeLocalFiles = options.includeLocalFiles;
    var html = makeScriptTag(angularUrls['angular.js']);
    var allFiles = [].concat(content.js, content.css, content.html, content.json);
    angular.forEach(content.deps, function(file) {
      if (file.name !== 'angular.js') {
        var isLocal = false;
        for(var i=0;i\n';
    };
    function makeCssLinkTag(src) {
      return '\n';
    };
  };
};
docsApp.serviceFactory.openPlunkr = function(templateMerge, formPostData, prepareEditorAssetTags, prepareDefaultAppModule) {
  return function(content) {
    var hasRouting = false;
    angular.forEach(content.deps, function(file) {
      hasRouting = hasRouting || file.name == 'angular-route.js';
    });
    var indexHtmlContent = '\n' +
                           '\n' +
                           '  \n' +
                           '{{scriptDeps}}';
    if(hasRouting) {
        indexHtmlContent += '\n';
    }
    indexHtmlContent += '\n' +
                        '  \n\n' +
                        '{{indexContents}}\n\n' +
                        '  \n' +
                        '\n';
    indexProp = {
      module: content.module,
      scriptDeps: prepareEditorAssetTags(content, { includeLocalFiles : true }),
      indexContents: content.html[0].content
    };
    var allFiles = [].concat(content.js, content.css, content.html, content.json);
    if(!content.module) {
      var moduleData = prepareDefaultAppModule(content);
      indexProp.module = moduleData.module;
      var found = false;
      angular.forEach(content.js, function(file) {
        if(file.name == 'script.js') {
          file.content = moduleData.script + file.content;
          found = true;
        }
      });
      if(!found) {
        indexProp.scriptDeps += '\n';
        allFiles.push({
          name : 'script.js',
          content : moduleData.script
        });
      }
    };
    var postData = {};
    angular.forEach(allFiles, function(file, index) {
      if (file.content && file.name != 'index.html') {
        postData['files[' + file.name + ']'] = file.content;
      }
    });
    postData['files[index.html]'] = templateMerge(indexHtmlContent, indexProp);
    postData['tags[]'] = "angularjs";
    postData.private = true;
    postData.description = 'AngularJS Example Plunkr';
    formPostData('http://plnkr.co/edit/?p=preview', postData);
  };
};
docsApp.serviceFactory.openJsFiddle = function(templateMerge, formPostData, prepareEditorAssetTags, prepareDefaultAppModule) {
  var HTML = '\n{{html:2}}
',
      CSS = '  \n' +
        '{{head:0}}