/**
 * 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;

      $scope.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, automatically open the last command run.
        var currentBuild = $scope.currentBuild;
        if (currentBuild['phase'] == 'error') {
          for (var i = $scope.logEntries.length - 1; i >= 0; i--) {
            var currentEntry = $scope.logEntries[i];
            if (currentEntry['type'] == 'command') {
              currentEntry['logs'].setVisible(true);
              break;
            }
          }
        }

        // 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) {
                $scope.$apply(function() {
                  handleLogsData(r, callback);
                });
              }).error(function(xhr) {
                $scope.$apply(function() {
                  if (xhr.status == 0) {
                    UtilService.isAdBlockEnabled(function(result) {
                      $scope.loadError = result ? 'blocked': 'request-failed';
                    });
                  } else {
                    $scope.loadError = 'request-failed';
                  }
                });
              });

              return;
            }

            handleLogsData(resp, callback);
          }, function(resp) {
            if (resp.status == 403) {
              $scope.loadError = 'unauthorized';
            } else {
              $scope.loadError = 'request-failed';
            }
            callback(false);
          });
        }, function() {
          $scope.loadError = 'request-failed';
          callback(false);
        });
      };

      var startWatching = function(build) {
        // 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 stopWatching = function() {
        if ($scope.pollChannel) {
          $scope.pollChannel.stop();
          $scope.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) {
        // Note: The standard format required by the Date constructor in JS is
        // "2011-10-10T14:48:00" for date-times. The date-time string we get is exactly that,
        // but with a space instead of a 'T', so we just replace it.
        var dt = new Date(datetimeString.replace(' ', 'T'));
        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;
});