(function() {
  /**
   * Repository view page.
   */
  angular.module('quayPages').config(['pages', function(pages) {
    pages.create('repo-view', 'repo-view.html', RepoViewCtrl, {
      'newLayout': true,
      'title': '{{ namespace }}/{{ name }}',
      'description': 'Repository {{ namespace }}/{{ name }}'
    }, ['layout'])

    pages.create('repo-view', 'old-repo-view.html', OldRepoViewCtrl, {
    }, ['old-layout']);
  }]);

  function RepoViewCtrl($scope, $routeParams, $location, $timeout, ApiService, UserService, AngularPollChannel) {
    $scope.namespace = $routeParams.namespace;
    $scope.name = $routeParams.name;

    $scope.logsShown = 0;
    $scope.viewScope = {
      'selectedTags': [],
      'repository': null,
      'images': null,
      'imagesResource': null,
      'builds': null,
      'changesVisible': false
    };

    var buildPollChannel = null;

    // Make sure we track the current user.
    UserService.updateUserIn($scope);

    // Watch the selected tags and update the URL accordingly.
    $scope.$watch('viewScope.selectedTags', function(selectedTags) {
      if (!selectedTags || !$scope.viewScope.repository) { return; }

      var tags = filterTags(selectedTags);
      if (!tags.length) {
        $location.search('tag', null);
        return;
      }

      $location.search('tag', tags.join(','));
    }, true);

    // Watch the repository to filter any tags removed.
    $scope.$watch('viewScope.repository', function(repository) {
      if (!repository) { return; }
      $scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
    });

    var filterTags = function(tags) {
      return (tags || []).filter(function(tag) {
        return !!$scope.viewScope.repository.tags[tag];
      });
    };

    var loadRepository = function() {
      var params = {
        'repository': $scope.namespace + '/' + $scope.name
      };

      $scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
        $scope.repository = repo;
        $scope.viewScope.repository = repo;

        // Load the remainder of the data async, so we don't block the initial view from
        // showing.
        $timeout(function() {
          $scope.setTags($routeParams.tag);

          // Load the images.
          loadImages();

          // Track builds.
          buildPollChannel = AngularPollChannel.create($scope, loadRepositoryBuilds, 15000 /* 15s */);
          buildPollChannel.start();
        }, 10);
      });
    };

    var loadImages = function() {
      var params = {
        'repository': $scope.namespace + '/' + $scope.name
      };

      $scope.viewScope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
        $scope.viewScope.images = resp.images;
      });
    };

    var loadRepositoryBuilds = function(callback) {
      var params = {
        'repository': $scope.namespace + '/' + $scope.name,
        'limit': 3
      };

      var errorHandler = function() {
        callback(false);
      };

      $scope.repositoryBuildsResource = ApiService.getRepoBuildsAsResource(params, /* background */true).get(function(resp) {
        // Note: We could just set the builds here, but that causes a full digest cycle. Therefore,
        // to be more efficient, we do some work here to determine if anything has changed since
        // the last build load in the common case.
        if ($scope.viewScope.builds && resp.builds.length == $scope.viewScope.builds.length) {
          var hasNewInformation = false;
          for (var i = 0; i < resp.builds.length; ++i) {
            var current = $scope.viewScope.builds[i];
            var updated = resp.builds[i];
            if (current.phase != updated.phase || current.id != updated.id) {
              hasNewInformation = true;
              break;
            }
          }

          if (!hasNewInformation) {
            callback(true);
            return;
          }
        }

        $scope.viewScope.builds = resp.builds;
        callback(true);
      }, errorHandler);
    };

    // Load the repository.
    loadRepository();

    $scope.setTags = function(tagNames) {
      if (!tagNames) {
        $scope.viewScope.selectedTags = [];
        return;
      }

      $scope.viewScope.selectedTags = $.unique(tagNames.split(','));
    };

    $scope.showLogs = function() {
      $scope.logsShown++;
    };

    $scope.handleChangesState = function(value) {
      $scope.viewScope.changesVisible = value;
    };
  }

  function OldRepoViewCtrl($scope, $sanitize, Restangular, ImageMetadataService, ApiService, $routeParams, $rootScope, $location, $timeout, Config, UtilService) {
    $scope.Config = Config;

    var namespace = $routeParams.namespace;
    var name = $routeParams.name;

    $scope.pullCommands = [];
    $scope.currentPullCommand = null;

    $rootScope.title = 'Loading...';

    // Watch for the destruction of the scope.
    $scope.$on('$destroy', function() {
      if ($scope.tree) {
        $scope.tree.dispose();
      }
    });

    // Watch for changes to the repository.
    $scope.$watch('repo', function() {
      $timeout(function() {
        if ($scope.tree) {
          $scope.tree.notifyResized();
        }
      });
    });

    // Watch for changes to the tag parameter.
    $scope.$on('$routeUpdate', function(){
      if ($location.search().tag) {
        $scope.setTag($location.search().tag, false);
      } else if ($location.search().image) {
        $scope.setImage($location.search().image, false);
      } else {
        $scope.setTag($location.search().tag, false);
      }
    });

    // Start scope methods //////////////////////////////////////////

    $scope.buildDialogShowCounter = 0;
    $scope.getFormattedCommand = ImageMetadataService.getFormattedCommand;

    $scope.setCurrentPullCommand = function(pullCommand) {
      $scope.currentPullCommand = pullCommand;
    };

    $scope.updatePullCommand = function() {
      $scope.pullCommands = [];

      if ($scope.currentTag) {
        $scope.pullCommands.push({
          'title': 'docker pull (Tag ' + $scope.currentTag.name + ')',
          'shortTitle': 'Pull Tag',
          'icon': 'fa-tag',
          'command': 'docker pull ' + Config.getDomain() + '/' + namespace + '/' + name + ':' + $scope.currentTag.name
        });
      }

      $scope.pullCommands.push({
        'title': 'docker pull (Full Repository)',
        'shortTitle': 'Pull Repo',
        'icon': 'fa-code-fork',
        'command': 'docker pull ' + Config.getDomain() + '/' + namespace + '/' + name
      });

      if ($scope.currentTag) {
        var squash = 'curl -L -f ' + Config.getHost('ACCOUNTNAME:PASSWORDORTOKEN');
        squash += '/c1/squash/' + namespace + '/' + name + '/' + $scope.currentTag.name;
        squash += ' | docker load';

        $scope.pullCommands.push({
          'title': 'Squashed image (Tag ' + $scope.currentTag.name + ')',
          'shortTitle': 'Squashed',
          'icon': 'fa-file-archive-o',
          'command': squash,
          'experimental': true
        });
      }

      $scope.currentPullCommand = $scope.pullCommands[0];
    };

    $scope.showNewBuildDialog = function() {
      $scope.buildDialogShowCounter++;
    };

    $scope.handleBuildStarted = function(build) {
      getBuildInfo($scope.repo);
      startBuildInfoTimer($scope.repo);
    };

    $scope.showBuild = function(buildInfo) {
      $location.path('/repository/' + namespace + '/' + name + '/build');
      $location.search('current', buildInfo.id);
    };

    $scope.isPushing = function(images) {
      if (!images) { return false; }

      var cached = images.__isPushing;
      if (cached !== undefined) {
        return cached;
      }

      return images.__isPushing = $scope.isPushingInternal(images);
    };

    $scope.isPushingInternal = function(images) {
      if (!images) { return false; }

      for (var i = 0; i < images.length; ++i) {
        if (images[i].uploading) { return true; }
      }

      return false;
    };

    $scope.getTooltipCommand = function(image) {
      var sanitized = ImageMetadataService.getEscapedFormattedCommand(image);
      return '<span class=\'codetooltip\'>' + sanitized + '</span>';
    };

    $scope.updateForDescription = function(content) {
      $scope.repo.description = content;
      $scope.repo.put();
    };

    $scope.parseDate = function(dateString) {
      return Date.parse(dateString);
    };

    $scope.getTimeSince = function(createdTime) {
        return moment($scope.parseDate(createdTime)).fromNow();
    };

    $scope.loadImageChanges = function(image) {
      if (!image) { return; }

      var params = {'repository': namespace + '/' + name, 'image_id': image.id};
      $scope.currentImageChangeResource = ApiService.getImageChangesAsResource(params).get(function(ci) {
        $scope.currentImageChanges = ci;
      });
    };

    $scope.getMoreCount = function(changes) {
      if (!changes) { return 0; }
      var addedDisplayed = Math.min(2, changes.added.length);
      var removedDisplayed = Math.min(2, changes.removed.length);
      var changedDisplayed = Math.min(2, changes.changed.length);

      return (changes.added.length + changes.removed.length + changes.changed.length) -
        addedDisplayed - removedDisplayed - changedDisplayed;
    };

    $scope.showAddTag = function(image) {
      $scope.toTagImage = image;
      $('#addTagModal').modal('show');
      setTimeout(function() {
        $('#tagName').focus();
      }, 500);
    };

    $scope.isOwnedTag = function(image, tagName) {
      if (!image || !tagName) { return false; }
      return image.tags.indexOf(tagName) >= 0;
    };

    $scope.isAnotherImageTag = function(image, tagName) {
      if (!image || !tagName) { return false; }
      return image.tags.indexOf(tagName) < 0 && $scope.repo.tags[tagName];
    };

    $scope.askDeleteTag = function(tagName) {
      if (!$scope.repo.can_admin) { return; }

      $scope.tagToDelete = tagName;
      $('#confirmdeleteTagModal').modal('show');
    };

    $scope.findImageForTag = function(tag) {
      return tag && $scope.imageByDockerId && $scope.imageByDockerId[tag.image_id];
    };

    $scope.createOrMoveTag = function(image, tagName, opt_invalid) {
      if (opt_invalid) { return; }

      $scope.creatingTag = true;

      var params = {
        'repository': $scope.repo.namespace + '/' + $scope.repo.name,
        'tag': tagName
      };

      var data = {
        'image': image.id
      };

      var errorHandler = ApiService.errorDisplay('Cannot create or move tag', function(resp) {
        $('#addTagModal').modal('hide');
      });

      ApiService.changeTagImage(data, params).then(function(resp) {
        $scope.creatingTag = false;
        loadViewInfo();
        $('#addTagModal').modal('hide');
      }, errorHandler);
    };

    $scope.deleteTag = function(tagName) {
      if (!$scope.repo.can_admin) { return; }

      var params = {
        'repository': namespace + '/' + name,
        'tag': tagName
      };

      var errorHandler = ApiService.errorDisplay('Cannot delete tag', function() {
        $('#confirmdeleteTagModal').modal('hide');
        $scope.deletingTag = false;
      });

      $scope.deletingTag = true;

      ApiService.deleteFullTag(null, params).then(function() {
        loadViewInfo();
        $('#confirmdeleteTagModal').modal('hide');
        $scope.deletingTag = false;
      }, errorHandler);
    };

    $scope.getImagesForTagBySize = function(tag) {
      var images = [];
      forAllTagImages(tag, function(image) {
          images.push(image);
      });

      images.sort(function(a, b) {
        return b.size - a.size;
      });

      return images;
    };

    $scope.getTotalSize = function(tag) {
      var size = 0;
      forAllTagImages(tag, function(image) {
        size += image.size;
      });
      return size;
    };

    $scope.setImage = function(imageId, opt_updateURL) {
      if (!$scope.images) { return; }

      var image = null;
      for (var i = 0; i < $scope.images.length; ++i) {
        var currentImage = $scope.images[i];
        if (currentImage.id == imageId || currentImage.id.substr(0, 12) == imageId) {
          image = currentImage;
          break;
        }
      }

      if (!image) { return; }

      $scope.currentTag = null;
      $scope.currentImage = image;
      $scope.loadImageChanges(image);
      if ($scope.tree) {
        $scope.tree.setImage(image.id);
      }

      if (opt_updateURL) {
        $location.search('tag', null);
        $location.search('image', imageId.substr(0, 12));
      }

      $scope.updatePullCommand();
    };

    $scope.setTag = function(tagName, opt_updateURL) {
      var repo = $scope.repo;
      if (!repo) { return; }

      var proposedTag = repo.tags[tagName];
      if (!proposedTag) {
        // We must find a good default.
        for (tagName in repo.tags) {
          if (!proposedTag || tagName == 'latest') {
            proposedTag = repo.tags[tagName];
          }
        }
      }

      if (proposedTag) {
        $scope.currentTag = proposedTag;
        $scope.currentImage = null;

        if ($scope.tree) {
          $scope.tree.setTag(proposedTag.name);
        }

        if (opt_updateURL) {
          $location.search('image', null);
          $location.search('tag', proposedTag.name);
        }
      }

      if ($scope.currentTag && !repo.tags[$scope.currentTag.name]) {
        $scope.currentTag = null;
        $scope.currentImage = null;
      }

      $scope.updatePullCommand();
    };

    $scope.getTagCount = function(repo) {
      if (!repo) { return 0; }
      var count = 0;
      for (var tag in repo.tags) {
        ++count;
      }
      return count;
    };

    $scope.hideTagMenu = function(tagName, clientX, clientY) {
      $scope.currentMenuTag = null;

      var tagMenu = $("#tagContextMenu");
      tagMenu.hide();
    };

    $scope.showTagMenu = function(tagName, clientX, clientY) {
      if (!$scope.repo.can_admin) { return; }

      $scope.currentMenuTag = tagName;

      var tagMenu = $("#tagContextMenu");
      tagMenu.css({
        display: "block",
        left: clientX,
        top: clientY
      });

      tagMenu.on("blur", function() {
        setTimeout(function() {
          tagMenu.hide();
        }, 100); // Needed to allow clicking on menu items.
      });

      tagMenu.on("click", "a", function() {
        setTimeout(function() {
          tagMenu.hide();
        }, 100); // Needed to allow clicking on menu items.
      });

      tagMenu[0].focus();
    };

    var getDefaultTag = function() {
      if ($scope.repo === undefined) {
        return undefined;
      } else if ($scope.repo.tags.hasOwnProperty('latest')) {
        return $scope.repo.tags['latest'];
      } else {
        for (key in $scope.repo.tags) {
          return $scope.repo.tags[key];
        }
      }
    };

    var forAllTagImages = function(tag, callback) {
      if (!tag || !$scope.imageByDockerId) { return; }

      var tag_image =  $scope.imageByDockerId[tag.image_id];
      if (!tag_image) { return; }

      // Callback the tag's image itself.
      callback(tag_image);

      // Callback any parent images.
      if (!tag_image.ancestors) { return; }
      var ancestors = tag_image.ancestors.split('/');
      for (var i = 0; i < ancestors.length; ++i) {
        var image = $scope.imageByDockerId[ancestors[i]];
        if (image) {
          callback(image);
        }
      }
    };

    var fetchRepository = function() {
      var params = {'repository': namespace + '/' + name};
      $rootScope.title = 'Loading Repository...';
      $scope.repository = ApiService.getRepoAsResource(params).get(function(repo) {
        // Set the repository object.
        $scope.repo = repo;

        // Set the default tag.
        $scope.setTag($routeParams.tag);

        // Set the title of the page.
        var qualifiedRepoName = namespace + '/' + name;
        $rootScope.title = qualifiedRepoName;
        var kind = repo.is_public ? 'public' : 'private';
        $rootScope.description = jQuery(UtilService.getFirstMarkdownLineAsText(repo.description)).text() ||
          'Visualization of images and tags for ' + kind + ' Docker repository: ' + qualifiedRepoName;

        // Load the builds for this repository. If none are active it will cancel the poll.
        startBuildInfoTimer(repo);
      });
    };

    var startBuildInfoTimer = function(repo) {
      if ($scope.interval) { return; }

      getBuildInfo(repo);
      $scope.interval = setInterval(function() {
        $scope.$apply(function() { getBuildInfo(repo); });
      }, 5000);

      $scope.$on("$destroy", function() {
        cancelBuildInfoTimer();
      });
    };

    var cancelBuildInfoTimer = function() {
      if ($scope.interval) {
        clearInterval($scope.interval);
      }
    };

    var getBuildInfo = function(repo) {
      var params = {
        'repository': repo.namespace + '/' + repo.name
      };

      ApiService.getRepoBuilds(null, params, true).then(function(resp) {
        // Build a filtered list of the builds that are currently running.
        var runningBuilds = [];
        for (var i = 0; i < resp.builds.length; ++i) {
          var build = resp.builds[i];
          if (build['phase'] != 'complete' && build['phase'] != 'error') {
            runningBuilds.push(build);
          }
        }

        var existingBuilds = $scope.runningBuilds || [];
        $scope.runningBuilds = runningBuilds;
        $scope.buildHistory = resp.builds;

        if (!runningBuilds.length) {
          // Cancel the build timer.
          cancelBuildInfoTimer();

          // Mark the repo as no longer building.
          $scope.repo.is_building = false;

          // Reload the repo information if all of the builds recently finished.
          if (existingBuilds.length > 0) {
            loadViewInfo();
          }
        }
      });
    };

    var listImages = function() {
      var params = {'repository': namespace + '/' + name};
      $scope.imageHistory = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
        $scope.images = resp.images;
        $scope.specificImages = [];

        // Build various images for quick lookup of images.
        $scope.imageByDockerId = {};
        for (var i = 0; i < $scope.images.length; ++i) {
          var currentImage = $scope.images[i];
          $scope.imageByDockerId[currentImage.id] = currentImage;
        }

        // Dispose of any existing tree.
        if ($scope.tree) {
          $scope.tree.dispose();
        }

        // Create the new tree.
        var tree = new ImageHistoryTree(namespace, name, resp.images,
            UtilService.getFirstMarkdownLineAsText, $scope.getTimeSince, ImageMetadataService.getEscapedFormattedCommand);

        $scope.tree = tree.draw('image-history-container');
        if ($scope.tree) {
          // If we already have a tag, use it
          if ($scope.currentTag) {
            $scope.tree.setTag($scope.currentTag.name);
          }

          // Listen for changes to the selected tag and image in the tree.
          $($scope.tree).bind('tagChanged', function(e) {
            $scope.$apply(function() { $scope.setTag(e.tag, true); });
          });

          $($scope.tree).bind('imageChanged', function(e) {
            $scope.$apply(function() { $scope.setImage(e.image.id, true); });
          });

          $($scope.tree).bind('showTagMenu', function(e) {
            $scope.$apply(function() { $scope.showTagMenu(e.tag, e.clientX, e.clientY); });
          });

          $($scope.tree).bind('hideTagMenu', function(e) {
            $scope.$apply(function() { $scope.hideTagMenu(); });
          });
        }

        if ($routeParams.image) {
          $scope.setImage($routeParams.image);
        }

        $timeout(function() {
          $scope.tree.notifyResized();
        }, 100);

        return resp.images;
      });
    };

    var loadViewInfo = function() {
      fetchRepository();
      listImages();
    };

    // Fetch the repository itself as well as the image history.
    loadViewInfo();
  }
})();