var TEAM_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$';
var ROBOT_PATTERN = '^[a-zA-Z][a-zA-Z0-9]+$';

function getFirstTextLine(commentString) {
  if (!commentString) { return ''; }
      
  var lines = commentString.split('\n');
  var MARKDOWN_CHARS = {
    '#': true,
    '-': true,
    '>': true,
    '`': true
  };

  for (var i = 0; i < lines.length; ++i) {
    // Skip code lines.
    if (lines[i].indexOf('    ') == 0) {
      continue;
    }

    // Skip empty lines.
    if ($.trim(lines[i]).length == 0) {
      continue;
    }

    // Skip control lines.
    if (MARKDOWN_CHARS[$.trim(lines[i])[0]]) {
      continue;
    }

    return getMarkedDown(lines[i]);
  }

  return '';
}

function createRobotAccount(Restangular, is_org, orgname, name, callback) {
  var url = is_org ? getRestUrl('organization', orgname, 'robots', name) :
      getRestUrl('user/robots', name);
  var createRobot = Restangular.one(url);
  createRobot.customPUT().then(callback, function(resp) {
    bootbox.dialog({
      "message": resp.data ? resp.data : 'The robot account could not be created',
      "title": "Cannot create robot account",
      "buttons": {
        "close": {
          "label": "Close",
          "className": "btn-primary"
        }
      }
    });
  });
}

function createOrganizationTeam(Restangular, orgname, teamname, callback) {
  var createTeam = Restangular.one(getRestUrl('organization', orgname, 'team', teamname));
  var data = {
    'name': teamname,
    'role': 'member'
  };
  
  createTeam.customPOST(data).then(callback, function() {
    bootbox.dialog({
      "message": resp.data ? resp.data : 'The team could not be created',
      "title": "Cannot create team",
      "buttons": {
        "close": {
          "label": "Close",
          "className": "btn-primary"
        }
      }
    });
  });
}

function getRestUrl(args) {
  var url = '';
  for (var i = 0; i < arguments.length; ++i) {
    if (i > 0) {
       url += '/';
    }
    url += encodeURI(arguments[i])
  }
  return url;
}

function getMarkedDown(string) {
  return Markdown.getSanitizingConverter().makeHtml(string || '');
}

