(function() {
  /**
   * Repository Build view page. Displays the status of a repository build.
   */
  angular.module('quayPages').config(['pages', function(pages) {
    pages.create('repo-build', 'repo-build.html', RepoBuildCtrl);
  }]);

  function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize,
                         ansi2html, AngularViewArray, AngularPollChannel) {
    var namespace = $routeParams.namespace;
    var name = $routeParams.name;

    // Watch for changes to the current parameter.
    $scope.$on('$routeUpdate', function(){
      if ($location.search().current) {
        $scope.setCurrentBuild($location.search().current, false);
      }
    });

    $scope.builds = null;
    $scope.pollChannel = null;
    $scope.buildDialogShowCounter = 0;

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

    $scope.handleBuildStarted = function(newBuild) {
      if (!$scope.builds) { return; }

      $scope.builds.unshift(newBuild);
      $scope.setCurrentBuild(newBuild['id'], true);
    };

    $scope.adjustLogHeight = function() {
      var triggerOffset = 0;
      if ($scope.currentBuild && $scope.currentBuild.trigger) {
        triggerOffset = 85;
      }
      $('.build-logs').height($(window).height() - 415 - triggerOffset);
    };

    $scope.askRestartBuild = function(build) {
      $('#confirmRestartBuildModal').modal({});
    };

    $scope.askCancelBuild = function(build) {
      bootbox.confirm('Are you sure you want to cancel this build?', function(r) {
        if (r) {
          var params = {
            'repository': namespace + '/' + name,
            'build_uuid': build.id
          };

          ApiService.cancelRepoBuild(null, params).then(function() {
            if (!$scope.builds) { return; }
            $scope.builds.splice($.inArray(build, $scope.builds), 1);

            if ($scope.builds.length) {
              $scope.currentBuild = $scope.builds[0];
            } else {
              $scope.currentBuild = null;
            }
          }, ApiService.errorDisplay('Cannot cancel build'));
        }
      });
    };

    $scope.restartBuild = function(build) {
      $('#confirmRestartBuildModal').modal('hide');

      var subdirectory = build['subdirectory'] || '';

      var data = {
        'file_id': build['resource_key'],
        'subdirectory': subdirectory,
        'docker_tags': build['tags']
      };

      if (build['pull_robot']) {
        data['pull_robot'] = build['pull_robot']['name'];
      }

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

      ApiService.requestRepoBuild(data, params).then(function(newBuild) {
        if (!$scope.builds) { return; }

        $scope.builds.unshift(newBuild);
        $scope.setCurrentBuild(newBuild['id'], true);
      });
    };

    $scope.hasLogs = function(container) {
      return container.logs.hasEntries;
    };

    $scope.setCurrentBuild = function(buildId, opt_updateURL) {
      if (!$scope.builds) { return; }

      // Find the build.
      for (var i = 0; i < $scope.builds.length; ++i) {
        if ($scope.builds[i].id == buildId) {
          $scope.setCurrentBuildInternal(i, $scope.builds[i], opt_updateURL);
          return;
        }
      }
    };

    $scope.processANSI = function(message, container) {
      var filter = container.logs._filter = (container.logs._filter || ansi2html.create());

      // Note: order is important here.
      var setup = filter.getSetupHtml();
      var stream = filter.addInputToStream(message);
      var teardown = filter.getTeardownHtml();
      return setup + stream + teardown;
    };

    $scope.setCurrentBuildInternal = function(index, build, opt_updateURL) {
      if (build == $scope.currentBuild) { return; }

      $scope.logEntries = null;
      $scope.logStartIndex = null;
      $scope.currentParentEntry = null;

      $scope.currentBuild = build;

      if (opt_updateURL) {
        if (build) {
          $location.search('current', build.id);
        } else {
          $location.search('current', null);
        }
      }

      // Timeout needed to ensure the log element has been created
      // before its height is adjusted.
      setTimeout(function() {
        $scope.adjustLogHeight();
      }, 1);

      // Stop any existing polling.
      if ($scope.pollChannel) {
        $scope.pollChannel.stop();
      }

      // Create a new channel for polling the build status and logs.
      var conductStatusAndLogRequest = function(callback) {
        getBuildStatusAndLogs(build, callback);
      };

      $scope.pollChannel = AngularPollChannel.create($scope, conductStatusAndLogRequest, 5 * 1000 /* 5s */);
      $scope.pollChannel.start();
    };

    var processLogs = function(logs, startIndex, endIndex) {
      if (!$scope.logEntries) { $scope.logEntries = []; }

      // If the start index given is less than that requested, then we've received a larger
      // pool of logs, and we need to only consider the new ones.
      if (startIndex < $scope.logStartIndex) {
        logs = logs.slice($scope.logStartIndex - startIndex);
      }

      for (var i = 0; i < logs.length; ++i) {
        var entry = logs[i];
        var type = entry['type'] || 'entry';
        if (type == 'command' || type == 'phase' || type == 'error') {
          entry['logs'] = AngularViewArray.create();
          entry['index'] = $scope.logStartIndex + i;

          $scope.logEntries.push(entry);
          $scope.currentParentEntry = entry;
        } else if ($scope.currentParentEntry) {
          $scope.currentParentEntry['logs'].push(entry);
        }
      }

      return endIndex;
    };

    var handleLogsData = function(logsData, callback) {
      // Process the logs we've received.
      $scope.logStartIndex = processLogs(logsData['logs'], logsData['start'], logsData['total']);

      // If the build status is an error, open the last two log entries.
      var currentBuild = $scope.currentBuild;
      if (currentBuild['phase'] == 'error' && $scope.logEntries.length > 1) {
        var openLogEntries = function(entry) {
          if (entry.logs) {
            entry.logs.setVisible(true);
          }
        };

        openLogEntries($scope.logEntries[$scope.logEntries.length - 2]);
        openLogEntries($scope.logEntries[$scope.logEntries.length - 1]);
      }

      // If the build phase is an error or a complete, then we mark the channel
      // as closed.
      callback(currentBuild['phase'] != 'error' && currentBuild['phase'] != 'complete');
    };

    var getBuildStatusAndLogs = function(build, callback) {
      var params = {
        'repository': namespace + '/' + name,
        'build_uuid': build.id
      };

      ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
        if (build != $scope.currentBuild) { callback(false); return; }

        // Note: We use extend here rather than replacing as Angular is depending on the
        // root build object to remain the same object.
        var matchingBuilds = $.grep($scope.builds, function(elem) {
          return elem['id'] == resp['id']
        });

        var currentBuild = matchingBuilds.length > 0 ? matchingBuilds[0] : null;
        if (currentBuild) {
          currentBuild = $.extend(true, currentBuild, resp);
        } else {
          currentBuild = resp;
          $scope.builds.push(currentBuild);
        }

        // Load the updated logs for the build.
        var options = {
          'start': $scope.logStartIndex
        };

        ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
          if (build != $scope.currentBuild) { callback(false); return; }

          // If we get a logs url back, then we need to make another XHR request to retrieve the
          // data.
          if (resp['logs_url']) {
            $.ajax({
              url: resp['logs_url'],
            }).done(function(r) {
              handleLogsData(r, callback);
            });
            return;
          }

          handleLogsData(resp, callback);
        }, function() {
          callback(false);
        });
      }, function() {
        callback(false);
      });
    };

    var fetchRepository = function() {
      var params = {'repository': namespace + '/' + name};
      $rootScope.title = 'Loading Repository...';
      $scope.repository = ApiService.getRepoAsResource(params).get(function(repo) {
        if (!repo.can_write) {
          $rootScope.title = 'Unknown builds';
          $scope.accessDenied = true;
          return;
        }

        $rootScope.title = 'Repository Builds';
        $scope.repo = repo;

        getBuildInfo();
      });
    };

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

      ApiService.getRepoBuilds(null, params).then(function(resp) {
        $scope.builds = resp.builds;

        if ($location.search().current) {
          $scope.setCurrentBuild($location.search().current, false);
        } else if ($scope.builds.length > 0) {
          $scope.setCurrentBuild($scope.builds[0].id, true);
        }
      });
    };

    fetchRepository();
  }
})();