/* global window */
(function(angular, _, moment){
  "use strict";

  // TODO Disqus
  // TODO Show compatible emacs versions for any package
  // TODO Google Analytics http://stackoverflow.com/questions/10713708/tracking-google-analytics-page-views-with-angular-js
  // TODO D3 visualisation for deps
  // TODO Fix json encoding of versions
  // TODO Link to specific github branch
  // TODO Show recent github events on package pages where applicable
  // TODO Voting / starring

  var app = angular.module('melpa', ["ngRoute"]);

  //////////////////////////////////////////////////////////////////////////////
  // SERVICES
  //////////////////////////////////////////////////////////////////////////////

  app.factory('packageService', function($http, $q) {
    var packages = $q.all({archive: $http.get("/archive.json"),
                           recipes: $http.get("/recipes.json"),
                           downloads: $http.get("/download_counts.json") // TODO: handle missing
                          })
          .then(function (info) {

            var calculateSourceURL = function(name, recipe) {
              if (recipe.fetcher == "github") {
                return (/\//.test(recipe.repo) ? "https://github.com/" : "https://gist.github.com/") + recipe.repo;
              } else if (recipe.fetcher == "wiki" && !recipe.files) {
                return "http://www.emacswiki.org/emacs/" + name + ".el";
              } else if (recipe.url) {
                var urlMatch = function(re, prefix) {
                  var m = recipe.url.match(re);
                  return m !== null ? prefix + m[0] : null;
                };
                return urlMatch(/(bitbucket\.org\/[^\/]+\/[^\/\?]+)/, "https://") ||
                  urlMatch(/(gitorious\.org\/[^\/]+\/[^.]+)/, "https://") ||
                  urlMatch(/\Alp:(.*)/, "https://launchpad.net/") ||
                  urlMatch(/\A(https?:\/\/code\.google\.com\/p\/[^\/]+\/)/) ||
                  urlMatch(/\A(https?:\/\/[^.]+\.googlecode\.com\/)/);
              }
              return null;
            };

            var listed = _.intersection(_.keys(info.archive.data), _.keys(info.recipes.data));
            return _(listed).reduce(function(pkgs, name) {
              var built = info.archive.data[name];
              var recipe = info.recipes.data[name];
              var descr = built[2].replace(/\s*\[((?:source: )?\w+)\]$/, "");
              var version = built[0].join(".");
              // Fix up hokey deps, which look like {"clojure-mode":{"2":[0,0]}} for 2.0.0
              var deps = _.map(built[1] || {}, function (val, name) {
                var v1 = _.keys(val)[0];
                return {name: name, version: [v1].concat(val[v1] || []).join('.')};
              });
              var oldNames = recipe['old-names'] || [];
              pkgs[name] = {
                name: name,
                version: version,
                dependencies: deps,
                description: descr,
                source: recipe.fetcher,
                downloads: _.reduce(oldNames.concat(name), function(sum, n) { return sum + (info.downloads.data[n] || 0); }, 0),
                fetcher: recipe.fetcher,
                recipeURL: "https://github.com/milkypostman/melpa/blob/master/recipes/" + name,
                packageURL: "packages/" + name + "-" + version + "." + (built[3] == "single" ? "el" : "tar"),
                sourceURL: calculateSourceURL(name, recipe),
                oldNames: oldNames
              };
              return pkgs;
            }, {});
          });
    var buildStatus = $http.get("/build-status.json").then(function(r) { return r.data; });

    return {
      getPackages: function() {
        return packages;
      },

      dependenciesOn: function(name) {
        return packages.then(function(pkgs) {
          return _.values(pkgs).filter(function(p) {
            return _.findWhere(p.dependencies, {name: name});
          });
        });
      },

      buildStatus: function() {
        return buildStatus;
      }
    };
  });


  //////////////////////////////////////////////////////////////////////////////
  // Controllers
  //////////////////////////////////////////////////////////////////////////////

  app.controller('PackageListCtrl', function ($scope, $timeout, packageService) {
    $scope.orderBy = "name";
    $scope.$on('$routeChangeSuccess', function (ev, current) {
      $scope.enteredSearchTerms = $scope.searchTerms = current.params.q;
    });
    var copyValue;
    $scope.$watch('enteredSearchTerms', function(value) {
      $timeout.cancel(copyValue);
      copyValue = $timeout(function() {
        $scope.searchTerms = value;
      }, 250);
    });

    packageService.getPackages().then(function(pkgs){
      $scope.packages = _.values(pkgs);
      $scope.totalDownloads = _.reduce(_.pluck($scope.packages, "downloads"), function (a, b) { return b === undefined ? a : a + b; }, 0);
    });
    $scope.packageMatcher = function(term) {
      var t = term && term.toLowerCase();
      var searchText = _.memoize(function(pkg) {
        return _([pkg.name, pkg.description, pkg.source, pkg.version, pkg.sourceURL]).compact().invoke('toLowerCase').valueOf().join(' ');
      }, function(pkg) { return pkg.name; });
      return function(pkg) {
        if (!term || !term.match(/\S/)) return true;
        return searchText(pkg).indexOf(t) != -1;
      };
    };
  });

  var detailsCtrl = app.controller('PackageDetailsCtrl', function ($scope, $q, $routeParams, $http, packageService) {
    var packageName =  $routeParams.packageName;
    packageService.getPackages().then(function(pkgs) {
      $scope.allPackages = pkgs;
      $scope.pkg = pkgs[packageName];
      $scope.reverseDependencies = packageService.dependenciesOn(packageName);
      $scope.reverseDependenciesOldNames = $q.all(_.map($scope.pkg.oldNames, packageService.dependenciesOn)).then(_.flatten);
      var downloadCounts = _.pluck(pkgs, 'downloads');
      $scope.downloadsPercentile = _.filter(downloadCounts, function(d) { return d < $scope.pkg.downloads; }).length * 100.0 / downloadCounts.length;
    });
    $scope.readme = $http.get("/packages/" + packageName + "-readme.txt").then(function(r) { return r.data; });
    $scope.havePackage = function(pkgName) {
      return $scope.allPackages[pkgName];
    };
  });
  detailsCtrl.resolve = {
    // Fail to resolve unless this particular package exists
    pkgs: function (packageService, $route, $q) {
      var deferred = $q.defer();
      packageService.getPackages().then(function(pkgs) {
        if (pkgs[$route.current.params.packageName])
          deferred.resolve(pkgs);
        else
          deferred.reject("No such package");
      });
      return deferred.promise;
    }
  };

  app.controller('BuildStatusCtrl', function($scope, packageService) {
    packageService.buildStatus().then(function(status) {
      $scope.completionTime = new Date(status.completed * 1000);
    });
  });

  app.controller('AppCtrl', function($scope, $rootScope, $route, $window) {
    $scope.hideSplash = false;
    $rootScope.$on("$routeChangeSuccess", function() {
      $scope.hideSplash = ($route.current.controller != 'PackageListCtrl');
    });
    $rootScope.$on("$locationChangeSuccess", function(e, newURL, oldURL) {
      //jshint unused: false
      if (newURL && (newURL.$$route||newURL).redirectTo) return;
      if ($window._gaq && newURL != (oldURL + "#/")) {
        var l = $window.location;
        var path = l.pathname + l.hash + l.search;
        $window._gaq.push (["_trackPageview", path ]);
      }
    });
  });

  //////////////////////////////////////////////////////////////////////////////
  // Directives
  //////////////////////////////////////////////////////////////////////////////

  app.directive("viewOrError", function($rootScope) {
    return {
      template: '<div>' +
        '<div class="alert alert-danger" ng-if="routeError"><strong>Error: </strong>{{routeError}}</div>' +
        '<div ng-if="!routeError"><div ng-view><ng-transclude></ng-transclude></div></div>'+'</div>',
      transclude: true,
      link: function(scope) {
        $rootScope.$on("$routeChangeError", function (event, current, previous, rejection) {
          //jshint unused: false
          scope.routeError = rejection;
        });
        $rootScope.$on("$routeChangeSuccess", function () {
          scope.routeError = null;
        });
      }
    };
  });

  app.directive("sortIndicator", function() {
    return {
      scope: { sortIndicator: "@" },
      replace: true,
      template: '<span ng-class="classname"></span>',
      link: function(scope) {
        scope.$watch('sortIndicator', function() {
          scope.classname = 'table-sort-indicator glyphicon ' +
            ({'asc': 'glyphicon-chevron-down',
              'desc': 'glyphicon-chevron-up' }[scope.sortIndicator] ||
             'glyphicon-minus');
        });
      }
    };
  });

  app.directive("tableSort", function() {
    return {
      scope: {
        tableSort: "=",
        sortField: "@"
      },
      transclude: true,
      template: '<div class="table-sort"></span><span ng-transclude></span><span sort-indicator="{{order}}"></div>',
      link: function(scope, element) {
        var sortingAsc = function() {
          return _([scope.sortField, "+" + scope.sortField]).contains(scope.tableSort);
        };
        var sortingDesc = function() {
          return scope.tableSort == "-" + scope.sortField;
        };
        element.bind('click', function() {
          scope.$apply(function() {
            scope.tableSort = sortingAsc() ? "-" + scope.sortField : scope.sortField;
          });
        });
        scope.$watch('tableSort', function() {
          scope.order = (function() {
              if (sortingAsc()) return 'asc';
              if (sortingDesc()) return 'desc';
              return '';
          })();
        });
      }
    };
  });

  //////////////////////////////////////////////////////////////////////////////
  // Filters
  //////////////////////////////////////////////////////////////////////////////
  app.filter('relativeTime', function() {
    return function(val) {
      return val && moment(val).fromNow();
    };
  });

  //////////////////////////////////////////////////////////////////////////////
  // Routes
  //////////////////////////////////////////////////////////////////////////////
  app.config(['$locationProvider', function($locationProvider){
    $locationProvider.html5Mode(false);
  }]);
  app.config(['$routeProvider', function($routeProvider){
    $routeProvider
      .when('/', {templateUrl: 'partials/list.html', controller: 'PackageListCtrl', splash: true})
      .when('/getting-started', {templateUrl: 'partials/getting-started.html'})
      .when('/:packageName', {templateUrl: 'partials/package.html', controller: 'PackageDetailsCtrl', resolve: detailsCtrl.resolve});
  }]);

})(window.angular, window._, window.moment);