
/**
 * An element which displays a box to search for an entity (org, user, robot, team). This control
 * allows for filtering of the entities found and whether to allow selection by e-mail.
 */
angular.module('quay').directive('entitySearch', function () {
  var number = 0;
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/entity-search.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    require: '?ngModel',
    link: function(scope, element, attr, ctrl) {
      scope.ngModel = ctrl;
    },
    scope: {
      'namespace': '=namespace',
      'placeholder': '=placeholder',

      // Default: ['user', 'team', 'robot']
      'allowedEntities': '=allowedEntities',

      'currentEntity': '=currentEntity',

      'entitySelected': '&entitySelected',
      'emailSelected': '&emailSelected',

      // When set to true, the contents of the control will be cleared as soon
      // as an entity is selected.
      'autoClear': '=autoClear',

      // Set this property to immediately clear the contents of the control.
      'clearValue': '=clearValue',

      // Whether e-mail addresses are allowed.
      'allowEmails': '=allowEmails',
      'emailMessage': '@emailMessage',

      // True if the menu should pull right.
      'pullRight': '@pullRight'
    },
    controller: function($rootScope, $scope, $element, Restangular, UserService, ApiService, UtilService, Config, CreateService) {
      $scope.lazyLoading = true;

      $scope.teams = null;
      $scope.robots = null;

      $scope.isAdmin = false;
      $scope.isOrganization = false;

      $scope.includeTeams = true;
      $scope.includeRobots = true;
      $scope.includeOrgs = false;

      $scope.currentEntityInternal = $scope.currentEntity;

      $scope.Config = Config;

      var isSupported = function(kind, opt_array) {
        return $.inArray(kind, opt_array || $scope.allowedEntities || ['user', 'team', 'robot']) >= 0;
      };

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

        // Reset the cached teams and robots.
        $scope.teams = null;
        $scope.robots = null;

        // Load the organization's teams (if applicable).
        if ($scope.isOrganization && isSupported('team')) {
          // Note: We load the org here again so that we always have the fully up-to-date
          // teams list.
          ApiService.getOrganization(null, {'orgname': $scope.namespace}).then(function(resp) {
            $scope.teams = resp.teams;
          });
        }

        // Load the user/organization's robots (if applicable).
        if ($scope.isAdmin && isSupported('robot')) {
          ApiService.getRobots($scope.isOrganization ? $scope.namespace : null).then(function(resp) {
            $scope.robots = resp.robots;
            $scope.lazyLoading = false;
          }, function() {
            $scope.lazyLoading = false;
          });
        } else {
          $scope.lazyLoading = false;
        }
      };

      $scope.createTeam = function() {
        CreateService.askCreateTeam($scope.namespace, function(created) {
          $scope.setEntity(created.name, 'team', false, created.avatar);
          $scope.teams[created.name] = created;
        });
      };

      $scope.createRobot = function() {
        CreateService.askCreateRobot($scope.namespace, function(created) {
          $scope.setEntity(created.name, 'user', true, created.avatar);
          $scope.robots.push(created);
        });
      };

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

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

        $scope.setEntityInternal(entity, false);
      };

      $scope.clearEntityInternal = function() {
        $scope.currentEntityInternal = null;
        $scope.currentEntity = null;
        $scope.entitySelected({'entity': null});
        if ($scope.ngModel) {
          $scope.ngModel.$setValidity('entity', false);
        }
      };

      $scope.setEntityInternal = function(entity, updateTypeahead) {
        if (updateTypeahead) {
          $(input).typeahead('val', $scope.autoClear ? '' : entity.name);
        } else {
          $(input).val($scope.autoClear ? '' : entity.name);
        }

        if (!$scope.autoClear) {
          $scope.currentEntityInternal = entity;
          $scope.currentEntity = entity;
        }

        $scope.entitySelected({'entity': entity});
        if ($scope.ngModel) {
          $scope.ngModel.$setValidity('entity', !!entity);
        }
      };

      // Setup the typeahead.
      var input = $element[0].firstChild.firstChild;

      (function() {
        // Create the bloodhound search query system.
        $rootScope.__entity_search_counter = (($rootScope.__entity_search_counter || 0) + 1);
        var entitySearchB = new Bloodhound({
          name: 'entities' + $rootScope.__entity_search_counter,
          remote: {
            url: '/api/v1/entities/%QUERY',
            replace: function (url, uriEncodedQuery) {
              var namespace = $scope.namespace || '';
              url = url.replace('%QUERY', uriEncodedQuery);
              url += '?namespace=' + encodeURIComponent(namespace);
              if ($scope.isOrganization && isSupported('team')) {
                url += '&includeTeams=true'
              }
              if (isSupported('org')) {
                url += '&includeOrgs=true'
              }
              return url;
            },
            filter: function(data) {
              var datums = [];
              for (var i = 0; i < data.results.length; ++i) {
                var entity = data.results[i];

                var found = 'user';
                if (entity.kind == 'user') {
                  found = entity.is_robot ? 'robot' : 'user';
                } else if (entity.kind == 'team') {
                  found = 'team';
                } else if (entity.kind == 'org') {
                  found = 'org';
                }

                if (!isSupported(found)) {
                  continue;
                }

                datums.push({
                  'value': entity.name,
                  'tokens': [entity.name],
                  'entity': entity
                });
              }
              return datums;
            }
          },
          datumTokenizer: function(d) {
            return Bloodhound.tokenizers.whitespace(d.val);
          },
          queryTokenizer: Bloodhound.tokenizers.whitespace
        });
        entitySearchB.initialize();

        // Setup the typeahead.
        $(input).typeahead({
          'highlight': true
        }, {
          source: entitySearchB.ttAdapter(),
          templates: {
            'empty': function(info) {
              // Only display the empty dialog if the server load has finished.
              if (info.resultKind == 'remote') {
                var val = $(input).val();
                if (!val) {
                  return null;
                }

                if (UtilService.isEmailAddress(val)) {
                  if ($scope.allowEmails) {
                    return '<div class="tt-message">' + $scope.emailMessage + '</div>';
                  } else {
                    return '<div class="tt-empty">A ' + Config.REGISTRY_TITLE_SHORT + ' username (not an e-mail address) must be specified</div>';
                  }
                }

                var classes = [];

                if (isSupported('user')) { classes.push('users'); }
                if (isSupported('org')) { classes.push('organizations'); }
                if ($scope.isAdmin && isSupported('robot')) { classes.push('robot accounts'); }
                if ($scope.isOrganization && isSupported('team')) { classes.push('teams'); }

                if (classes.length > 1) {
                  classes[classes.length - 1] = 'or ' +  classes[classes.length - 1];
                } else if (classes.length == 0) {
                return '<div class="tt-empty">No matching entities found</div>';
                }

                var class_string = '';
                for (var i = 0; i < classes.length; ++i) {
                  if (i > 0) {
                    if (i == classes.length - 1) {
                      class_string += ' or ';
                    } else {
                      class_string += ', ';
                    }
                  }

                  class_string += classes[i];
                }

                return '<div class="tt-empty">No matching ' + Config.REGISTRY_TITLE_SHORT + ' ' + class_string + ' found</div>';
              }

              return null;
            },
            'suggestion': 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 ci-robot fa-lg"></i>';
              } else if (datum.entity.kind == 'team') {
                template += '<i class="fa fa-group fa-lg"></i>';
              } else if (datum.entity.kind == 'org') {
                template += '<i class="fa">' + AvatarService.getAvatar(datum.entity.avatar, 16) + '</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('keypress', function(e) {
          var val = $(input).val();
          var code = e.keyCode || e.which;
          if (code == 13 && $scope.allowEmails && UtilService.isEmailAddress(val)) {
            $scope.$apply(function() {
              $scope.emailSelected({'email': val});
            });
          }
        });

        $(input).on('input', function(e) {
          $scope.$apply(function() {
            $scope.clearEntityInternal();
          });
        });

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

      $scope.$watch('clearValue', function() {
        if (!input) { return; }

        $(input).typeahead('val', '');
        $scope.clearEntityInternal();
      });

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

      $scope.$watch('allowedEntities', function(allowed) {
        if (!allowed) { return; }
        $scope.includeTeams = isSupported('team', allowed);
        $scope.includeRobots = isSupported('robot', allowed);
      });

      $scope.$watch('namespace', function(namespace) {
        if (!namespace) { return; }
        $scope.isAdmin = UserService.isNamespaceAdmin(namespace);
        $scope.isOrganization = !!UserService.getOrganization(namespace);
      });

      $scope.$watch('currentEntity', function(entity) {
        if ($scope.currentEntityInternal != entity) {
          if (entity) {
            $scope.setEntityInternal(entity, false);
          } else {
            $scope.clearEntityInternal();
          }
        }
      });
    }
  };
  return directiveDefinitionObject;
});