// Start the application code itself.
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', '$strap.directives', 'ngCookies'], function($provide, cfpLoadingBarProvider) {
    cfpLoadingBarProvider.includeSpinner = false;
    
    $provide.factory('CookieService', ['$cookies', '$cookieStore', function($cookies, $cookieStore) {
      var cookieService = {};
      cookieService.putPermanent = function(name, value) {
        document.cookie = escape(name) + "=" + escape(value) + "; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/";
      };

      cookieService.putSession = function(name, value) {
        $cookies[name] = value;
      };

      cookieService.clear = function(name) {
        $cookies[name] = '';
      };

      cookieService.get = function(name) {
        return $cookies[name];
      };

      return cookieService;
    }]);

    $provide.factory('UserService', ['Restangular', 'CookieService', function(Restangular, CookieService) {
      var userResponse = {
        verified: false,
        anonymous: true,
        username: null,
        email: null,
        askForPassword: false,
        organizations: []
      }

      var userService = {}

      userService.hasEverLoggedIn = function() {
        return CookieService.get('quay.loggedin') == 'true';
      };

      userService.updateUserIn = function(scope, opt_callback) {
        scope.$watch(function () { return userService.currentUser(); }, function (currentUser) {
          scope.user = currentUser;
          if (opt_callback) {
            opt_callback(currentUser); 
          }
        }, true);
      };

      userService.load = function(opt_callback) {
        var userFetch = Restangular.one('user/');
        userFetch.get().then(function(loadedUser) {
          userResponse = loadedUser;

          if (!userResponse.anonymous) {
            mixpanel.identify(userResponse.username);
            mixpanel.people.set({
              '$email': userResponse.email,
              '$username': userResponse.username,
              'verified': userResponse.verified
            });
            mixpanel.people.set_once({
              '$created': new Date()
            })

            CookieService.putPermanent('quay.loggedin', 'true');
          }

          if (opt_callback) {
            opt_callback();
          }
        });
      };

      userService.getOrganization = function(name) {
        if (!userResponse || !userResponse.organizations) { return null; }
        for (var i = 0; i < userResponse.organizations.length; ++i) {
          var org = userResponse.organizations[i];
          if (org.name == name) {
            return org;
          }
        }

        return null;
      };

      userService.isNamespaceAdmin = function(namespace) {
        if (namespace == userResponse.username) {
          return true;
        }

        var org = userService.getOrganization(namespace);
        if (!org) {
          return false;
        }

        return org.is_org_admin;
      };
        
      userService.currentUser = function() {
        return userResponse;
      };

      // Load the user the first time.
      userService.load();

      return userService;
    }]);


    $provide.factory('ApiService', ['Restangular', function(Restangular) {
      var apiService = {}
      apiService.at = function(locationPieces) {
        var location = getRestUrl.apply(this, arguments);
        var info = {
          'url': location,
          'caller': Restangular.one(location),
          'withOptions': function(options) {
            info.options = options;
            return info;
          },
            'get': function(processor, opt_errorHandler) {
            var options = info.options;
            var caller = info.caller;
            var result = {
              'loading': true,
              'value': null,
              'hasError': false
            };

            caller.get(options).then(function(resp) {
              result.value = processor(resp);
              result.loading = false;
            }, function(resp) {
              result.hasError = true;
              result.loading = false;
              if (opt_errorHandler) {
                opt_errorHandler(resp);
              }
            });

            return result;
          }
        };

        return info;
      };

      return apiService;
    }]);

    $provide.factory('KeyService', ['$location', function($location) {
      var keyService = {}

      if ($location.host() === 'quay.io') {
        keyService['stripePublishableKey'] = 'pk_live_P5wLU0vGdHnZGyKnXlFG4oiu';
        keyService['githubClientId'] = '5a8c08b06c48d89d4d1e';
      } else {        
        keyService['stripePublishableKey'] = 'pk_test_uEDHANKm9CHCvVa2DLcipGRh';
        keyService['githubClientId'] = 'cfbc4aca88e5c1b40679';
      }

      return keyService;
    }]);

    $provide.factory('PlanService', ['Restangular', 'KeyService', 'UserService', 'CookieService',
        function(Restangular, KeyService, UserService, CookieService) {
      var plans = null;
      var planDict = {};
      var planService = {};
      var listeners = [];

      planService.getFreePlan = function() {
        return 'free';
      };

      planService.registerListener = function(obj, callback) {
        listeners.push({'obj': obj, 'callback': callback});
      };

      planService.unregisterListener = function(obj) {
        for (var i = 0; i < listeners.length; ++i) {
          if (listeners[i].obj == obj) {
            listeners.splice(i, 1);
            break;
          }
        }
      };

      planService.notePlan = function(planId) {
        CookieService.putSession('quay.notedplan', planId);
      };

      planService.isOrgCompatible = function(plan) {
        return plan['stripeId'] == planService.getFreePlan() || plan['bus_features'];
      };

      planService.getMatchingBusinessPlan = function(callback) {
        planService.getPlans(function() {
          planService.getSubscription(null, function(sub) {
            var plan = planDict[sub.plan];
            if (!plan) {
              planService.getMinimumPlan(0, true, callback);
              return;
            }

            var count = Math.max(sub.usedPrivateRepos, plan.privateRepos);
            planService.getMinimumPlan(count, true, callback);
          }, function() {
            planService.getMinimumPlan(0, true, callback);
          });
        });
      };

      planService.handleNotedPlan = function() {
        var planId = planService.getAndResetNotedPlan();
        if (!planId) { return; }

        UserService.load(function() {
          if (UserService.currentUser().anonymous) {
            return;
          }

          planService.getPlan(planId, function(plan) {
            if (planService.isOrgCompatible(plan)) {
              document.location = '/organizations/new/?plan=' + planId;
            } else {
              document.location = '/user?plan=' + planId;
            }
          });
        });
      };

      planService.getAndResetNotedPlan = function() {
        var planId = CookieService.get('quay.notedplan');
        CookieService.clear('quay.notedplan');
        return planId;
      };
      
      planService.handleCardError = function(resp) {
        if (!planService.isCardError(resp)) { return; }

        bootbox.dialog({
          "message": resp.data.carderror,
          "title": "Credit card issue",
          "buttons": {
            "close": {
              "label": "Close",
              "className": "btn-primary"
            }
          }
        });
      };

      planService.isCardError = function(resp) {
        return resp && resp.data && resp.data.carderror;
      };

      planService.verifyLoaded = function(callback) {
        if (plans) {
          callback(plans);
          return;
        }

        var getPlans = Restangular.one('plans');
        getPlans.get().then(function(data) {
          var i = 0;
          for(i = 0; i < data.plans.length; i++) {
            planDict[data.plans[i].stripeId] = data.plans[i];
          }
          plans = data.plans;
          callback(plans);
        }, function() { callback([]); });
      };

      planService.getPlans = function(callback, opt_includePersonal) {
        planService.verifyLoaded(function() {
          var filtered = [];
          for (var i = 0; i < plans.length; ++i) {
            var plan = plans[i];
            if (plan['deprecated']) { continue; }
            if (!opt_includePersonal && !planService.isOrgCompatible(plan)) { continue; }
            filtered.push(plan);
          }
          callback(filtered);
        });
      };

      planService.getPlan = function(planId, callback) {
        planService.getPlanIncludingDeprecated(planId, function(plan) {
          if (!plan['deprecated']) {
            callback(plan);
          }
        });
      };

      planService.getPlanIncludingDeprecated = function(planId, callback) {
        planService.verifyLoaded(function() {
          if (planDict[planId]) {            
            callback(planDict[planId]);
          }
        });
      };

      planService.getMinimumPlan = function(privateCount, isBusiness, callback) {
        planService.getPlans(function(plans) {          
          for (var i = 0; i < plans.length; i++) {
            var plan = plans[i];
            if (isBusiness && !planService.isOrgCompatible(plan)) {
              continue;
            }

            if (plan.privateRepos >= privateCount) {
              callback(plan);
              return;
            }
          }

          callback(null);
        });
      };

      planService.getSubscription = function(orgname, success, failure) {
        var url = planService.getSubscriptionUrl(orgname);
        var getSubscription = Restangular.one(url);
        getSubscription.get().then(success, failure);
      };

      planService.getSubscriptionUrl = function(orgname) {
        return orgname ? getRestUrl('organization', orgname, 'plan') : 'user/plan';
      };

      planService.setSubscription = function(orgname, planId, success, failure, opt_token) {
        var subscriptionDetails = {
          plan: planId
        };

        if (opt_token) {
          subscriptionDetails['token'] = opt_token.id;
        }

        var url = planService.getSubscriptionUrl(orgname);
        var createSubscriptionRequest = Restangular.one(url);
        createSubscriptionRequest.customPUT(subscriptionDetails).then(function(resp) {
          success(resp);
          planService.getPlan(planId, function(plan) {
            for (var i = 0; i < listeners.length; ++i) {
              listeners[i]['callback'](plan);
            }
          });
        }, failure);
      };

      planService.getCardInfo = function(orgname, callback) {
        var url = orgname ? getRestUrl('organization', orgname, 'card') : 'user/card';       
        var getCard = Restangular.one(url);
        getCard.customGET().then(function(resp) {
          callback(resp.card);
        }, function() {
          callback({'is_valid': false});
        });
      };

      planService.changePlan = function($scope, orgname, planId, callbacks) {
        if (callbacks['started']) {
          callbacks['started']();
        }

        planService.getPlan(planId, function(plan) {
          if (orgname && !planService.isOrgCompatible(plan)) { return; }

          planService.getCardInfo(orgname, function(cardInfo) {
            if (plan.price > 0 && !cardInfo.last4) {
              planService.showSubscribeDialog($scope, orgname, planId, callbacks);
              return;
            }
        
            planService.setSubscription(orgname, planId, callbacks['success'], function(resp) {
              planService.handleCardError(resp);
              callbacks['failure'](resp);
            });
          });
        });
      };

      planService.changeCreditCard = function($scope, orgname, callbacks) {
        if (callbacks['opening']) {
          callbacks['opening']();
        }

        var submitted = false;
        var submitToken = function(token) {
          if (submitted) { return; }
          submitted = true;
          $scope.$apply(function() {            
            if (callbacks['started']) {
              callbacks['started']();
            }
            
            var cardInfo = {
              'token': token.id
            };

            var url = orgname ? getRestUrl('organization', orgname, 'card') : 'user/card';
            var changeCardRequest = Restangular.one(url);
              changeCardRequest.customPOST(cardInfo).then(callbacks['success'], function(resp) {
                planService.handleCardError(resp);
                callbacks['failure'](resp);
              });
          });
        };

        var email = planService.getEmail(orgname);
        StripeCheckout.open({
            key:         KeyService.stripePublishableKey,
            address:     false,
            email:       email,
            currency:    'usd',
            name:        'Update credit card',
            description: 'Enter your credit card number',
            panelLabel:  'Update',
            token:       submitToken,
            image:       'static/img/quay-icon-stripe.png',
            opened:      function() { $scope.$apply(function() { callbacks['opened']() }); },
            closed:      function() { $scope.$apply(function() { callbacks['closed']() }); }
         });
      };

      planService.getEmail = function(orgname) {
        var email = null;
        if (UserService.currentUser()) {
          email = UserService.currentUser().email;

          if (orgname) {
            org = UserService.getOrganization(orgname);
            if (org) {
              emaiil = org.email;
            }
          }
        }
        return email;
      };

      planService.showSubscribeDialog = function($scope, orgname, planId, callbacks) {
        if (callbacks['opening']) {
          callbacks['opening']();
        }

        var submitted = false;
        var submitToken = function(token) {
          if (submitted) { return; }
          submitted = true;

          mixpanel.track('plan_subscribe');

          $scope.$apply(function() {
            if (callbacks['started']) {
              callbacks['started']();
            }
            planService.setSubscription(orgname, planId, callbacks['success'], callbacks['failure'], token);
          });
        };

        planService.getPlan(planId, function(planDetails) {
          var email = planService.getEmail(orgname);
          StripeCheckout.open({
            key:         KeyService.stripePublishableKey,
            address:     false,
            email:       email,
            amount:      planDetails.price,
            currency:    'usd',
            name:        'Quay ' + planDetails.title + ' Subscription',
            description: 'Up to ' + planDetails.privateRepos + ' private repositories',
            panelLabel:  'Subscribe',
            token:       submitToken,
            image:       'static/img/quay-icon-stripe.png',
            opened:      function() { $scope.$apply(function() { callbacks['opened']() }); },
            closed:      function() { $scope.$apply(function() { callbacks['closed']() }); }
          });
        });
      };

      return planService;
    }]);
  }).
  directive('match', function($parse) {
    return {
      require: 'ngModel',
      link: function(scope, elem, attrs, ctrl) {
        scope.$watch(function() {        
          return $parse(attrs.match)(scope) === ctrl.$modelValue;
        }, function(currentValue) {
          ctrl.$setValidity('mismatch', currentValue);
        });
      }
    };
  }).
  directive('onresize', function ($window, $parse) {
    return function (scope, element, attr) {
      var fn = $parse(attr.onresize);

      var notifyResized = function() {
        scope.$apply(function () {
          fn(scope);
        });
      };

      angular.element($window).on('resize', null, notifyResized);

      scope.$on('$destroy', function() {
        angular.element($window).off('resize', null, notifyResized);
      });
    };
  }).
  config(['$routeProvider', '$locationProvider', '$analyticsProvider',
    function($routeProvider, $locationProvider, $analyticsProvider) {

    $analyticsProvider.virtualPageviews(true);

    $locationProvider.html5Mode(true);

    // WARNING WARNING WARNING
    // If you add a route here, you must add a corresponding route in thr endpoints/web.py
    // index rule to make sure that deep links directly deep into the app continue to work.
    // WARNING WARNING WARNING
    $routeProvider.
      when('/repository/:namespace/:name', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
                                            fixFooter: false, reloadOnSearch: false}).
      when('/repository/:namespace/:name/tag/:tag', {templateUrl: '/static/partials/view-repo.html', controller: RepoCtrl,
                                                     fixFooter: false}).
      when('/repository/:namespace/:name/image/:image', {templateUrl: '/static/partials/image-view.html', controller: ImageViewCtrl, reloadOnSearch: false}).
      when('/repository/:namespace/:name/admin', {templateUrl: '/static/partials/repo-admin.html', controller:RepoAdminCtrl, reloadOnSearch: false}).
      when('/repository/', {title: 'Repositories', description: 'Public and private docker repositories list',
                            templateUrl: '/static/partials/repo-list.html', controller: RepoListCtrl}).
      when('/user/', {title: 'Account Settings', description:'Account settings for Quay.io', templateUrl: '/static/partials/user-admin.html',
                      reloadOnSearch: false, controller: UserAdminCtrl}).
      when('/guide/', {title: 'Guide', description:'Guide to using private docker repositories on Quay.io', templateUrl: '/static/partials/guide.html',
                       controller: GuideCtrl}).
      when('/plans/', {title: 'Plans and Pricing', description: 'Plans and pricing for private docker repositories on Quay.io',
                       templateUrl: '/static/partials/plans.html', controller: PlansCtrl}).
      when('/security/', {title: 'Security', description: 'Security features used when transmitting and storing data',
                       templateUrl: '/static/partials/security.html', controller: SecurityCtrl}).
      when('/signin/', {title: 'Sign In', description: 'Sign into Quay.io', templateUrl: '/static/partials/signin.html', controller: SigninCtrl}).
      when('/new/', {title: 'Create new repository', description: 'Create a new public or private docker repository, optionally constructing from a dockerfile',
                     templateUrl: '/static/partials/new-repo.html', controller: NewRepoCtrl}).
      when('/organizations/', {title: 'Organizations', description: 'Private docker repository hosting for businesses and organizations',
                                 templateUrl: '/static/partials/organizations.html', controller: OrgsCtrl}).
      when('/organizations/new/', {title: 'New Organization', description: 'Create a new organization on Quay.io',
                                   templateUrl: '/static/partials/new-organization.html', controller: NewOrgCtrl}).
      when('/organization/:orgname', {templateUrl: '/static/partials/org-view.html', controller: OrgViewCtrl}).
      when('/organization/:orgname/admin', {templateUrl: '/static/partials/org-admin.html', controller: OrgAdminCtrl, reloadOnSearch: false}).
      when('/organization/:orgname/teams/:teamname', {templateUrl: '/static/partials/team-view.html', controller: TeamViewCtrl}).
      when('/organization/:orgname/logs/:membername', {templateUrl: '/static/partials/org-member-logs.html', controller: OrgMemberLogsCtrl}).
      when('/v1/', {title: 'Activation information', templateUrl: '/static/partials/v1-page.html', controller: V1Ctrl}).
      when('/', {title: 'Hosted Private Docker Registry', templateUrl: '/static/partials/landing.html', controller: LandingCtrl}).
      otherwise({redirectTo: '/'});
  }]).
  config(function(RestangularProvider) {
    RestangularProvider.setBaseUrl('/api/');
  });


