'use strict';

var directivesModule = require('./_index.js');

var d3 = require('d3');

/**
 * @ngInject
 */
function timelineViewport($document) {
  var link = function(scope, el, attrs, timelineController) {
    var margin = timelineController.margin;
    var height = 200;
    var loaded = false;

    var y = d3.scale.linear();
    var xSelected = timelineController.axes.selection;

    var statusColorMap = timelineController.statusColorMap;

    var chart = d3.select(el[0])
        .append('svg')
        .attr('width', timelineController.width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom);

    var defs = chart.append('defs')
        .append('clipPath')
        .attr('id', 'clip')
        .append('rect')
        .attr('width', timelineController.width);

    var main = chart.append('g')
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
        .attr('width', timelineController.width);

    var laneLines = main.append('g');
    var laneLabels = main.append('g');

    var itemGroups = main.append('g');

    var cursorGroup = main.append('g')
        .style('opacity', 0)
        .style('pointer-events', 'none');

    var cursor = cursorGroup.append('line')
        .attr('x1', 0)
        .attr('x2', 0)
        .attr('stroke', 'blue');

    var cursorText = cursorGroup.append('text')
        .attr('x', 0)
        .attr('y', -10)
        .attr('dy', '-.5ex')
        .style('text-anchor', 'middle')
        .style('font', '9px sans-serif');

    var selectedRect = null;

    var color = function(rect, color) {
      if (!rect.attr('data-old-fill')) {
        rect.attr('data-old-fill', rect.attr('fill'));
      }

      rect.attr('fill', color);
    };

    var uncolor = function(rect) {
      if (!$document[0].contains(rect[0][0])) {
        // we load the original colored rect so we can't unset its color,
        // force a full reload
        updateItems(timelineController.data);
        return;
      }

      if (rect.attr('data-old-fill')) {
        rect.attr('fill', rect.attr('data-old-fill'));
        rect.attr('data-old-fill', null);
      }
    };

    var rectMouseOver = function(d) {
      if (selectedRect !== null) {
        return;
      }

      timelineController.setHover(d);
      scope.$apply();

      color(d3.select(this), statusColorMap.hover);
    };

    var rectMouseOut = function(d) {
      if (selectedRect !== null) {
        return;
      }

      timelineController.clearHover();
      scope.$apply();

      var self = d3.select(this);
      uncolor(d3.select(this));
    };

    var rectClick = function(d) {
      timelineController.selectItem(d);
      scope.$apply();
    };

    var updateLanes = function(data) {
      var lines = laneLines.selectAll('.laneLine')
          .data(data, function(d) { return d.key; });

      lines.enter().append('line')
          .attr('x1', 0)
          .attr('x2', timelineController.width)
          .attr('stroke', 'lightgray')
          .attr('class', 'laneLine');

      lines.attr('y1', function(d, i) { return y(i - 0.1); })
          .attr('y2', function(d, i) { return y(i - 0.1); });

      lines.exit().remove();

      var labels = laneLabels.selectAll('.laneLabel')
          .data(data, function(d) { return d.key; });

      labels.enter().append('text')
          .text(function(d) { return 'Worker #' + d.key; })
          .attr('x', -margin.right)
          .attr('dy', '.5ex')
          .attr('text-anchor', 'end')
          .attr('class', 'laneLabel');

      labels.attr('y', function(d, i) { return y(i + 0.5); });
      labels.exit().remove();

      cursor.attr('y2', y(data.length - 0.1));
    };

    var updateItems = function(data) {
      var extent = timelineController.viewExtents;
      var minExtent = extent[0];
      var maxExtent = extent[1];

      // filter visible items to include only those within the current extent
      // additionally prune extremely small values to improve performance
      var visibleItems = data.map(function(group) {
        return {
          key: group.key,
          values: group.values.filter(function(e) {
            if (xSelected(e.endDate) - xSelected(e.startDate) < 2) {
              return false;
            }

            if (e.startDate > maxExtent || e.endDate < minExtent) {
              return false;
            }

            return true;
          })
        };
      });

      var groups = itemGroups.selectAll("g")
          .data(visibleItems, function(d) { return d.key; });

      groups.enter().append("g");

      var rects = groups.selectAll("rect")
          .data(function(d) { return d.values; }, function(d) { return d.name; });

      rects.enter().append("rect")
          .attr('y', function(d) { return y(d.worker); })
          .attr('height', 0.8 * y(1))
          .attr('stroke', 'rgba(100, 100, 100, 0.25)')
          .attr('clip-path', 'url(#clip)');

      rects
          .attr('x', function(d) {
            return xSelected(d.startDate);
          })
          .attr('width', function(d) {
            return xSelected(d.endDate) - xSelected(d.startDate);
          })
          .attr('fill', function(d) {
            if (timelineController.selectionName === d.name) {
              return statusColorMap.selected;
            } else {
              return statusColorMap[d.status];
            }
          })
          .attr('data-old-fill', function(d) {
            if (timelineController.selectionName === d.name) {
              return statusColorMap[d.status];
            } else {
              return null;
            }
          })
          .on("mouseover", rectMouseOver)
          .on('mouseout', rectMouseOut)
          .on('click', rectClick);

      rects.exit().remove();
      groups.exit().remove();
    };

    var update = function(data) {
      updateItems(timelineController.data);
      updateLanes(timelineController.data);
    };

    var select = function(rect) {
      if (selectedRect) {
        uncolor(selectedRect);
      }

      selectedRect = rect;

      if (rect !== null) {
        color(rect, statusColorMap.selected);
      }
    };

    chart.on('mouseout', function() {
      cursorGroup.style('opacity', 0);
    });

    chart.on('mousemove', function() {
      var pos = d3.mouse(this);
      var px = pos[0];
      var py = pos[1];

      if (px >= margin.left && px < (timelineController.width + margin.left) &&
          py > margin.top && py < (height + margin.top)) {
        var relX = px - margin.left;
        var currentTime = new Date(xSelected.invert(relX));

        cursorGroup
            .style('opacity', '0.5')
            .attr('transform', 'translate(' + relX + ', 0)');

        cursorText.text(d3.time.format('%X')(currentTime));
      }
    });

    scope.$on('dataLoaded', function(event, data) {
      y.domain([0, data.length]).range([0, height]);

      defs.attr('height', height);
      main.attr('height', height);
      cursor.attr('y1', y(-0.1));

      loaded = true;
    });

    scope.$on('update', function() {
      if (!loaded) {
        return;
      }

      chart.style('width', timelineController.width + margin.left + margin.right);
      defs.attr('width', timelineController.width);
      main.attr('width', timelineController.width);

      update(timelineController.data);
    });

    scope.$on('updateView', function() {
      if (!loaded) {
        return;
      }

      update(timelineController.data);
    });

    scope.$on('postSelect', function(event, selection) {
      if (selection) {
        // iterate over all rects to find match
        itemGroups.selectAll('rect').each(function(d) {
          if (d.name === selection.item.name) {
            select(d3.select(this));
          }
        });
      } else {
        select(null);
      }
    });
  };

  return {
    restrict: 'E',
    require: '^timeline',
    scope: true,
    link: link
  };
}

directivesModule.directive('timelineViewport', timelineViewport);