// Configure Angular JS to use CDN and fallback to webjars
require.config({
    paths: {
        'css': [
            'https://cdnjs.cloudflare.com/ajax/libs/require-css/0.1.5/css.min', // cdn
            '../../../webjars/require-css/0.1.7/css' // local fallback
        ],
        'angular': [
            'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.8/angular.min', // cdn
            '../../../webjars/angular/1.3.8/js/angular.min' // local fallback
        ],
        'angular-loading-bar': [
            'https://cdnjs.cloudflare.com/ajax/libs/angular-loading-bar/0.7.1/loading-bar.min', // cdn
            '../../../webjars/angular-loading-bar/0.7.1/loading-bar.min' // local fallback
        ]
    },
    shim: {
        'angular' : {'exports' : 'angular'},
        'angular-loading-bar' : {'deps' : ['angular', 'css!angular-loading-bar']}
    }
});

// Use Angular JS and define the xwiki services
require(['angular'],function(angular) {
  /**
   * Scriptdoc services
   */
  var services = angular.module('scriptdoc.xwikiServices', []);

  /**
   * Filter service - Hold active filters for the list of bindings
   */
  services.factory('xwikiService', function() {
    return {
      proxyURL: 'http://89.39.209.240:10018/bin/get/ScriptingDocumentation/WebHome?url=',
      getKindsURL: 'http://89.39.209.240:10018/bin/get/ScriptingDocumentation/ScriptDocPanel?sheet=&outputSyntax=plain&get=kinds',
      getBindingsURL: 'http://89.39.209.240:10018/bin/get/ScriptingDocumentation/ScriptDocPanel?sheet=&outputSyntax=plain&get=bindings'      
    };
  });
});
// Use Angular JS and define the scriptoc services and application
require(['jquery','angular','angular-loading-bar'],
        function($, angular) {

  /**
   * Scriptdoc services
   */
  var services = angular.module('scriptdoc.services', ['scriptdoc.xwikiServices']);

  /**
   * Filter service - Hold active filters for the list of bindings
   */
  services.factory('filterService',
                   function() {
    return {

      /**
       * Active filters on the list
       */
      activeFilters: {
        internal: false,
        deprecated: false,
        legacy: false,
      },

      /**
       * Text search used to refine
       */
      searchText: '',

      /**
       * Manage the deprecated filter to stop filtering deprecated and legacy
       * bindings.
       *
       * @param value set to true to display deprecated and legacy bindings.
       * @return return true when deprecated and legacy bindings are NOT
       *         filtered out.
       */
      deprecated: function(value) {
        if (angular.isDefined(value)) {
          if (value) {
            delete this.activeFilters.deprecated;
            delete this.activeFilters.legacy;
          } else {
            this.activeFilters.deprecated = false;
            this.activeFilters.legacy = false;
          }
        }
        return !this.activeFilters.hasOwnProperty('deprecated');
      },

      /**
      * Manage the internal filter to stop filtering internal bindings.
      *
      * @param value set to true to display internal bindings.
      * @return return true when internal bindings are NOT filtered out.
      */
      internal: function(value) {
        if (angular.isDefined(value)) {
          if (value) {
            delete this.activeFilters.internal;
          } else {
            this.activeFilters.internal = false;
          }
        }
        return !this.activeFilters.hasOwnProperty('internal');
      }
    };
  });

  /**
  * Javadoc service - Manage the currently displayed javadoc location
  */
  services.factory('javadocService',
                   ['xwikiService', '$location',
                    function(xwikiService, $location) {

    var _location = null, _base = null, _hash = null, _params = null, _element = $('html'),
        absUrlRE = /^https?:\/\//i,
        hostUrlRE = /^(https?:\/\/[^/]+)/i
        xwikiUrlRE = /^https?:\/\/[^/]*\.xwiki\.org/i,
        parentRE = /[\-\w]+\/\.\.\//,
        extensionUrlRE = /^https?:\/\/extensions\.xwiki\.org\/?\?id=([^:]+):([^:]+)(?::([^:]+))?:::.+\/apidocs(\/[^?]+)\?is-external=true$/,
        classRE = /^[a-zA-Z_$][a-zA-Z\d_$]+\.(xwiki|xpn)\.([a-zA-Z_$][a-zA-Z\d_$]*\.)*[a-zA-Z_$][a-zA-Z\d_$]*$/;

    function isURLAbsolute(url) {
      return absUrlRE.test(url);
    }

    function isXWikiURL(url) {
      return xwikiUrlRE.test(url);
    }

    function isClassName(name) {
      return classRE.test(name);
    }

    function reduceURL(url) {
      if (!parentRE.test(url)) return url;
      return reduceURL(url.replace(parentRE, ""));
    }

    function getHost(url) {
      return url.match(hostUrlRE)[0];
    }

    function parseExtensionURL(url) {
      var r = url.match(extensionUrlRE);
      if(!r) return null;
      if(!r[3]) {
          // TODO: find version
      }
      return {
          groupId: r[1],
          artifactId: r[2],
          version: r[3],
          jdocPath: r[4]
      }
    }

    function buildJavadocURL(parsedUrl) {
      return 'http://nexus.xwiki.org/nexus/service/local/repositories/public/archive/'
        + parsedUrl.groupId.replace(/\./g,'/') + '/' + parsedUrl.artifactId + '/' + parsedUrl.version + '/'
        + parsedUrl.artifactId + '-' + parsedUrl.version + '-javadoc.jar/!'
        + parsedUrl.jdocPath;
    }

    function scrollToHash(hash) {
      var anchor = (hash) ? $('*[name="' + hash + '"]')[0] : null,
          target = (hash && anchor) ? $(anchor).offset().top : _element.offset().top,
          distance = target - $(document).scrollTop(),
          speed = Math.sqrt(Math.abs(distance)) * 5;

      if ((hash && anchor) || distance < 0) { // No scroll down to top
        $('html,body').animate({
          scrollTop: target
        }, speed);
      }
    }

    function setNewLocation(url, hash) {
      if (url != _location || hash != _hash) {
        if (url) {
          _base = url.substring(0, url.lastIndexOf('/'));
          _location = url;
          _hash = hash;
        } else {
          _location = null;
          _base = null;
          _hash = null;
        }
        updateBrowserLocation();
      }
    }

    function updateBrowserLocation() {
      if (_location) {
        if (_hash) {
          $location.search('url', _location + '#' + _hash);
        } else {
          $location.search('url', _location);
        }
      } else {
        $location.search('url', null);
      }
    }

    function setLocation(value) {
      if (!value || value == '' || typeof value !== "string") {
        setNewLocation(null)
        return;
      }

      var pval = value.split('#'),
          url = pval[0], hash = pval[1] ? decodeURIComponent(pval[1]) : null;

      if (url.startsWith('&')) {
        _params = url;
      } else if (isURLAbsolute(url)) {
        if (isXWikiURL(url)) {
          // compute redirection for Javadoc URL that lead to e.x.o
          var parsedUrl = parseExtensionURL(url)
          if (parsedUrl) {
            url = buildJavadocURL(parsedUrl);
          }

          // set new internal (xwiki) URL location
          setNewLocation(url, hash);
        } else {
          // external (non-xwiki) URL are open in a new window
          window.open(value);
        }
      } else if (_base) {
        if (url.startsWith('/')) {
          url = getHost(_base) + url;
        } else {
          url = reduceURL(_base + '/' + url);
        }
        
        if (_location == url) {
          // set new hash only
          _hash = hash;
          scrollToHash(hash);
          updateBrowserLocation();
        } else {
          // set new URL location based on relative URL
          setNewLocation(url, hash);
        }
      } else {
        setNewLocation(null);
      }
    }

    function getPotentialLink(node) {
      if(node.childNodes.length == 1 && node.firstChild.nodeType == 3) {
        var text = node.firstChild.data;
        if (text && isClassName(text)) {
          return text;
        } 
      }
      return null;
    }

    return {
      isExternalUrl: function(url) {
        return isURLAbsolute(url) && !isXWikiURL(url);
      },

      /**
       * URL of the currently selected javadoc
       *
       * @param value The URL to be selected. If the URL is relative, it will be
       *              canonalized based on the previously selected URL. If an
       *              anchor is provided, it will be stripped out and store in
       *              the anchor property below.
       */
      location : function(value) {
        if (angular.isDefined(value)) {
          // Setter
          setLocation(value)
        } else {
          // Getter
          var url = xwikiService.proxyURL;
          if (_location) {
            url += encodeURIComponent(_location);
          }
          if (_params) {
            url += _params;
          }
          return url;
        }
      },

      postLoadHook : function(element) {
        if (_params) {
          _params = null;
          var comments = $(element.children()[0]).contents().filter(function(){ return this.nodeType == 8;});
          if (comments.size() > 0) {
            var url = comments[0].data.trim();
            if (isURLAbsolute(url)) {
              setNewLocation(url, null);
              updateBrowserLocation();
            }
          }
        }
        scrollToHash(_hash);
      },

      handleClick : function(event) {
        event.preventDefault();
        var target = $(event.target).attr('href');
        if (target) {
          setLocation(target);
        }
        target = getPotentialLink(event.target);
        if (target) {
          setLocation("&class=" + encodeURIComponent(target));
        }
      },

      hoverIn : function(event) {
        var target = $(event.target);
        if (!target.attr('href') && getPotentialLink(event.target)) {
          target.addClass('hoverlink');
          $(target).one('mouseleave', function() { target.removeClass('hoverlink') });
        }  
      }
    };
  }]);

  /**
   * Scriptdoc application
   */
  var app = angular.module('scriptdoc',
                           ['angular-loading-bar', 'scriptdoc.xwikiServices', 'scriptdoc.services']);

  app.config(['$locationProvider', 'cfpLoadingBarProvider', function($locationProvider, cfpLoadingBarProvider) {
    $locationProvider.html5Mode({enabled: true, requireBase: false, rewriteLinks: false}).hashPrefix('!');
    cfpLoadingBarProvider.parentSelector = '#contentcontainerinner';
  }]);

  /**
   * List controller
   */
  app.controller('ListCtrl',
                 ['$scope', '$http', 'xwikiService', 'filterService', 'javadocService',
                  function($scope, $http, xwikiService, filterService, javadocService) {
    $scope.filterService = filterService;
    $scope.isExternalUrl = javadocService.isExternalUrl;
    $http.get(xwikiService.getBindingsURL)
      .success(function(data) {
        $scope.bindings = data;
      });
    $scope.getBindingStateLetter = function(binding) {
      if (binding.internal) return 'I';
      return binding.deprecated ? 'D' : 'L';
    };
    $scope.goto = function(event, url) {
      javadocService.location(url);
      event.stopPropagation();
    };
  }]);

  /**
   * Filter controller
   */
  app.controller('FilterCtrl',
                 ['$scope', '$http', 'xwikiService', 'filterService',
                  function($scope, $http, xwikiService, filterService) {
    // Provides bindings to the filter service
    $scope.filterService = filterService;

    // Dynamically fill the list of kinds
    $http.get(xwikiService.getKindsURL)
      .success(function(data) {
        $scope.kinds = data;
      });
  }]);

  /**
   * Javascript controller
   */
  app.controller('JavadocCtrl',
                 ['$scope', '$http', 'javadocService',
                  function($scope, $http, javadocService) {

    // Provides bindings to the javadoc service
    $scope.javadocService = javadocService;
  }]);

  app.directive('javadocDisplayer',
                ['$compile', 'javadocService',
                 function($compile, javadocService){
    return {
      restrict: 'A',
      scope : false,
      transclude:true,
      template: "<div id='javadocdiv' ng-include='javadocService.location()' ng-mouseover='javadocService.hoverIn($event)' ng-click='javadocService.handleClick($event)'></div><div ng-transclude/>",
      link: function($scope, $element, $attrs){
        var el = $element;
        el.removeAttr('style');
        $scope.javadocService = javadocService;
        $scope.$on('$includeContentLoaded', function() {
          var div = el.children('#javadocdiv');
          $scope.loadFailed = false;
          $scope.notFound = !div.html();
          if (!$scope.notFound) {
            javadocService.postLoadHook(el);
          }
        });
        $scope.$on('includeContentError', function() {
          $scope.notFound = false;
          $scope.loadFailed = true;
        });
      }
    };
  }]);

  /**
   * ngPopover directive - Directive to create bootstrap popover on hover
   */
  app.directive('ngPopover', function(){
    return {
      restrict: 'A',
      link: function(scope, element, attrs){
        $(element).hover(function(){
          $(element).popover({
            placement: function() {
              return $(window).width() < 975 ? 'top' : 'left';
            }
          });
          $(element).popover('show');
        }, function(){
          $(element).popover('hide');
        });
      }
    };
  });

  app.run(['javadocService', '$rootScope', '$location', function(javadocService, $rootScope, $location) {
    $rootScope.$on('$locationChangeSuccess', function(event) {
      var url = $location.search().url;
      url = (url) ? url : null; // avoid setter to act as getter since url could be undefined
      javadocService.location(url);
    });
    $(document).ready( function() {
      var container = $('#contentcontainerinner'),
          containerTop = container.offset().top,
          state = true;
      container.css({position:'relative'});
      $(window).on('scroll', function() {
          var scrolled = $(window).scrollTop() >= containerTop;
          if (state && scrolled) {
              container.removeAttr('style');
              state = false;
          } else if (!state && !scrolled) {
              container.css({position:'relative'});
              state = true;             
          }
      });
    });
  }]);

  /**
   * Application bootstrapper
   */
  angular.element(document).ready(function() {
    var appScope;
    if (XWiki.currentDocument.space == "ScriptingDocumentation") {
      appScope = $('#contentcontainer');
    } else {
      appScope = $('.ScriptDocPanel');
    }
    angular.bootstrap(appScope, ['scriptdoc']);
  });
});