quayApp.directive('entityReference', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/entity-reference.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'name': '=name',
      'orgname': '=orgname',
      'team': '=team',
      'isrobot': '=isrobot'
    },
    controller: function($scope, $element, UserService) {
      $scope.getIsAdmin = function(orgname) {
        return UserService.isNamespaceAdmin(orgname);
      };

      $scope.getPrefix = function(name) {
        if (!name) { return ''; }
        var plus = name.indexOf('+');
        return name.substr(0, plus + 1);
      };

      $scope.getShortenedName = function(name) {
        if (!name) { return ''; }
        var plus = name.indexOf('+');
        return name.substr(plus + 1);
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('markdownView', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/markdown-view.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'content': '=content',
      'firstLineOnly': '=firstLineOnly'
    },
    controller: function($scope, $element, $sce) {
      $scope.getMarkedDown = function(content, firstLineOnly) {
        if (firstLineOnly) {
          content = getFirstTextLine(content);
        }
        return $sce.trustAsHtml(getMarkedDown(content));
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('repoCircle', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/repo-circle.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'repo': '=repo'
    },
    controller: function($scope, $element) {
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('userSetup', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/user-setup.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {
      'redirectUrl': '=redirectUrl',
      'signInStarted': '&signInStarted',
      'signedIn': '&signedIn'
    },
    controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) {
      $scope.sendRecovery = function() {
        var signinPost = Restangular.one('recovery');
        signinPost.customPOST($scope.recovery).then(function() {
          $scope.invalidEmail = false;
          $scope.sent = true;
        }, function(result) {
          $scope.invalidEmail = true;
          $scope.sent = false;
        });
      };

      $scope.hasSignedIn = function() {
        return UserService.hasEverLoggedIn();
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('signinForm', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/signin-form.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {
      'redirectUrl': '=redirectUrl',
      'signInStarted': '&signInStarted',
      'signedIn': '&signedIn'
    },
    controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) {
      $scope.showGithub = function() {
        $scope.markStarted();

        var mixpanelDistinctIdClause = '';
        if (mixpanel.get_distinct_id !== undefined) {
          $scope.mixpanelDistinctIdClause = "&state=" + encodeURIComponent(mixpanel.get_distinct_id());
        }

        // Needed to ensure that UI work done by the started callback is finished before the location
        // changes.
        $timeout(function() {
          var url = 'https://github.com/login/oauth/authorize?client_id=' + encodeURIComponent(KeyService.githubClientId) +
                '&scope=user:email' + mixpanelDistinctIdClause;
          document.location = url;
        }, 250);
      };

      $scope.markStarted = function() {
       if ($scope.signInStarted != null) {
         $scope.signInStarted();
       }
      };

      $scope.signin = function() {
        $scope.markStarted();

        var signinPost = Restangular.one('signin');
        signinPost.customPOST($scope.user).then(function() {
          $scope.needsEmailVerification = false;
          $scope.invalidCredentials = false;

          if ($scope.signedIn != null) {
            $scope.signedIn();
          }
            
          UserService.load();

          // Redirect to the specified page or the landing page
          // Note: The timeout of 500ms is needed to ensure dialogs containing sign in
          // forms get removed before the location changes.
          $timeout(function() {
           if ($scope.redirectUrl  == $location.path()) {
             return;
           }
           $location.path($scope.redirectUrl ? $scope.redirectUrl : '/');
          }, 500);
        }, function(result) {
          $scope.needsEmailVerification = result.data.needsEmailVerification;
          $scope.invalidCredentials = result.data.invalidCredentials;
        });
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('signupForm', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/signup-form.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {

    },
    controller: function($scope, $location, $timeout, Restangular, KeyService, UserService) {      
      $('.form-signup').popover();

      angulartics.waitForVendorApi(mixpanel, 500, function(loadedMixpanel) {
        var mixpanelId = loadedMixpanel.get_distinct_id();
        $scope.github_state_clause = '&state=' + mixpanelId;    
      });

      $scope.githubClientId = KeyService.githubClientId;

      $scope.awaitingConfirmation = false;
      $scope.registering = false;
        
      $scope.register = function() {
        $('.form-signup').popover('hide');
        $scope.registering = true;

        var newUserPost = Restangular.one('user/');
        newUserPost.customPOST($scope.newUser).then(function() {
          $scope.awaitingConfirmation = true;
          $scope.registering  = false;

          mixpanel.alias($scope.newUser.username);
        }, function(result) {
          $scope.registering  = false;
          $scope.registerError = result.data.message;
          $timeout(function() {
            $('.form-signup').popover('show');
          });
        });
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('plansTable', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/plans-table.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'plans': '=plans',
      'currentPlan': '=currentPlan'
    },
    controller: function($scope, $element) {
      $scope.setPlan = function(plan) {
        $scope.currentPlan = plan;
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('dockerAuthDialog', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/docker-auth-dialog.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {
      'username': '=username',
      'token': '=token',
      'shown': '=shown',
      'counter': '=counter'
    },
    controller: function($scope, $element, Restangular) {     
      $scope.isDownloadSupported = function() {
        try { return !!new Blob(); } catch(e){}
        return false;
      };

      $scope.downloadCfg = function() {
        var auth = $.base64.encode($scope.username + ":" + $scope.token);
        config = {
          "https://quay.io/v1/": {
            "auth": auth,
            "email": ""
          }
        };

        var file = JSON.stringify(config, null, ' ');
        var blob = new Blob([file]);
        saveAs(blob, '.dockercfg');
      };

      var show = function(r) {
        if (!$scope.shown || !$scope.username || !$scope.token) {
          $('#dockerauthmodal').modal('hide');
          return;
        }
         
        $('#copyClipboard').clipboardCopy();
        $('#dockerauthmodal').modal({});
      };

      $scope.$watch('counter', show);
      $scope.$watch('shown', show);
      $scope.$watch('username', show);
      $scope.$watch('token', show);
    }
  };
  return directiveDefinitionObject;
});


quayApp.filter('visibleLogFilter', function () {
  return function (logs, allowed) {
    if (!allowed) {
      return logs;
    }
    
    var filtered = [];
    angular.forEach(logs, function (log) {
      if (allowed[log.kind]) {
        filtered.push(log);
      }
    });

    return filtered;
  };
});


quayApp.directive('billingInvoices', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/billing-invoices.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'organization': '=organization',
      'user': '=user',
      'visible': '=visible'
    },
    controller: function($scope, $element, $sce, Restangular) {
      $scope.loading = false;
      $scope.invoiceExpanded = {};

      $scope.toggleInvoice = function(id) {
        $scope.invoiceExpanded[id] = !$scope.invoiceExpanded[id];
      };

      var update = function() {
        var hasValidUser = !!$scope.user;
        var hasValidOrg = !!$scope.organization;
        var isValid = hasValidUser || hasValidOrg;

        if (!$scope.visible || !isValid) {
          return;
        }
        
        $scope.loading = true;

        var url = getRestUrl('user/invoices');
        if ($scope.organization) {
          url = getRestUrl('organization', $scope.organization.name, 'invoices');
        }

        var getInvoices = Restangular.one(url);
        getInvoices.get().then(function(resp) {
          $scope.invoices = resp.invoices;
          $scope.loading = false;
        });
      };

      $scope.$watch('organization', update);
      $scope.$watch('user', update);
      $scope.$watch('visible', update);
    }
  };

  return directiveDefinitionObject;
});


quayApp.directive('logsView', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/logs-view.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'organization': '=organization',
      'user': '=user',
      'visible': '=visible',
      'repository': '=repository',
      'performer': '=performer'
    },
    controller: function($scope, $element, $sce, Restangular) {
      $scope.loading = true;
      $scope.logs = null;
      $scope.kindsAllowed = null;
      $scope.chartVisible = true;
      $scope.logsPath = '';
      
      var datetime = new Date();
      $scope.logStartDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate() - 7);
      $scope.logEndDate = new Date(datetime.getUTCFullYear(), datetime.getUTCMonth(), datetime.getUTCDate());

      var logDescriptions = {
          'account_change_plan': 'Change plan',
          'account_change_cc': 'Update credit card',
          'account_change_password': 'Change password',
          'account_convert': 'Convert account to organization',
          'create_robot': 'Create Robot Account: {robot}',
          'delete_robot': 'Delete Robot Account: {robot}',
          'create_repo': 'Create Repository: {repo}',
          'push_repo': 'Push to repository: {repo}',
          'pull_repo': function(metadata) {
            if (metadata.token) {
              return 'Pull repository {repo} via token {token}';
            } else if (metadata.username) {
              return 'Pull repository {repo} by {username}';
            } else {
              return 'Public pull of repository {repo} by {_ip}';
            }
          },
          'delete_repo': 'Delete repository: {repo}',
          'change_repo_permission': function(metadata) {
            if (metadata.username) {
              return 'Change permission for user {username} in repository {repo} to {role}';
            } else if (metadata.team) {
                return 'Change permission for team {team} in repository {repo} to {role}';
            } else if (metadata.token) {
              return 'Change permission for token {token} in repository {repo} to {role}';
            }
          },
          'delete_repo_permission': function(metadata) {
            if (metadata.username) {
              return 'Remove permission for user {username} from repository {repo}';
            } else if (metadata.team) {
                return 'Remove permission for team {team} from repository {repo}';
            } else if (metadata.token) {
              return 'Remove permission for token {token} from repository {repo}';
            }
          },
          'change_repo_visibility': 'Change visibility for repository {repo} to {visibility}',
          'add_repo_accesstoken': 'Create access token {token} in repository {repo}',
          'delete_repo_accesstoken': 'Delete access token {token} in repository {repo}',
          'add_repo_webhook': 'Add webhook in repository {repo}',
          'delete_repo_webhook': 'Delete webhook in repository {repo}',
          'set_repo_description': 'Change description for repository {repo}: {description}',
          'build_dockerfile': 'Build image from Dockerfile for repository {repo}',
          'org_create_team': 'Create team: {team}',
          'org_delete_team': 'Delete team: {team}',
          'org_add_team_member': 'Add member {member} to team {team}',
          'org_remove_team_member': 'Remove member {member} from team {team}',
          'org_set_team_description': 'Change description of team {team}: {description}',
          'org_set_team_role': 'Change permission of team {team} to {role}'
      };

      var logKinds = {
          'account_change_plan': 'Change plan',
          'account_change_cc': 'Update credit card',
          'account_change_password': 'Change password',
          'account_convert': 'Convert account to organization',
          'create_robot': 'Create Robot Account',
          'delete_robot': 'Delete Robot Account',
          'create_repo': 'Create Repository',
          'push_repo': 'Push to repository',
          'pull_repo': 'Pull repository',
          'delete_repo': 'Delete repository',
          'change_repo_permission': 'Change repository permission',
          'delete_repo_permission': 'Remove user permission from repository',
          'change_repo_visibility': 'Change repository visibility',
          'add_repo_accesstoken': 'Create access token',
          'delete_repo_accesstoken': 'Delete access token',
          'add_repo_webhook': 'Add webhook',
          'delete_repo_webhook': 'Delete webhook',
          'set_repo_description': 'Change repository description',
          'build_dockerfile': 'Build image from Dockerfile',
          'org_create_team': 'Create team',
          'org_delete_team': 'Delete team',
          'org_add_team_member': 'Add team member',
          'org_remove_team_member': 'Remove team member',
          'org_set_team_description': 'Change team description',
          'org_set_team_role': 'Change team permission'
      };

      var getDateString = function(date) {
        return (date.getMonth() + 1) + '/' + date.getDate() + '/' + date.getFullYear();
      };

      var getOffsetDate = function(date, days) {
        return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
      };

      var update = function() {
        var hasValidUser = !!$scope.user;
        var hasValidOrg = !!$scope.organization;
        var hasValidRepo = $scope.repository && $scope.repository.namespace;
        var isValid = hasValidUser || hasValidOrg || hasValidRepo;

        if (!$scope.visible || !isValid) {
          return;
        }

        var twoWeeksAgo = getOffsetDate($scope.logEndDate, -14);
        if ($scope.logStartDate > $scope.logEndDate || $scope.logStartDate < twoWeeksAgo) {
          $scope.logStartDate = twoWeeksAgo;
        }

        $scope.loading = true;

        var url = getRestUrl('user/logs');
        if ($scope.organization) {
          url = getRestUrl('organization', $scope.organization.name, 'logs');
        }
        if ($scope.repository) {
          url = getRestUrl('repository', $scope.repository.namespace, $scope.repository.name, 'logs');
        }

        url += '?starttime=' + encodeURIComponent(getDateString($scope.logStartDate));
        url += '&endtime=' + encodeURIComponent(getDateString($scope.logEndDate));

        if ($scope.performer) {
          url += '&performer=' + encodeURIComponent($scope.performer.username);
        }

        var loadLogs = Restangular.one(url);
        loadLogs.customGET().then(function(resp) {
          $scope.logsPath = '/api/' + url;

          if (!$scope.chart) {
            $scope.chart = new LogUsageChart(logKinds);
            $($scope.chart).bind('filteringChanged', function(e) {
              $scope.$apply(function() { $scope.kindsAllowed = e.allowed; });
            });
          }

          $scope.chart.draw('bar-chart', resp.logs, $scope.logStartDate, $scope.logEndDate);
          $scope.kindsAllowed = null;
          $scope.logs = resp.logs;
          $scope.loading = false;
        });
      };

      $scope.toggleChart = function() {
        $scope.chartVisible = !$scope.chartVisible;
      };

      $scope.isVisible = function(allowed, kind) {
        return allowed == null || allowed.hasOwnProperty(kind);
      };

      $scope.getColor = function(kind) {
        return $scope.chart.getColor(kind);
      };

      $scope.getDescription = function(log) {
        var fieldIcons = {
          'username': 'user',
          'team': 'group',
          'token': 'key',
          'repo': 'hdd',
          'robot': 'wrench'
        };

        if (log.ip) {
          log.metadata['_ip'] = log.ip;
        }

        var description = logDescriptions[log.kind] || logTitles[log.kind] || log.kind;
        if (typeof description != 'string') {
          description = description(log.metadata);
        }
          
        for (var key in log.metadata) {
          if (log.metadata.hasOwnProperty(key)) {
            var markedDown = getMarkedDown(log.metadata[key].toString());
            markedDown = markedDown.substr('<p>'.length, markedDown.length - '<p></p>'.length);

            var icon = fieldIcons[key];
            if (icon) {
              markedDown = '<i class="fa fa-' + icon + '"></i>' + markedDown;
            }

            description = description.replace('{' + key + '}', '<code>' + markedDown + '</code>');
          }
        }
        return $sce.trustAsHtml(description);
      };

      $scope.$watch('organization', update);
      $scope.$watch('user', update);
      $scope.$watch('repository', update);
      $scope.$watch('visible', update);
      $scope.$watch('performer', update);
      $scope.$watch('logStartDate', update);
      $scope.$watch('logEndDate', update);
    }
  };

  return directiveDefinitionObject;
});

quayApp.directive('robotsManager', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/robots-manager.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'organization': '=organization',
      'user': '=user'
    },
    controller: function($scope, $element, Restangular) {
      $scope.ROBOT_PATTERN = ROBOT_PATTERN;
      $scope.robots = null;
      $scope.loading = false;
      $scope.shownRobot = null;
      $scope.showRobotCounter = 0;

      $scope.showRobot = function(info) {
        $scope.shownRobot = info;
        $scope.showRobotCounter++;
      };

      $scope.getShortenedName = function(name) {
        var plus = name.indexOf('+');
        return name.substr(plus + 1);
      };

      $scope.getPrefix = function(name) {
        var plus = name.indexOf('+');
        return name.substr(0, plus);
      };

      $scope.createRobot = function(name) {
        if (!name) { return; }

        createRobotAccount(Restangular, !!$scope.organization, $scope.organization ? $scope.organization.name : '', name,
          function(created) {
            $scope.robots.push(created);
          });
      };

      $scope.deleteRobot = function(info) {
        var shortName = $scope.getShortenedName(info.name);
        var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots', shortName) :
              getRestUrl('user/robots', shortName);

        var deleteRobot = Restangular.one(url);
        deleteRobot.customDELETE().then(function(resp) {
          for (var i = 0; i < $scope.robots.length; ++i) {
            if ($scope.robots[i].name == info.name) {
              $scope.robots.splice(i, 1);
              return;
            }
          }
        }, function() {
           bootbox.dialog({
             "message": 'The selected robot account could not be deleted',
             "title": "Cannot delete robot account",
             "buttons": {
               "close": {
                 "label": "Close",
                 "className": "btn-primary"
               }
             }
           });
        });
      };

      var update = function() {
        if (!$scope.user && !$scope.organization) { return; }
        if ($scope.loading) { return; }

        $scope.loading = true;
        var url = $scope.organization ? getRestUrl('organization', $scope.organization.name, 'robots') : 'user/robots';
        var getRobots = Restangular.one(url);
        getRobots.customGET($scope.obj).then(function(resp) {
          $scope.robots = resp.robots;
          $scope.loading = false;
        });
      };

      $scope.$watch('organization', update);
      $scope.$watch('user', update);
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('popupInputButton', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/popup-input-button.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {
      'placeholder': '=placeholder',
      'pattern': '=pattern',
      'submitted': '&submitted'
    },
    controller: function($scope, $element) {
      $scope.popupShown = function() {
        setTimeout(function() {
          var box = $('#input-box');
          box[0].value = '';
          box.focus();
        }, 10);
      };

      $scope.getRegexp = function(pattern) {
        if (!pattern) {
          pattern = '.*';
        }
        return new RegExp(pattern);
      };

      $scope.inputSubmit = function() {
        var box = $('#input-box');
        if (box.hasClass('ng-invalid')) { return; }

        var entered = box[0].value;
        if (!entered) {
          return;
        }

        if ($scope.submitted) {
          $scope.submitted({'value': entered});
        }
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('resourceView', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/resource-view.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {
      'resource': '=resource',
      'errorMessage': '=errorMessage'
    },
    controller: function($scope, $element) {
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('quaySpinner', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/spinner.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {},
    controller: function($scope, $element) {
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('organizationHeader', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/organization-header.html',
    replace: false,
    transclude: true,
    restrict: 'C',
    scope: {
      'organization': '=organization',
      'teamName': '=teamName',
      'clickable': '=clickable'
    },
    controller: function($scope, $element) {
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('markdownInput', function () {
  var counter = 0;

  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/markdown-input.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'content': '=content',
      'canWrite': '=canWrite',
      'contentChanged': '=contentChanged',
      'fieldTitle': '=fieldTitle'
    },
    controller: function($scope, $element) {
      var elm = $element[0];

      $scope.id = (counter++);

      $scope.editContent = function() {
        if (!$scope.canWrite) { return; }

        if (!$scope.markdownDescriptionEditor) {
          var converter = Markdown.getSanitizingConverter();
          var editor = new Markdown.Editor(converter, '-description-' + $scope.id);
          editor.run();
          $scope.markdownDescriptionEditor = editor;
        }

        $('#wmd-input-description-' + $scope.id)[0].value = $scope.content;
        $(elm).find('.modal').modal({});
      };

      $scope.saveContent = function() {
        $scope.content = $('#wmd-input-description-' + $scope.id)[0].value;
        $(elm).find('.modal').modal('hide');

        if ($scope.contentChanged) {
          $scope.contentChanged($scope.content);
        }
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('repoSearch', function () {
  var number = 0;
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/repo-search.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
    },
    controller: function($scope, $element, $location, UserService, Restangular) {
      var searchToken = 0;
      $scope.$watch( function () { return UserService.currentUser(); }, function (currentUser) {
        ++searchToken;
      }, true);
        
      var element = $($element[0].childNodes[0]);
      element.typeahead({
        name: 'repositories',
        remote: {
          url: '/api/find/repository?query=%QUERY',
          replace: function (url, uriEncodedQuery) {
            url = url.replace('%QUERY', uriEncodedQuery);              
            url += '&cb=' + searchToken;
            return url;
          },
          filter: function(data) {
            var datums = [];
            for (var i = 0; i < data.repositories.length; ++i) {
              var repo = data.repositories[i];
              datums.push({
                'value': repo.name,
                'tokens': [repo.name, repo.namespace],
                'repo': repo
              });
            }
            return datums;
          }
        },
        template: function (datum) {
          template = '<div class="repo-mini-listing">';
          template += '<i class="fa fa-hdd fa-lg"></i>'
          template += '<span class="name">' + datum.repo.namespace +'/' + datum.repo.name + '</span>'
          if (datum.repo.description) {
            template += '<span class="description">' + getFirstTextLine(datum.repo.description) + '</span>'
          }

          template += '</div>'
          return template;
        }
      });

      element.on('typeahead:selected', function (e, datum) {
        element.typeahead('setQuery', '');
        document.location = '/repository/' + datum.repo.namespace + '/' + datum.repo.name;
      });
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('headerBar', function () {
  var number = 0;
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/header-bar.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
    },
    controller: function($scope, $element, $location, UserService, PlanService, Restangular) {
      $scope.overPlan = false;

      var checkOverPlan = function() {
        if ($scope.user.anonymous) {
          $scope.overPlan = false;
          return;
        }

        var checkPrivate = Restangular.one('user/private');
        checkPrivate.customGET().then(function(resp) {
          $scope.overPlan = resp.privateCount > resp.reposAllowed;
        });
      };
      
      // Monitor any user changes and place the current user into the scope.
      UserService.updateUserIn($scope, checkOverPlan);
     
      // Monitor any plan changes.
      PlanService.registerListener(this, checkOverPlan);
        
      $scope.signout = function() {
        var signoutPost = Restangular.one('signout');
        signoutPost.customPOST().then(function() {
            UserService.load();
            $location.path('/');
        });
      };
        
      $scope.appLinkTarget = function() {
        if ($("div[ng-view]").length === 0) {
            return "_self";
        }
        return "";
      };     
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('entitySearch', function () {
  var number = 0;
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/entity-search.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'namespace': '=namespace',
      'inputTitle': '=inputTitle',
      'entitySelected': '=entitySelected',
      'includeTeams': '=includeTeams',
      'isOrganization': '=isOrganization'
    },
    controller: function($scope, $element, Restangular, UserService) {
      $scope.lazyLoading = true;
      $scope.isAdmin = false;

      $scope.lazyLoad = function() {
        if (!$scope.namespace || !$scope.lazyLoading) { return; }

        $scope.isAdmin = UserService.isNamespaceAdmin($scope.namespace);

        if ($scope.isOrganization && $scope.includeTeams) {
          var url = getRestUrl('organization', $scope.namespace);
          var getOrganization = Restangular.one(url);
          getOrganization.customGET().then(function(resp) {
            $scope.teams = resp.teams;
          });
        }

        var url = $scope.isOrganization ? getRestUrl('organization', $scope.namespace, 'robots') : 'user/robots';
        var getRobots = Restangular.one(url);
        getRobots.customGET().then(function(resp) {
          $scope.robots = resp.robots;
          $scope.lazyLoading = false;
        }, function() {
          $scope.lazyLoading = false;
        });
      };

      $scope.createTeam = function() {
        if (!$scope.isAdmin) { return; }

        bootbox.prompt('Enter the name of the new team', function(teamname) {
          if (!teamname) { return; }
            
          var regex = new RegExp(TEAM_PATTERN);
          if (!regex.test(teamname)) {
            bootbox.alert('Invalid team name');
            return;
          }

          createOrganizationTeam(Restangular, $scope.namespace, teamname, function(created) {
            $scope.setEntity(created.name, 'team', false);
            $scope.teams[teamname] = created;
          });          
        });
      };

      $scope.createRobot = function() {
        if (!$scope.isAdmin) { return; }

        bootbox.prompt('Enter the name of the new robot account', function(robotname) {
          if (!robotname) { return; }

          var regex = new RegExp(ROBOT_PATTERN);
          if (!regex.test(robotname)) {
            bootbox.alert('Invalid robot account name');
            return;
          }

          createRobotAccount(Restangular, $scope.isOrganization, $scope.namespace, robotname, function(created) {
            $scope.setEntity(created.name, 'user', true);
            $scope.robots.push(created);
          });          
        });
      };

      $scope.setEntity = function(name, kind, is_robot) {
        var entity = {
          'name': name,
          'kind': kind,
          'is_robot': is_robot
        };

        if ($scope.is_organization) {
          entity['is_org_member'] = true;
        }

        $scope.entitySelected(entity);
      };

      if (!$scope.entitySelected) { return; }

      number++;

      var input = $element[0].firstChild.firstChild;
      $(input).typeahead({
        name: 'entities' + number,
        remote: {
          url: '/api/entities/%QUERY',
          replace: function (url, uriEncodedQuery) {
            var namespace = $scope.namespace || '';
            url = url.replace('%QUERY', uriEncodedQuery);
            url += '?namespace=' + encodeURIComponent(namespace);
            if ($scope.includeTeams) {
              url += '&includeTeams=true'
            }
            return url;
          },
          filter: function(data) {
            var datums = [];
            for (var i = 0; i < data.results.length; ++i) {
              var entity = data.results[i];
              datums.push({
                'value': entity.name,
                'tokens': [entity.name],
                'entity': entity
              });
            }
            return datums;
          }
        },
        template: function (datum) {
          template = '<div class="entity-mini-listing">';
          if (datum.entity.kind == 'user' && !datum.entity.is_robot) {
            template += '<i class="fa fa-user fa-lg"></i>';
          } else if (datum.entity.kind == 'user' && datum.entity.is_robot) {
            template += '<i class="fa fa-wrench fa-lg"></i>';            
          } else if (datum.entity.kind == 'team') {
            template += '<i class="fa fa-group fa-lg"></i>';
          }
          template += '<span class="name">' + datum.value + '</span>';

          if (datum.entity.is_org_member === false && datum.entity.kind == 'user') {
            template += '<i class="fa fa-exclamation-triangle" title="User is outside the organization"></i>';
          }

          template += '</div>';
          return template;
        },
      });

      $(input).on('typeahead:selected', function(e, datum) {
        $(input).typeahead('setQuery', '');
        $scope.$apply(function() {
          $scope.entitySelected(datum.entity);
        });
      });

      $scope.$watch('inputTitle', function(title) {
        input.setAttribute('placeholder', title);
      });
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('roleGroup', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/role-group.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'roles': '=roles',
      'currentRole': '=currentRole',
      'roleChanged': '&roleChanged'
    },
    controller: function($scope, $element) {
      $scope.setRole = function(role) {
        if ($scope.currentRole == role) { return; }
        if ($scope.roleChanged) {          
          $scope.roleChanged({'role': role});
        } else {
          $scope.currentRole = role;
        }
      };
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('billingOptions', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/billing-options.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'user': '=user',
      'organization': '=organization'
    },
    controller: function($scope, $element, PlanService, Restangular) {
      $scope.invoice_email = false;
      $scope.currentCard = null;

      // Listen to plan changes.
      PlanService.registerListener(this, function(plan) {
        if (plan && plan.price > 0) {
          update();
        }
      });

      $scope.$on('$destroy', function() {
        PlanService.unregisterListener(this);
      });

      $scope.changeCard = function() {
        var previousCard = $scope.currentCard;
        $scope.changingCard = true;
        var callbacks = {
          'opened': function() { $scope.changingCard = true; },
          'closed': function() { $scope.changingCard = false; },
          'started': function() { $scope.currentCard = null; },
          'success': function(resp) {
             $scope.currentCard = resp.card;
             $scope.changingCard = false;
          },
          'failure': function(resp) {
             $scope.changingCard = false;
             $scope.currentCard = previousCard;

             if (!PlanService.isCardError(resp)) {
               $('#cannotchangecardModal').modal({});
             }
          }
        };

        PlanService.changeCreditCard($scope, $scope.organization ? $scope.organization.name : null, callbacks);
      };

      $scope.getCreditImage = function(creditInfo) {
        if (!creditInfo || !creditInfo.type) { return 'credit.png'; }

        var kind = creditInfo.type.toLowerCase() || 'credit';
        var supported = {
          'american express': 'amex',
          'credit': 'credit',
          'diners club': 'diners',
          'discover': 'discover',
          'jcb': 'jcb',
          'mastercard': 'mastercard',       
          'visa': 'visa'
        };
        
        kind = supported[kind] || 'credit';
        return kind + '.png';
      };

      var update = function() {
        if (!$scope.user && !$scope.organization) { return; }
        $scope.obj = $scope.user ? $scope.user : $scope.organization;
        $scope.invoice_email = $scope.obj.invoice_email;

        // Load the credit card information.
        PlanService.getCardInfo($scope.organization ? $scope.organization.name : null, function(card) {
          $scope.currentCard = card;
        });
      };

      var save = function() {
        $scope.working = true;
        var url = $scope.organization ? getRestUrl('organization', $scope.organization.name) : 'user/';
        var conductSave = Restangular.one(url);
        conductSave.customPUT($scope.obj).then(function(resp) {
          $scope.working = false;
        });
      };

      var checkSave = function() {
        if (!$scope.obj) { return; }
        if ($scope.obj.invoice_email != $scope.invoice_email) {
          $scope.obj.invoice_email = $scope.invoice_email;
          save();
        }
      };
      
      $scope.$watch('invoice_email', checkSave);
      $scope.$watch('organization', update);
      $scope.$watch('user', update);
    }
  };
  return directiveDefinitionObject;
});

      
quayApp.directive('planManager', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/plan-manager.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'user': '=user',
      'organization': '=organization',
      'readyForPlan': '&readyForPlan',
      'planChanged': '&planChanged'
    },
    controller: function($scope, $element, PlanService, Restangular) {
      var hasSubscription = false;
        
      $scope.isPlanVisible = function(plan, subscribedPlan) {
        if (plan['deprecated']) {
          return plan == subscribedPlan;
        }

        if ($scope.organization && !PlanService.isOrgCompatible(plan)) {
          return false;
        }

        return true;
      };

      $scope.changeSubscription = function(planId) {
        if ($scope.planChanging) { return; }

        var callbacks = {
          'opening': function() { $scope.planChanging = true; },
          'started': function() { $scope.planChanging = true; },
          'opened': function() { $scope.planChanging = true; },
          'closed': function() { $scope.planChanging = false; },
          'success': subscribedToPlan,
          'failure': function(resp) {            
             $scope.planChanging = false;
          }
        };

        PlanService.changePlan($scope, $scope.organization, planId, callbacks);
      };

      $scope.cancelSubscription = function() {
        $scope.changeSubscription(PlanService.getFreePlan());
      };

      var subscribedToPlan = function(sub) {
        $scope.subscription = sub;

        if (sub.plan != PlanService.getFreePlan()) {
          hasSubscription = true;
        }

        PlanService.getPlanIncludingDeprecated(sub.plan, function(subscribedPlan) {
          $scope.subscribedPlan = subscribedPlan;
          $scope.planUsagePercent = sub.usedPrivateRepos * 100 / $scope.subscribedPlan.privateRepos;
          
          if ($scope.planChanged) {
            $scope.planChanged({ 'plan': subscribedPlan });
          }

          if (sub.usedPrivateRepos > $scope.subscribedPlan.privateRepos) {
            $scope.limit = 'over';
          } else if (sub.usedPrivateRepos == $scope.subscribedPlan.privateRepos) {
            $scope.limit = 'at';
          } else if (sub.usedPrivateRepos >= $scope.subscribedPlan.privateRepos * 0.7) {
            $scope.limit = 'near';
          } else {
            $scope.limit = 'none';
          }

          if (!$scope.chart) {
            $scope.chart = new RepositoryUsageChart();
            $scope.chart.draw('repository-usage-chart');
          }

          $scope.chart.update(sub.usedPrivateRepos || 0, $scope.subscribedPlan.privateRepos || 0);

          $scope.planChanging = false;
          $scope.planLoading = false;
        });
      };

      var update = function() {
        $scope.planLoading = true;
        if (!$scope.plans) { return; }

        PlanService.getSubscription($scope.organization, subscribedToPlan, function() {
          // User/Organization has no subscription.
          subscribedToPlan({ 'plan': PlanService.getFreePlan() });
        });
      };

      var loadPlans = function() {
        if ($scope.plans || $scope.loadingPlans) { return; }
        if (!$scope.user && !$scope.organization) { return; }

        $scope.loadingPlans = true;
        PlanService.verifyLoaded(function(plans) {
          $scope.plans = plans;
          update();
            
          if ($scope.readyForPlan) {
            var planRequested = $scope.readyForPlan();
            if (planRequested && planRequested != PlanService.getFreePlan()) {
              $scope.changeSubscription(planRequested);
            }
          }
        });
      };

      // Start the initial download.
      $scope.planLoading = true;
      loadPlans();

      $scope.$watch('organization', loadPlans);
      $scope.$watch('user', loadPlans);
    }
  };
  return directiveDefinitionObject;
});



quayApp.directive('namespaceSelector', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/namespace-selector.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'user': '=user',
      'namespace': '=namespace',
      'requireCreate': '=requireCreate'
    },
    controller: function($scope, $element, $routeParams, CookieService) {
      $scope.namespaces = {};

      $scope.initialize = function(user) {
        var namespaces = {};
        namespaces[user.username] = user;
        if (user.organizations) {
          for (var i = 0; i < user.organizations.length; ++i) {
            namespaces[user.organizations[i].name] = user.organizations[i];
          }
        }

        var initialNamespace = $routeParams['namespace'] || CookieService.get('quay.namespace') || $scope.user.username;
        $scope.namespaces = namespaces;
        $scope.setNamespace($scope.namespaces[initialNamespace]);
      };

      $scope.setNamespace = function(namespaceObj) {
        if (!namespaceObj) {
          namespaceObj = $scope.namespaces[$scope.user.username];
        }

        if ($scope.requireCreate && !namespaceObj.can_create_repo) {
          namespaceObj = $scope.namespaces[$scope.user.username];
        }

        var newNamespace = namespaceObj.name || namespaceObj.username;       
        $scope.namespaceObj = namespaceObj;
        $scope.namespace = newNamespace;

        if (newNamespace) {
          CookieService.putPermanent('quay.namespace', newNamespace);
        }
      };

      $scope.$watch('user', function(user) {
        $scope.user = user;
        $scope.initialize(user);
      });
    }
  };
  return directiveDefinitionObject;
});


quayApp.directive('buildStatus', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/build-status.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'build': '=build'
    },
    controller: function($scope, $element) {
        $scope.getBuildProgress = function(buildInfo) {
            switch (buildInfo.status) {
              case 'building':
                return (buildInfo.current_command / buildInfo.total_commands) * 100;
                break;
                
              case 'pushing':
                var imagePercentDecimal = (buildInfo.image_completion_percent / 100);
                return ((buildInfo.current_image + imagePercentDecimal) / buildInfo.total_images) * 100;
                break;

              case 'complete':
                return 100;
                break;

              case 'initializing':
              case 'starting':
              case 'waiting':
                return 0;
                break;
            }
            
            return -1;
        };

        $scope.getBuildMessage = function(buildInfo) {
            switch (buildInfo.status) {
            case 'initializing':
                return 'Starting Dockerfile build';
                break;

            case 'starting':
            case 'waiting':
            case 'building':
                return 'Building image from Dockerfile';
                break;

            case 'pushing':
                return 'Pushing image built from Dockerfile';
                break;

            case 'complete':
                return 'Dockerfile build completed and pushed';
                break;
                
            case 'error':
                return 'Dockerfile build failed: ' + buildInfo.message;
                break;
            }
        };
    }
  };
  return directiveDefinitionObject;
});

// Note: ngBlur is not yet in Angular stable, so we add it manaully here.
quayApp.directive('ngBlur', function() {
  return function( scope, elem, attrs ) {
    elem.bind('blur', function() {
      scope.$apply(attrs.ngBlur);
    });
  };
});

quayApp.run(['$location', '$rootScope', 'Restangular', 'UserService', 'PlanService', '$http', '$timeout',
    function($location, $rootScope, Restangular, UserService, PlanService, $http, $timeout) {
  // Handle session expiration.
  Restangular.setErrorInterceptor(function(response) {
    if (response.status == 401) {
      $('#sessionexpiredModal').modal({});
      return false;
    }

    return true;
  });

  // Check if we need to redirect based on a previously chosen plan.
  PlanService.handleNotedPlan();

  var changeTab = function(activeTab, opt_timeout) {
    var checkCount = 0;

    $timeout(function() {
      if (checkCount > 5) { return; }
      checkCount++;

      $('a[data-toggle="tab"]').each(function(index) {
        var tabName = this.getAttribute('data-target').substr(1);
        if (tabName != activeTab) {
          return;
        }

        if (this.clientWidth == 0) {
          changeTab(activeTab, 500);
          return;
        }

        this.click();
      });
    }, opt_timeout);
  };

  var resetDefaultTab = function() {
    $timeout(function() {
      $('a[data-toggle="tab"]').each(function(index) {
        if (index == 0) {
          this.click();
        }
      });
    });
  };

  $rootScope.$on('$routeUpdate', function(){
    if ($location.search()['tab']) {
      changeTab($location.search()['tab']);
    } else {
      resetDefaultTab();
    }
  });

  $rootScope.$on('$routeChangeSuccess', function (event, current, previous) {
    if (current.$$route.title) {
      $rootScope.title = current.$$route.title;
    }

    if (current.$$route.description) {
      $rootScope.description = current.$$route.description;
    } else {
      $rootScope.description = 'Hosted private docker repositories. Includes full user management and history. Free for public repositories.';
    }

    $rootScope.fixFooter = !!current.$$route.fixFooter;
    $rootScope.current = current.$$route;
  });

  $rootScope.$on('$viewContentLoaded', function(event, current) {
    var activeTab = $location.search()['tab'];

    // Setup deep linking of tabs. This will change the search field of the URL whenever a tab
    // is changed in the UI.
    $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
      var tabName = e.target.getAttribute('data-target').substr(1);
      $rootScope.$apply(function() {
        var isDefaultTab = $('a[data-toggle="tab"]')[0] == e.target;
        var data = isDefaultTab ? {} : {'tab':  tabName};
        $location.search(data);
      });
      
      e.preventDefault();        
    });

    if (activeTab) {
      changeTab(activeTab);
    }
  });

  var initallyChecked = false;
  window.__isLoading = function() {
    if (!initallyChecked) {
      initallyChecked = true;
      return true;
    }
    return $http.pendingRequests.length > 0;
  };
}]);