import revertModalTemplate from 'orchestra/project-management/revert-modal.html'

let d3 = require('d3')

export default function tasksVis (
    $uibModal, dataService, orchestraApi, visUtils, assignmentsVis, crosshair, axis) {
  /**
   * Service to modularize task visualization and manipulation within
   * the project management view.
   */
  'ngAnnotate'
  var _humanizeAudit = function (audit) {
    /**
     * Selectively render a revert audit in a human-readable way.
     */
    var humanAudit = {
      'step': audit.task.step_slug,
      'change': audit.change,
      'assignments': {}
    }
    audit.assignments.forEach(function (assignmentAudit) {
      humanAudit.assignments[assignmentAudit.assignment.worker.username] = {
        'change': assignmentAudit.change,
        'iterations': assignmentAudit.iterations
      }
    })
    return humanAudit
  }

  var _hasOneClassFrom = function (target, classes) {
    /**
     * Determine if a d3 selection has at least one class from a given list.
     */
    var cls
    for (var i = 0; i < classes.length; i++) {
      cls = target.classed(classes[i])
      if (cls) {
        break
      }
    }
    return cls
  }

  return {
    reverting: false,
    draw: function () {
      /**
       * Draws/updates tasks within project management visualization.
       */
      var tasksVis = this
      var taskViews = visUtils.parentContainer.selectAll('.task-view')
        .data(dataService.timeSortedSlugs, function (slug) {
          return slug
        })
      taskViews.exit().remove()
      var taskViewsEnter = taskViews.enter().append('div')
        .attr('class', 'task-view')

      var taskSvgsEnter = taskViewsEnter.append('svg')
        .attr('class', 'task-svg')
        .style('margin-left', visUtils.params.marginLeft + 'px')

      taskViews.selectAll('.task-svg').attr('width', visUtils.getSvgWidth())
      taskViews.style('width', visUtils.getSvgWidth() + visUtils.params.marginLeft + 'px')

      var tasksEnter = taskSvgsEnter.append('g')
        .attr('class', 'task')

      taskViews.selectAll('.task')
        .transition()
        .attr({
          'transform': function (taskKey) {
            var task = dataService.taskFromKey(taskKey)
            return visUtils.translateString(axis.getOffset(task.start_datetime), visUtils.params.lanePadding.top)
          }
        })

      tasksVis.drawRevertFlags()

      assignmentsVis.draw()

      tasksEnter.append('rect')
        .attr({
          'class': 'task-rect',
          'height': visUtils.params.barHeight,
          'fill-opacity': 0,
          'stroke': 'black'
        })

      taskViews.selectAll('.task-rect')
        .transition()
        .attr('width', function (slug) {
          var task = dataService.taskFromKey(slug)
          return axis.getOffset(dataService.taskEnd(task)) - axis.getOffset(task.start_datetime)
        })
        .each(function (slug) {
          tasksVis.expand(d3.select(tasksVis.parentNode))
        })

      taskViews.on('click', function (taskKey) {
        var target = d3.select(d3.event.target)
        var classes = ['task-view', 'task-svg', 'task-rect']
        if (!_hasOneClassFrom(target, classes)) {
          return
        }
        var expandAssignments = dataService.taskMeta(taskKey, 'expandAssignments')
        dataService.taskMeta(taskKey, 'expandAssignments', !expandAssignments)
        tasksVis.distribute()
      })

      this.drawMeta()
      this.drawBackgrounds()
      tasksVis.distribute()
    },
    drawMeta: function () {
      /**
       * Draws task step slugs, statuses, and action buttons to the left of
       * the main project management visualization.
       */
      var tasksVis = this
      var taskNames = d3.select('.task-names').selectAll('.task-name')
        .data(dataService.timeSortedSlugs, function (slug) {
          return slug
        })
      taskNames.exit().remove()
      var taskNamesEnter = taskNames.enter().append('div')
        .attr('class', 'task-name')
      taskNamesEnter.append('span')
        .attr('class', 'step-slug')
      var taskActionWrappers = taskNamesEnter.append('div').attr('class', 'task-action-wrapper')
      var actions = taskNames.selectAll('.task-action-wrapper').selectAll('.skip-task')
        .data(function (taskKey) {
          return dataService.taskFromKey(taskKey).status !== 'Complete' ? [taskKey] : []
        })
      actions.exit().remove()
      actions.enter().append('button')
        .attr('class', 'skip-task task-action btn btn-danger btn-xs')
        .text('Skip task')
        .on('click', function (taskKey) {
          tasksVis.completeAndSkipTask(dataService.taskFromKey(taskKey))
        })
      taskActionWrappers.append('a')
        .attr({
          'href': function (taskKey) {
            return dataService.taskFromKey(taskKey).admin_url
          },
          'target': '_blank',
          'class': 'task-action'
        })
        .append('button')
        .attr('class', 'btn btn-default btn-xs')
        .text('View in admin')

      taskNamesEnter.append('span').attr('class', 'step-status')

      taskNames.selectAll('.step-slug').text(function (slug) {
        var task = dataService.taskFromKey(slug)
        return task.step_slug + ' ' + task.id
      })

      taskNames.selectAll('.step-status').text(function (slug) {
        var task = dataService.taskFromKey(slug)
        return task.status
      })
    },
    drawBackgrounds: function () {
      /**
       * Renders each task "swim lane" with an alternating color.
       */
      d3.selectAll('.task-name')
        .style('background-color', function (slug, i, j) {
          return i % 2 === 0 ? '#eee' : 'white'
        })
      visUtils.parentContainer.selectAll('.task-view')
        .style('background-color', function (slug, i) {
          return i % 2 === 0 ? '#eee' : 'white'
        })
    },
    drawRevertFlags: function () {
      /**
       * Draws the flags corresponding to suitable task revert times.
       */
      var tasksVis = this
      var tasks = visUtils.parentContainer.selectAll('.task')
      var revertGroups = tasks.selectAll('.revert-group').data(function (slug) {
        var task = dataService.taskFromKey(slug)
        var datetimes = []
        task.assignments.forEach(function (assignment) {
          if (assignment.iterations) {
            assignment.iterations.forEach(function (iteration, i) {
              if (i === 0) {
                // If iteration is the first of its assignment, we can
                // revert to before it was created.
                datetimes.push({
                  'datetime': new Date(iteration.start_datetime),
                  'taskKey': task.step_slug,
                  'iterationId': iteration.id,
                  'revertBefore': true
                })
              }
              if (iteration.status !== 'Processing') {
                // If iteration isn't processing, we can revert to it
                datetimes.push({
                  'datetime': new Date(iteration.end_datetime),
                  'taskKey': task.step_slug,
                  'iterationId': iteration.id,
                  'revertBefore': false
                })
              }
            })
          }
        })
        return datetimes
      }, function (datetime) {
        return datetime.datetime
      })
      revertGroups.exit().remove()
      var revertGroupsEnter = revertGroups.enter().append('g')
        .attr('class', 'revert-group')

      revertGroupsEnter.append('line')
        .attr({
          'class': 'revert-line',
          'stroke': 'rgb(0, 121, 191)'
        })

      // Revert flags
      revertGroupsEnter.append('path')
        .attr({
          'd': 'M0,0 V4 L-2,2 Z',
          'fill': 'rgb(0, 121, 191)',
          'transform': visUtils.translateString(0, -visUtils.params.lanePadding.top / 2) + ' scale(3, 3)'
        })
        .style('cursor', 'pointer')
        .on('mouseenter', function (datetimeInfo) {
          if (!tasksVis.reverting) {
            crosshair.move(datetimeInfo.datetime)
            crosshair.show()
          }
        })
        .on('mouseleave', function (datetimeInfo) {
          if (!tasksVis.reverting) {
            crosshair.hide()
          }
        })
        .on('click', function (datetimeInfo) {
          var taskId = dataService.taskFromKey(datetimeInfo.taskKey).id
          tasksVis.revertTask(taskId, datetimeInfo.iterationId, datetimeInfo.revertBefore)
        })

      revertGroups.transition().attr({
        'transform': function (datetimeInfo) {
          var taskStartDatetime = dataService.taskFromKey(datetimeInfo.taskKey).start_datetime
          return visUtils.translateString(
            axis.timeScale(datetimeInfo.datetime) - axis.getOffset(taskStartDatetime), 0
          )
        }
      })

      revertGroups.selectAll('.revert-line')
        .transition()
        .attr({
          'y1': -visUtils.params.lanePadding.top / 2,
          'y2': function (datetimeInfo) {
            var taskKey = datetimeInfo.taskKey
            var task = dataService.taskFromKey(taskKey)
            if (dataService.taskMeta(taskKey, 'expandAssignments')) {
              return (task.assignments.length + 1) * visUtils.params.barHeight + 1
            } else {
              return visUtils.params.barHeight + 1
            }
          }
        })
    },
    expand: function () {
      /**
       * Toggles expansion of selected tasks to show more detailed assignment
       * data and actions.
       */
      var taskViews = d3.selectAll('.task-view')

      taskViews.selectAll('.assignments')
        .transition()
        .attr('transform', function (taskKey) {
          var expand = dataService.taskMeta(taskKey, 'expandAssignments')
          visUtils.translateString(0, expand ? visUtils.params.barHeight : 0)
        })

      taskViews.selectAll('.assignment')
        .transition()
        .attr({
          'transform': function (assignmentKey, i) {
            var assignment = dataService.assignmentFromKey(assignmentKey)
            var taskKey = dataService.keyFromTask(assignment.task)
            var expand = dataService.taskMeta(taskKey, 'expandAssignments')
            return visUtils.translateString(0, expand ? (visUtils.params.barHeight * (i + 1)) : 0)
          }
        })

      taskViews.selectAll('.assignment-meta')
        .transition()
        .style({
          'position': 'absolute',
          'top': function (assignmentKey, i) {
            var assignment = dataService.assignmentFromKey(assignmentKey)
            var taskKey = dataService.keyFromTask(assignment.task)
            var expand = dataService.taskMeta(taskKey, 'expandAssignments')
            if (expand) {
              return (visUtils.params.barHeight * (i + 1) + visUtils.params.lanePadding.top + 4) + 'px'
            }
            return visUtils.params.lanePadding.top + 'px'
          },
          'right': function (assignmentKey) {
            var assignment = dataService.assignmentFromKey(assignmentKey)
            return (visUtils.getSvgWidth() - axis.getOffset(assignment.task.start_datetime) + 10) + 'px'
          },
          'display': function (assignmentKey, i) {
            var assignment = dataService.assignmentFromKey(assignmentKey)
            var taskKey = dataService.keyFromTask(assignment.task)
            var expand = dataService.taskMeta(taskKey, 'expandAssignments')
            return expand ? 'inherit' : 'none'
          }
        })

      taskViews.selectAll('.active-assignment')
        .attr('display', function (assignmentKey) {
          var assignment = dataService.assignmentFromKey(assignmentKey)
          var taskKey = dataService.keyFromTask(assignment.task)
          var expand = dataService.taskMeta(taskKey, 'expandAssignments')
          return expand ? 'inherit' : 'none'
        })
    },
    distribute: function () {
      /**
       * Vertically distribute task visualizations inside their container.
       */
      visUtils.parentContainer.selectAll('.task-svg')
        .transition()
        .attr({
          'height': function (taskKey) {
            var task = dataService.taskFromKey(taskKey)
            return visUtils.getTaskHeight(task)
          }
        })

      var taskNamesWrapper = d3.selectAll('.task-names').style('margin-top', visUtils.params.scaleHeight + 'px')
      taskNamesWrapper.selectAll('.task-name')
        .transition()
        .style({
          'height': function (slug, i) {
            var task = dataService.taskFromKey(slug)
            return visUtils.getTaskHeight(task) + 'px'
          },
          'padding-top': visUtils.params.lanePadding.top / 2 + 'px'
        })

      this.drawRevertFlags()
      this.expand()
    },
    completeAndSkipTask: function (task) {
      /**
       * Handles the promise returned by orchestraApi.completeAndSkipTask
       * in the visualization.
       */
      if (!window.confirm('Are you sure you want to skip this task and mark it ' +
        'as complete? This might leave the project in a ' +
        'corrupted/unrecoverable state.')) {
        return
      }
      orchestraApi.completeAndSkipTask(task.id)
        .then(function () {
          dataService.updateData()
        }, function (response) {
          var errorMessage = 'Error skipping task.'
          if (response.status === 400) {
            errorMessage = response.data.message
          }
          window.alert(errorMessage)
        })
    },
    revertTask: function (taskId, iterationId, revertBefore) {
      /**
       * Displays a modal containing the audit trail of items to be reverted.
       * Handles the promise returned by orchestraApi.revertTask in the
       * visualization.
       */
      var tasksVis = this
      if (tasksVis.reverting) {
        return
      }
      tasksVis.reverting = true
      orchestraApi.revertTask(taskId, iterationId, revertBefore, false)
        .then(function (response) {
          var modalInstance = $uibModal.open({
            template: revertModalTemplate,
            controller: function ($scope) {
              $scope.audit = _humanizeAudit(response.data)
              $scope.cancel = modalInstance.close
              $scope.confirmRevert = function () {
                orchestraApi.revertTask(taskId, iterationId, revertBefore, true)
                  .then(function () {
                    dataService.updateData()
                  }, function (response) {
                    var errorMessage = 'Could not revert task.'
                    if (response.status === 400) {
                      errorMessage = response.data.message
                    }
                    window.alert(errorMessage)
                  })
                  .finally(function () {
                    modalInstance.close()
                  })
              }
            }
          })

          modalInstance.result.finally(function () {
            tasksVis.reverting = false
            crosshair.hide()
          })
        }, function (response) {
          var errorMessage = 'Could not generate revert information.'
          if (response.status === 400) {
            errorMessage = response.data.message
          }
          window.alert(errorMessage)
        })
    }
  }
}
