/**
 * An element which displays and auto-updates the logs from a build.
 */
angular.module('quay').directive('buildLogsView', function () {
  var directiveDefinitionObject = {
    priority: 0,
    templateUrl: '/static/directives/build-logs-view.html',
    replace: false,
    transclude: false,
    restrict: 'C',
    scope: {
      'build': '=build',
      'useTimestamps': '=useTimestamps',
      'buildUpdated': '&buildUpdated'
    },
    controller: function($scope, $element, $interval, $sanitize, ansi2html, AngularViewArray,
                         AngularPollChannel, ApiService, Restangular, UtilService) {

      var result = $element.find('#copyButton').clipboardCopy();
      if (!result) {
        $element.find('#copyButton').hide();
      }

      $scope.logEntries = null;
      $scope.currentParentEntry = null;
      $scope.logStartIndex = 0;
      $scope.buildLogsText = '';
      $scope.currentBuild = null;
      $scope.loadError = null;

      var pollChannel = null;

      var appendToTextLog = function(type, message) {
        if (type == 'phase') {
          text = 'Starting phase: ' + message + '\n';
        } else {
          text = message + '\n';
        }

        $scope.buildLogsText += text.replace(new RegExp("\\033\\[[^m]+m"), '');
      };

      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);
          }

          appendToTextLog(type, entry['message']);
        }

        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': build.repository.namespace + '/' + build.repository.name,
          'build_uuid': build.id
        };

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

          // Call the build updated handler.
          $scope.buildUpdated({'build': resp});

          // Save the current build.
          $scope.currentBuild = resp;

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

          ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
            // If we get a logs url back, then we need to make another XHR request to retrieve the
            // data.
            var logsUrl = resp['logs_url'];
            if (logsUrl) {
              $.ajax({
                url: logsUrl,
              }).done(function(r) {
                handleLogsData(r, callback);
              }).error(function(xhr) {
                if (xhr.status == 0) {
                  UtilService.isAdBlockEnabled(function(result) {
                    $scope.loadError = result ? 'blocked': 'request-failed';
                  });
                } else {
                  $scope.loadError = 'request-failed';
                }
              });

              return;
            }

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

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

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

      var stopWatching = function() {
        if (pollChannel) {
          pollChannel.stop();
          pollChannel = null;
        }
      };

      $scope.$watch('useTimestamps', function() {
        if (!$scope.logEntries) { return; }
        $scope.logEntries = $scope.logEntries.slice();
      });

      $scope.$watch('build', function(build) {
        if (build) {
          startWatching(build);
        } else {
          stopWatching();
        }
      });

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

      $scope.formatDatetime = function(datetimeString) {
        var dt = new Date(datetimeString);
        return dt.toLocaleString();
      }

      $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;
      };
    }
  };
  return directiveDefinitionObject;
});
